├── .circleci
└── config.yml
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
├── PULL_REQUEST_TEMPLATE
└── stale.yml
├── .gitignore
├── LICENSE
├── README.md
├── docs
├── .gitignore
└── header.md
├── endpoints
├── address
│ ├── documentation.md
│ ├── graphql_schema.js
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── aur
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── bus
│ ├── documentation.md
│ ├── graphql_schema.js
│ ├── index.js
│ ├── realtime.js
│ ├── search.js
│ └── tests
│ │ └── integration_test.js
├── calendar
│ ├── graphql_schema.js
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── car
│ ├── documentation.md
│ ├── graphql_schema.js
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── carparks
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── cinema
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── company
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── concerts
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── currency
│ ├── arion.js
│ ├── borgun.js
│ ├── documentation.md
│ ├── index.js
│ ├── lb.js
│ ├── m5.js
│ └── tests
│ │ ├── arion.fixture
│ │ ├── integration_test.js
│ │ └── m5.fixture
├── cyclecounter
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── declension
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── earthquake
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ ├── integration_test.js
│ │ └── test.fixture
├── flight
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── frontpage
│ └── index.js
├── golf
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── hljomaholl
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── hospital
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ ├── integration_test.js
│ │ └── test.fixture
├── isbolti
│ ├── index.js
│ └── tests
│ │ ├── integration_test.js
│ │ └── test.fixture
├── isnic
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── lottery
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── names
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── particulates
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── petrol
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── rides
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── sarschool
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ ├── integration_test.js
│ │ └── test.fixture
├── ship
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── sports
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── static_endpoints
│ ├── frontpage.js
│ ├── help_out.js
│ └── phone.js
├── tv
│ ├── 365.js
│ ├── documentation.md
│ ├── index.js
│ ├── ruv.js
│ ├── skjarinn.js
│ └── tests
│ │ └── integration_test.js
└── weather
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ └── integration_test.js
├── graphql
└── types
│ └── GraphQLDate.js
├── index.js
├── lib
├── cache.js
├── cors.js
├── expressMetrics.js
├── expressMetrics.spec.js
├── redis.js
└── test_helpers.js
├── make-docs.js
├── mock-data.json
├── package-lock.json
├── package.json
├── server.js
└── test.js
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | # Javascript Node CircleCI 2.0 configuration file
2 | #
3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details
4 | #
5 | version: 2
6 | jobs:
7 | build:
8 | docker:
9 | # specify the version you desire here
10 | - image: circleci/node:7.10
11 |
12 | # Specify service dependencies here if necessary
13 | # CircleCI maintains a library of pre-built images
14 | # documented at https://circleci.com/docs/2.0/circleci-images/
15 | # - image: circleci/mongo:3.4.4
16 |
17 | working_directory: ~/repo
18 |
19 | steps:
20 | - checkout
21 |
22 | # Download and cache dependencies
23 | - restore_cache:
24 | keys:
25 | - v1-dependencies-{{ checksum "package.json" }}
26 | # fallback to using the latest cache if no exact match is found
27 | - v1-dependencies-
28 |
29 | - run: npm install
30 |
31 | - save_cache:
32 | paths:
33 | - node_modules
34 | key: v1-dependencies-{{ checksum "package.json" }}
35 |
36 | - run: npm run lint
37 | - run: npm run test
38 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # Lines starting with '#' are comments.
2 | # Each line is a file pattern followed by one or more owners.
3 |
4 | # These owners will be the default owners for everything in the repo.
5 | * @koddsson @kristjanmik @benediktvaldez @MiniGod
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE:
--------------------------------------------------------------------------------
1 | Describe the issue you are having here.
2 |
3 | ### Steps to reproduce
4 |
5 | Is it a issue with:
6 | - the service running on http://apis.is?
7 | - running the service locally?
8 | - documentation?
9 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE:
--------------------------------------------------------------------------------
1 | Describe this code change as best you can here. Is it a new endpoint, bugfix or
2 | changes to the documentation?
3 |
4 | ### Checklist
5 |
6 | - [ ] Write tests
7 | - [ ] Write documentation
8 |
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - bug
8 | # Label to use when marking an issue as stale
9 | staleLabel: wontfix
10 | # Comment to post when marking an issue as stale. Set to `false` to disable
11 | markComment: >
12 | This issue has been automatically marked as stale because it has not had
13 | recent activity. It will be closed if no further activity occurs. Thank you
14 | for your contributions.
15 | # Comment to post when closing a stale issue. Set to `false` to disable
16 | closeComment: false
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | npm-debug.log
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2012-2017 APIs.is
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [APIs.is](http://apis.is) - Making data pretty since 2012!
2 |
3 | [](https://codeship.com/projects/63542)
4 | [](https://greenkeeper.io/)
5 |
6 | The purpose of [APIs.is](http://apis.is) is to make data readily available to anyone interested. All data that is delivered through APIs.is is JSON formatted and scraped from open public websites.
7 |
8 | The code that is running the service is open source under the [MIT licence](https://en.wikipedia.org/wiki/MIT_License). The platform itself is hosted on a load balanced setup by [Advania](https://www.advania.com/).
9 |
10 | **Don't hesitate to lend a hand - All knowledge and help is much appreciated!**
11 |
12 | ## Maintainers
13 |
14 | [@kristjanmik](https://github.com/kristjanmik/)
15 |
16 | [@benediktvaldez](https://github.com/benediktvaldez/)
17 |
18 | [@koddsson](https://github.com/koddsson/)
19 |
20 | [@MiniGod](https://github.com/minigod/)
21 |
22 | ## Running locally
23 |
24 | To run the project locally, first clone this repository...
25 | ```sh
26 | $ git clone https://github.com/apis-is/apis.git
27 | ```
28 |
29 | .... install the dependencies and run the project.
30 |
31 | ```sh
32 | $ npm install
33 | [Bunch of output]
34 | $ npm run
35 | ```
36 |
37 | ## Tests
38 |
39 | To run the tests:
40 | ```sh
41 | $ npm test
42 | ```
43 |
44 | The tests utilize a man-in-the-middle library called [nock](https://github.com/node-nock/nock) that
45 | intercepts requests that the tests made and respond with data from disk. The data was generated using
46 | the [record feature](https://github.com/node-nock/nock#recording) and saved in [`mock-data.json`](mock-data.json).
47 |
48 | If a endpoints data source has changed and the we need to re-record this data we can simply set the
49 | env variable `RECORD_MOCK_DATA` to a truthy value and run the tests. This will disable nock in the tests
50 | and make requests to each endpoints data source and save that to disk.
51 |
52 | ```sh
53 | RECORD_MOCK_DATA=true npm test
54 | ```
55 |
56 | Newly added endpoints should mock the endpoints data source using the `nock` API since this initial
57 | data mocking was only made to help migrate to a mocking library. See the [original PR](https://github.com/apis-is/apis/pull/376)
58 | for more info.
59 |
60 | ## Adding a new Endpoint
61 |
62 | ### Step by Step
63 |
64 | 1. View current endpoints for structure and hierarchy.
65 | 2. Add a new folder to the `endpoints/` directory with the name of your endpoint.
66 | 3. The file will be loaded automatically. Remember to require the server. Bare minimum example endpoint:
67 |
68 | ```javascript
69 | const app = require('../../server');
70 |
71 | app.get('/path', (req,res) => {
72 | //Sends out empty json object
73 | return res.json({});
74 | });
75 | ```
76 |
77 | ### Additional requirements
78 |
79 | Add integration tests to the endpoint by creating a file called `integration_test.js` inside a `tests/` folder within your endpoint directory. For reference, please take a look at one of the integration tests.
80 |
81 | Add documentation for your endpoint to the `gh-pages` branch of this repo.
82 |
83 | ### More servers
84 |
85 | To ensure close to zero downtime, the plan is to start up more workers/servers around the world so that projects relying on this service will not be affected. Want to help out with that? Feel free to contact us by opening a [issue](https://github.com/apis-is/apis/issues/new).
86 |
87 | ### Helpful pointers
88 |
89 | - Endpoints can implement any node module, but try to use the ones that are already included in the project.
90 | - Information on how to handle requests and responses can be found [here](http://expressjs.com/api.html).
91 | - It is much appreciated that endpoints are thoroughly documented and written with care.
92 | - Issues are managed by the [GitHub issue tracker](https://github.com/apis-is/apis/issues).
93 | - Have fun and eat some cake! (preferrably just some plain vanilla cake, but whatever floats your boat)
94 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
--------------------------------------------------------------------------------
/docs/header.md:
--------------------------------------------------------------------------------
1 |
2 |
47 |
48 |
--------------------------------------------------------------------------------
/endpoints/address/documentation.md:
--------------------------------------------------------------------------------
1 | # Icelandic Addresses
2 |
3 | Source [Iceland Post](https://postur.is)
4 |
5 | - GET [/address](https://apis.is/address)
6 |
7 | Lookup addresses in Iceland through the Icelandic Post API
8 |
9 | | Parameters | Description | Example |
10 | |--------------------|---------------|--------------------------------------------------------|
11 | | Address (required) | Valid address | [Laugarvegur 1](https://apis.is/address/Laugarvegur 1) |
12 |
13 | ---
14 |
--------------------------------------------------------------------------------
/endpoints/address/graphql_schema.js:
--------------------------------------------------------------------------------
1 | const {
2 | GraphQLObjectType,
3 | GraphQLList,
4 | GraphQLString,
5 | } = require('graphql')
6 |
7 | const lookupAddresses = require('.')
8 |
9 | const addressType = new GraphQLObjectType({
10 | name: 'Address',
11 | fields: {
12 | street: {
13 | type: GraphQLString,
14 | description: 'The name of the street the address belongs to',
15 | },
16 | house: {
17 | type: GraphQLString,
18 | description: 'The house number the address belongs to',
19 | },
20 | zip: {
21 | type: GraphQLString,
22 | description: 'The zip code where this address is registered',
23 | },
24 | city: {
25 | type: GraphQLString,
26 | description: 'The city where this address is registered',
27 | },
28 | apartment: {
29 | type: GraphQLString,
30 | description: 'A name for an apartment belonging to this address',
31 | },
32 | letter: {
33 | type: GraphQLString,
34 | },
35 | },
36 | })
37 |
38 | const addressesType = new GraphQLList(addressType)
39 |
40 | module.exports = {
41 | type: addressesType,
42 | args: {
43 | address: { type: GraphQLString },
44 | },
45 | resolve: (_, args) => {
46 | const address = args.address.replace(' ', '+')
47 | return lookupAddresses(address)
48 | },
49 | }
50 |
--------------------------------------------------------------------------------
/endpoints/address/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/first */
2 | const request = require('request')
3 | const h = require('apis-helpers')
4 | const _ = require('lodash')
5 | const app = require('../../server')
6 |
7 | const lookupAddresses = address => new Promise((resolve, reject) => {
8 | request.get({
9 | headers: { 'User-Agent': h.browser() },
10 | url: `https://api.postur.is/PosturIs/ws.asmx/GetPostals?address=${address}`,
11 | }, (error, response, body) => {
12 | if (error || response.statusCode !== 200) {
13 | reject(error)
14 | }
15 |
16 | // There is a enclosing () in the response
17 | const data = _.flatten(JSON.parse(body.replace(/[()]/g, '')))
18 |
19 | const results = _.map(data, elem => ({
20 | street: elem.Gata,
21 | house: elem.Husnumer,
22 | zip: elem.Postnumer,
23 | city: elem.Sveitafelag,
24 | apartment: elem.Ibud,
25 | letter: elem.Stafur,
26 | }))
27 | resolve(results)
28 | })
29 | })
30 |
31 | app.get('/address/:address?', async (req, res) => {
32 | const address = (
33 | req.query.address || req.params.address || ''
34 | ).replace(' ', '+')
35 |
36 | if (address === '') {
37 | res.status(400).json({
38 | error: 'Please provide a valid address to lookup',
39 | })
40 | }
41 |
42 | try {
43 | const results = await lookupAddresses(address)
44 | res.cache().json({ results })
45 | } catch (error) {
46 | res.status(500).json({ error: 'www.postur.is refuses to respond or give back data' })
47 | }
48 | })
49 |
50 | module.exports = lookupAddresses
51 |
--------------------------------------------------------------------------------
/endpoints/address/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const helpers = require('../../../lib/test_helpers')
3 |
4 | describe('zip', () => {
5 | it('should return an array of objects containing correct fields', (done) => {
6 | const fieldsToCheckFor = ['street', 'house', 'zip', 'city', 'apartment', 'letter']
7 | const params = helpers.testRequestParams('/address', { address: 'laugavegur 26' })
8 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
9 | request.get(params, resultHandler)
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/endpoints/aur/documentation.md:
--------------------------------------------------------------------------------
1 | # Auroracoin to ISK exchange
2 |
3 | Source [ISX.is](https://isx.is)
4 |
5 | - GET [/aur](https://apis.is/aur)
6 |
7 | Current Auroracoin exchange rate and various statistics for the past day.
8 |
9 | ---
10 |
--------------------------------------------------------------------------------
/endpoints/aur/index.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const moment = require('moment')
3 | const app = require('../../server')
4 |
5 | // set cache time to 2 minutes
6 | const cacheTime = 120
7 |
8 | function queryGzipJson(url, callback) {
9 | const headers = {
10 | 'user-agent': 'apis.is',
11 | }
12 | request.get({
13 | headers,
14 | url,
15 | gzip: true,
16 | json: true,
17 | }, (error, response, data) => {
18 | if (error || response.statusCode !== 200) {
19 | callback(error, response, null)
20 | }
21 | callback(error, response, data)
22 | })
23 | }
24 |
25 | function queryStats(callback) {
26 | queryGzipJson('https://isx.is/api/stats?currency=isk&market=aur', callback)
27 | }
28 |
29 | function queryHistory(callback) {
30 | // permitted values for timeframe are "1mon", "3mon", "6mon", "1year" and "ytd"
31 | // default is "1mon", "ytd" is from last new years eve to present
32 | queryGzipJson(
33 | 'https://isx.is/api/historical-prices?currency=isk&market=aur&timeframe=1year',
34 | callback
35 | )
36 | }
37 |
38 | function queryTransactions(callback) {
39 | // set limit to OVER 9000
40 | queryGzipJson(
41 | 'https://isx.is/api/transactions?currency=isk&market=aur&limit=9001',
42 | callback
43 | )
44 | }
45 |
46 | function queryOrderBook(callback) {
47 | queryGzipJson('https://isx.is/api/order-book?currency=isk&market=aur', callback)
48 | }
49 |
50 | function formatStatsData(data) {
51 | return {
52 | volume_1h: data.stats['1h_volume'],
53 | volume_1h_buy: data.stats['1h_volume_buy'],
54 | volume_1h_sell: data.stats['1h_volume_sell'],
55 | volume_24h: data.stats['24h_volume'],
56 | volume_24h_buy: data.stats['24h_volume_buy'],
57 | volume_24h_sell: data.stats['24h_volume_sell'],
58 | ask: data.stats.ask,
59 | bid: data.stats.bid,
60 | daily_change: data.stats.daily_change,
61 | daily_change_percent: data.stats.daily_change_percent,
62 | global_units: data.stats.global_units,
63 | global_volume: data.stats.global_volume,
64 | last_price: data.stats.last_price,
65 | last_transaction_type: data.stats.last_transaction_type.toLowerCase(),
66 | market_cap: data.stats.market_cap,
67 | max: data.stats.max,
68 | min: data.stats.min,
69 | open: data.stats.open,
70 | timestampApis: moment().format('YYYY-MM-DDTHH:mm:ss'),
71 | }
72 | }
73 |
74 | function formatHistoryData(data) {
75 | const formattedData = {
76 | results: [],
77 | currency: data.currency,
78 | market: data.market,
79 | timestampApis: moment().format('YYYY-MM-DDTHH:mm:ss'),
80 | }
81 | data.data.forEach((result) => {
82 | formattedData.results.push({
83 | date: result.date,
84 | rate: result.price,
85 | })
86 | })
87 | return formattedData
88 | }
89 |
90 | function formatTransactionsData(data) {
91 | const formattedData = []
92 | data.forEach((result) => {
93 | let transactionType = result.maker_type
94 | if (result.maker_type === 'kaup') {
95 | transactionType = 'buy'
96 | } else if (result.maker_type === 'sala') {
97 | transactionType = 'sell'
98 | }
99 | formattedData.push({
100 | id: result.id,
101 | amount_isk: result.amount,
102 | amount_aur: result.btc,
103 | rate: result.price,
104 | type: transactionType,
105 | timestamp: moment(result.timestamp * 1000).format('YYYY-MM-DDTHH:mm:ss'),
106 | })
107 | })
108 | return formattedData
109 | }
110 |
111 | function formatOrderBookData(data) {
112 | const formattedData = []
113 | data.forEach((result) => {
114 | formattedData.push({
115 | rate: result.price,
116 | order_amount_aur: result.order_amount,
117 | order_value_isk: result.order_value,
118 | timestamp: moment(result.timestamp * 1000).format('YYYY-MM-DDTHH:mm'),
119 | })
120 | })
121 | return formattedData
122 | }
123 |
124 | function standardErrorResponse(res) {
125 | return res.status(500).json({
126 | error: 'isx.is refuses to respond or give back data',
127 | })
128 | }
129 |
130 | app.get('/aur', (req, res) => {
131 | queryStats((error, response, data) => {
132 | if (error || response.statusCode !== 200) {
133 | return standardErrorResponse(res)
134 | }
135 | return res.cache(cacheTime).json(formatStatsData(data))
136 | })
137 | })
138 |
139 | app.get('/aur/history', (req, res) => {
140 | queryHistory((error, response, data) => {
141 | if (error || response.statusCode !== 200) {
142 | return standardErrorResponse(res)
143 | }
144 | return res.cache(cacheTime).json(formatHistoryData(data['historical-prices']))
145 | })
146 | })
147 |
148 | app.get('/aur/transactions', (req, res) => {
149 | queryTransactions((error, response, data) => {
150 | if (error || response.statusCode !== 200) {
151 | return standardErrorResponse(res)
152 | }
153 | return res.cache(cacheTime).json({
154 | results: formatTransactionsData(data.transactions.data),
155 | timestampApis: moment().format('YYYY-MM-DDTHH:mm:ss'),
156 | })
157 | })
158 | })
159 |
160 | app.get('/aur/order-book', (req, res) => {
161 | queryOrderBook((error, response, data) => {
162 | if (error || response.statusCode !== 200) {
163 | return standardErrorResponse(res)
164 | }
165 | return res.cache(cacheTime).json({
166 | ask: formatOrderBookData(data['order-book'].ask),
167 | bid: formatOrderBookData(data['order-book'].bid),
168 | timestampApis: moment().format('YYYY-MM-DDTHH:mm:ss'),
169 | })
170 | })
171 | })
172 |
--------------------------------------------------------------------------------
/endpoints/aur/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-bitwise */
2 | /* eslint-disable import/first */
3 | /* eslint-disable import/extensions */
4 | const assert = require('assert')
5 | const request = require('request')
6 | const moment = require('moment')
7 | const helpers = require('../../../lib/test_helpers.js')
8 |
9 | const transactionTypes = ['buy', 'sell']
10 |
11 | describe('stats', function () {
12 | this.timeout(20000)
13 | const statsFieldsType = {
14 | ask: Number,
15 | bid: Number,
16 | daily_change: Number,
17 | daily_change_percent: Number,
18 | global_units: Number,
19 | global_volume: Number,
20 | last_price: Number,
21 | last_transaction_type: String,
22 | market_cap: Number,
23 | max: Number,
24 | min: Number,
25 | open: Number,
26 | volume_1h: Number,
27 | volume_1h_buy: Number,
28 | volume_1h_sell: Number,
29 | volume_24h: Number,
30 | volume_24h_buy: Number,
31 | volume_24h_sell: Number,
32 | timestampApis: Date,
33 | }
34 | const statsFields = Object.keys(statsFieldsType)
35 | const timestampFormat = 'YYYY-MM-DDTHH:mm:ss'
36 | it('should return object containing specific fields with correct types', (done) => {
37 | const params = helpers.testRequestParams('/aur')
38 | request.get(params, (err, res, body) => {
39 | if (err) throw err
40 | let json
41 | try {
42 | json = JSON.parse(body)
43 | } catch (e) {
44 | throw e
45 | }
46 | helpers.assertPresenceOfFields(statsFields, [json])
47 | helpers.assertTypesOfFields(statsFieldsType, [json])
48 | assert(
49 | ~transactionTypes.indexOf(json.last_transaction_type),
50 | `Unexpected transaction type '${json.last_transaction_type}'`
51 | )
52 | assert(
53 | moment(json.timestampApis, timestampFormat, true).isValid(),
54 | `Unexpected timestamp format, ${json.timestampApis}' does not match ${timestampFormat}`
55 | )
56 | done()
57 | })
58 | })
59 | })
60 |
61 | describe('history', function () {
62 | this.timeout(20000)
63 | const historyFieldsType = {
64 | date: Date,
65 | rate: Number,
66 | }
67 | const historyFields = Object.keys(historyFieldsType)
68 | const timestampFormat = 'YYYY-MM-DDTHH:mm:ss'
69 | const dateFormat = 'YYYY-MM-DD'
70 | it('should return results array of objects containing correct fields', (done) => {
71 | const params = helpers.testRequestParams('/aur/history')
72 | request.get(params, (err, res, body) => {
73 | if (err) throw err
74 | let json
75 | try {
76 | json = JSON.parse(body)
77 | } catch (e) {
78 | throw e
79 | }
80 | assert(json.results, 'Does not contain a \'results\' field')
81 | assert(json.currency, 'Does not contain a \'currency\' field')
82 | assert(json.market, 'Does not contain a \'market\' field')
83 | helpers.assertPresenceOfFields(historyFields, json.results)
84 | helpers.assertTypesOfFields(historyFieldsType, json.results)
85 | assert(
86 | json.currency.constructor === String,
87 | 'Unexpected currency type, should be String'
88 | )
89 | assert(
90 | json.market.constructor === String,
91 | 'Unexpected market type, should be String'
92 | )
93 | assert(
94 | moment(json.timestampApis, timestampFormat, true).isValid(),
95 | `Unexpected timestamp format, '${json.timestampApis}' does not match ${timestampFormat}`
96 | )
97 | json.results.forEach((result, i) => {
98 | assert(
99 | moment(result.date, dateFormat, true).isValid(),
100 | (`Unexpected date format in result #${i};` +
101 | ` '${result.date}' does not match ${dateFormat}`)
102 | )
103 | })
104 | done()
105 | })
106 | })
107 | })
108 |
109 | describe('transactions', function () {
110 | this.timeout(20000)
111 | const transactionsFieldsType = {
112 | id: Number,
113 | amount_isk: Number,
114 | amount_aur: Number,
115 | rate: Number,
116 | type: String,
117 | timestamp: Date,
118 | }
119 | const transactionsFields = Object.keys(transactionsFieldsType)
120 | const timestampFormat = 'YYYY-MM-DDTHH:mm:ss'
121 | it('should return results array of objects containing correct fields', (done) => {
122 | const params = helpers.testRequestParams('/aur/transactions')
123 | request.get(params, (err, res, body) => {
124 | if (err) throw err
125 | let json
126 | try {
127 | json = JSON.parse(body)
128 | } catch (e) {
129 | throw e
130 | }
131 | assert(json.results, 'Does not contain a \'results\' field')
132 | helpers.assertPresenceOfFields(transactionsFields, json.results)
133 | helpers.assertTypesOfFields(transactionsFieldsType, json.results)
134 | json.results.forEach((result, i) => {
135 | assert(
136 | ~transactionTypes.indexOf(result.type),
137 | `Unexpected transaction type '${result.type}' in result #${i}`
138 | )
139 | assert(
140 | moment(result.timestamp, timestampFormat, true).isValid(),
141 | (`Unexpected timestamp format in result #${i};` +
142 | ` '${result.timestamp}' does not match ${timestampFormat}`)
143 | )
144 | })
145 | done()
146 | })
147 | })
148 | })
149 |
150 | describe('order-book', function () {
151 | this.timeout(20000)
152 | const bidAskFieldsType = {
153 | rate: Number,
154 | order_amount_aur: Number,
155 | order_value_isk: Number,
156 | timestamp: Date,
157 | }
158 | const bidAskFields = Object.keys(bidAskFieldsType)
159 | const timestampFormat = 'YYYY-MM-DDTHH:mm'
160 | it('should return bid/ask arrays of objects containing correct fields', (done) => {
161 | const params = helpers.testRequestParams('/aur/order-book')
162 | request.get(params, (err, res, body) => {
163 | if (err) throw err
164 | let json
165 | try {
166 | json = JSON.parse(body)
167 | } catch (e) {
168 | throw e
169 | }
170 | assert(json.ask, 'Does not contain a \'ask\' field')
171 | helpers.assertPresenceOfFields(bidAskFields, json.ask)
172 | helpers.assertTypesOfFields(bidAskFieldsType, json.ask)
173 | json.ask.forEach((result, i) => {
174 | assert(
175 | moment(result.timestamp, timestampFormat, true).isValid(),
176 | (`Unexpected timestamp format in ask result #${i};` +
177 | ` '${result.timestamp}' does not match ${timestampFormat}`)
178 | )
179 | })
180 | assert(json.bid, 'Does not contain a \'bid\' field')
181 | helpers.assertPresenceOfFields(bidAskFields, json.bid)
182 | helpers.assertTypesOfFields(bidAskFieldsType, json.bid)
183 | json.bid.forEach((result, i) => {
184 | assert(
185 | moment(result.timestamp, timestampFormat, true).isValid(),
186 | (`Unexpected timestamp format in bid result #${i};` +
187 | ` "${result.timestamp}" does not match ${timestampFormat}`)
188 | )
189 | })
190 | done()
191 | })
192 | })
193 | })
194 |
--------------------------------------------------------------------------------
/endpoints/bus/documentation.md:
--------------------------------------------------------------------------------
1 | # Icelandic Bus System
2 |
3 | Source [bus.is](https://bus.is)
4 |
5 | - GET [/bus/realtime](https://apis.is/bus/realtime)
6 |
7 | Real-time location of busses. Results are only shown for active busses.
8 |
9 | | Parameters | |
10 | |-------------|---|
11 | | BUSSES | Comma seperated list of numbers. Good to know: If not declared, the endpoint will return location of all available busses. |
12 |
13 | ---
14 |
--------------------------------------------------------------------------------
/endpoints/bus/graphql_schema.js:
--------------------------------------------------------------------------------
1 | const {
2 | GraphQLObjectType,
3 | GraphQLList,
4 | GraphQLString,
5 | } = require('graphql')
6 |
7 | const getBusRoutes = require('./realtime')
8 |
9 | const busInfoType = new GraphQLObjectType({
10 | name: 'BusInfo',
11 | fields: {
12 | unixTime: { type: GraphQLString },
13 | x: { type: GraphQLString },
14 | y: { type: GraphQLString },
15 | from: { type: GraphQLString },
16 | to: { type: GraphQLString },
17 | },
18 | })
19 |
20 | const busRouteType = new GraphQLObjectType({
21 | name: 'BusRoutes',
22 | fields: {
23 | busNr: { type: GraphQLString },
24 | busses: { type: new GraphQLList(busInfoType) },
25 | },
26 | })
27 |
28 | const busRoutesType = new GraphQLList(busRouteType)
29 |
30 | module.exports = {
31 | type: busRoutesType,
32 | args: {
33 | busses: { type: GraphQLString },
34 | },
35 | resolve: async (_, args) => {
36 | const data = await getBusRoutes(args)
37 | return data.results
38 | },
39 | }
40 |
--------------------------------------------------------------------------------
/endpoints/bus/index.js:
--------------------------------------------------------------------------------
1 | // const request = require('request')
2 | // const h = require('apis-helpers')
3 | const app = require('../../server')
4 |
5 | app.post('/bus/search', (req, res) => {
6 | return res.status(404).json({
7 | error: 'This api endpoint has been closed, because Bus.is changed it\'s markup.',
8 | })
9 | })
10 |
--------------------------------------------------------------------------------
/endpoints/bus/realtime.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | const h = require('apis-helpers')
4 | const request = require('request')
5 | const app = require('../../server')
6 | const isn2wgs = require('isn2wgs')
7 |
8 | const debug = require('debug')('bus/realtime')
9 |
10 | const getBusRoutes = (data) => new Promise((resolve, reject) => {
11 | request.get('http://straeto.is/bitar/bus/livemap/json.jsp', function (error, response, body) {
12 | if (error || response.statusCode !== 200) {
13 | return reject('The bus api is down or refuses to respond')
14 | }
15 |
16 | var obj
17 | try {
18 | obj = JSON.parse(body)
19 | } catch (error) {
20 | return reject(error)
21 | }
22 |
23 | var activeBusses = [],
24 | requestedBusses = []
25 |
26 | const routes = obj.routes || []
27 |
28 | routes.forEach(function (object, key) {
29 | activeBusses.push(object.id)
30 | })
31 |
32 | if (data.busses) { // Post busses = 1,2,3,4,5
33 | requestedBusses = data.busses.split(',')
34 |
35 | for (var i in requestedBusses) { // Prevent requested to busses that are not available
36 | if (activeBusses.indexOf(requestedBusses[i]) == -1) {
37 | requestedBusses.splice(requestedBusses.indexOf(requestedBusses[i]), 1)
38 | }
39 | }
40 | } else {
41 | // No bus was posted, use all active busses
42 | requestedBusses = activeBusses
43 | }
44 |
45 | var objString = requestedBusses.join('%2C')
46 |
47 | request('http://straeto.is/bitar/bus/livemap/json.jsp?routes=' + objString, function (error, response, body) {
48 |
49 | if (error || response.statusCode !== 200) {
50 | return reject(error)
51 | }
52 |
53 | try {
54 | var data = JSON.parse(body)
55 | } catch (e) {
56 | return reject(e)
57 | }
58 |
59 | var routes = data.routes || []
60 |
61 | var objRoutes = {
62 | results: []
63 | }
64 | routes.forEach(function (route, key) {
65 |
66 | var objRoute = {
67 | busNr: route.id || '', // will be undefined if none are active
68 | busses: []
69 | }
70 | objRoutes.results.push(objRoute)
71 |
72 | if (!route.busses) return // No busses active, eg. after schedule
73 |
74 | route.busses.forEach(function (bus, key) {
75 |
76 | var location = isn2wgs(bus.X, bus.Y),
77 | oneRoute = {
78 | 'unixTime': Date.parse(bus.TIMESTAMPREAL) / 1000,
79 | 'x': location.latitude,
80 | 'y': location.longitude,
81 | 'from': bus.FROMSTOP,
82 | 'to': bus.TOSTOP
83 | }
84 | objRoute.busses.push(oneRoute)
85 |
86 | })
87 |
88 | })
89 | return resolve(objRoutes)
90 | })
91 | })
92 | })
93 |
94 | app.get('/bus/realtime', function (req, res) {
95 | var data = req.query
96 |
97 | getBusRoutes(data).then(
98 | (routes) => res.cache(1).json(routes),
99 | () => res.status(500).json({ error:'Something is wrong with the data provided from the data source' })
100 | )
101 | })
102 |
103 | module.exports = getBusRoutes
104 |
--------------------------------------------------------------------------------
/endpoints/bus/search.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | var app = require('../../server')
4 |
5 | app.post('/bus/search', function (req, res) {
6 | res.status(404).json({ error: 'This api endpoint has been closed temporarily, because Bus.is changed it\'s markup.' })
7 | }) // Old
8 | app.get('/bus/search', function (req, res) {
9 | res.status(404).json({ error: 'This api endpoint has been closed temporarily, because Bus.is changed it\'s markup.' })
10 | })
11 |
--------------------------------------------------------------------------------
/endpoints/bus/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 |
3 | var request = require('request');
4 | var assert = require('assert');
5 | var helpers = require('../../../lib/test_helpers.js');
6 | var sinon = require('sinon');
7 | var getBusRoutes = require('../realtime.js');
8 |
9 | describe('bus', function() {
10 | var fieldsToCheckFor = ['busNr', 'busses'];
11 |
12 | var customCheck = function(json) {
13 | var busses = json.results[0].busses;
14 | if (busses.length > 0) return;
15 | helpers.assertPresenceOfFields(
16 | ['unixTime', 'x', 'y', 'from', 'to'],
17 | busses
18 | );
19 | };
20 |
21 | describe('realtime', function() {
22 | // describe('searching a single bus', function() {
23 | // it('should return an array of objects containing correct fields', function(
24 | // done
25 | // ) {
26 | // var params = helpers.testRequestParams('/bus/realtime?busses=1');
27 | // var resultHandler = helpers.testRequestHandlerForFields(
28 | // done,
29 | // fieldsToCheckFor,
30 | // customCheck
31 | // );
32 | // request.get(params, resultHandler);
33 | // });
34 | // });
35 |
36 | // describe('searching multiple busses', function () {
37 | // it('should return an array of objects containing correct fields', function (done) {
38 | // var params = helpers.testRequestParams('/bus/realtime?busses=1,5')
39 | // var resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor, customCheck)
40 | // request.get(params, resultHandler)
41 | // })
42 | // })
43 |
44 | // describe('searching for a non existant bus', function () {
45 | // it('should return an array of objects containing correct fields', function (done) {
46 | // var params = helpers.testRequestParams('/bus/realtime?busses=999')
47 | // var resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
48 | // request.get(params, resultHandler)
49 | // })
50 | // })
51 |
52 | describe('when the data source returns an error', () => {
53 | before(function() {
54 | sinon
55 | .stub(request, 'get')
56 | .yields(
57 | "We don't want no scrapers around these here parts",
58 | null,
59 | null
60 | );
61 | });
62 |
63 | after(function() {
64 | request.get.restore();
65 | });
66 |
67 | it('should return a appropriate message', done => {
68 | getBusRoutes({})
69 | .then(data => {
70 | should.fail(
71 | 'Got back data even though the data source returned an error'
72 | );
73 | })
74 | .catch(error => done());
75 | });
76 | });
77 | });
78 | });
79 |
--------------------------------------------------------------------------------
/endpoints/calendar/graphql_schema.js:
--------------------------------------------------------------------------------
1 | const {
2 | GraphQLObjectType,
3 | GraphQLList,
4 | GraphQLString,
5 | GraphQLBoolean,
6 | } = require('graphql')
7 | const { GraphQLDate } = require('../../graphql/types/GraphQLDate')
8 |
9 | const lookupHolidays = require('.')
10 |
11 | const holiday = new GraphQLObjectType({
12 | name: 'Holiday',
13 | description: 'An Icelandic holiday',
14 | fields: {
15 | date: {
16 | type: GraphQLDate,
17 | description: 'The datetime in ISO 8601 date format',
18 | },
19 | description: {
20 | type: GraphQLString,
21 | description: 'The name of the holiday in Icelandic',
22 | },
23 | holiday: {
24 | type: GraphQLBoolean,
25 | description: 'A boolean value indicating wether this is a holiday or not',
26 | },
27 | },
28 | })
29 |
30 | const holidays = new GraphQLList(holiday)
31 |
32 | module.exports = {
33 | type: holidays,
34 | args: {
35 | year: { type: GraphQLString },
36 | month: { type: GraphQLString },
37 | day: { type: GraphQLString },
38 | },
39 | resolve: async (_, { year, month, day }) => {
40 | try {
41 | return await lookupHolidays(year, month, day)
42 | } catch (error) {
43 | throw new Error(error)
44 | }
45 | },
46 | }
47 |
--------------------------------------------------------------------------------
/endpoints/calendar/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-promise-reject-errors */
2 | /* eslint-disable import/first */
3 | const fridagar = require('fridagar')
4 | const debug = require('debug')('endpoint:calendar')
5 | const { range, isString } = require('lodash')
6 | const app = require('../../server')
7 |
8 | const canBeInt = (intLike) => {
9 | const num = Number.parseInt(intLike, 10)
10 | return !Number.isNaN(num)
11 | }
12 |
13 | const normalizeParams = (year, month, day) => {
14 | // If string parsing failed, reject the promise
15 | if (isString(year) && !canBeInt(year)) return { error: 'Year must be a number' }
16 | if (isString(month) && !canBeInt(month)) return { error: 'Month must be a number' }
17 | if (isString(day) && !canBeInt(day)) return { error: 'Day must be a number' }
18 |
19 | return { year, month, day }
20 | }
21 |
22 | const lookupHolidays = (yearStr, monthStr, dayStr) => new Promise((resolve, reject) => {
23 | const {
24 | year, month, day, error,
25 | } = normalizeParams(yearStr, monthStr, dayStr)
26 |
27 | // Reject promise with relevant error when in error states
28 | if (error) reject({ error })
29 | if (!year) reject({ error: 'No year was provided' })
30 | if (!year && !month && !day) reject({ error: 'No parameters were provided' })
31 |
32 | if (year && !month && !day) {
33 | // Year
34 | const holidays = range(1, 13).reduce((sum, current) => {
35 | debug(`Getting year: ${year}, month: ${current}`)
36 | return sum.concat(fridagar.getHolidays(year, current))
37 | }, [])
38 | resolve(holidays)
39 | } else if (year && month && !day) {
40 | // Year, Month
41 | resolve(fridagar.getHolidays(year, month))
42 | } else if (year && month && day) {
43 | // Year, Month, Day
44 | const holiday = fridagar.getHolidays(year, month).find((current) => {
45 | return current.date.toISOString().startsWith(`${year}-${month}-${day}`)
46 | })
47 | const results = holiday || {
48 | date: new Date(`${year}-${month}-${day}T00:00:00.000Z`),
49 | description: null,
50 | holiday: false,
51 | }
52 | // Wrap the single holiday in an array to keep responses consistent
53 | resolve([results])
54 | }
55 | })
56 |
57 | const lookupAndRespondHolidays = async (res, ...args) => {
58 | try {
59 | const results = await lookupHolidays(...args)
60 | res.json({ results })
61 | } catch (error) {
62 | res.status(400).json(error)
63 | }
64 | }
65 |
66 | app.get('/calendar/', (req, res) => {
67 | return res.json({
68 | description: 'Returns if a given date range has or is a holiday',
69 | provider: 'https://www.npmjs.com/package/fridagar',
70 | available_endpoints: [
71 | '/calendar/:year',
72 | '/calendar/:year/:month',
73 | '/calendar/:year/:month/:day',
74 | ],
75 | })
76 | })
77 |
78 | app.get('/calendar/:year', (req, res) => {
79 | const { year } = req.params
80 | lookupAndRespondHolidays(res, year)
81 | })
82 |
83 | app.get('/calendar/:year/:month', (req, res) => {
84 | const { year, month } = req.params
85 | lookupAndRespondHolidays(res, year, month)
86 | })
87 |
88 | app.get('/calendar/:year/:month/:day', (req, res) => {
89 | const { year, month, day } = req.params
90 | lookupAndRespondHolidays(res, year, month, day)
91 | })
92 |
93 | module.exports = lookupHolidays
94 | module.exports.normalizeParams = normalizeParams
95 |
--------------------------------------------------------------------------------
/endpoints/calendar/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const expect = require('expect')
3 | const helpers = require('../../../lib/test_helpers')
4 | const { normalizeParams } = require('..')
5 |
6 | describe('calendar/:year', () => {
7 | it('should return an array of objects containing correct fields', (done) => {
8 | const fieldsToCheckFor = ['date', 'description', 'holiday']
9 | const params = helpers.testRequestParams('/calendar/2016')
10 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
11 | request.get(params, resultHandler)
12 | })
13 | })
14 |
15 | describe('calendar/:year/month', () => {
16 | it('should return an array of objects containing correct fields', (done) => {
17 | const fieldsToCheckFor = ['date', 'description', 'holiday']
18 | const params = helpers.testRequestParams('/calendar/2016/06')
19 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
20 | request.get(params, resultHandler)
21 | })
22 | })
23 |
24 | describe('calendar/:year/:month/:day', () => {
25 | it('should return an array of objects containing correct fields', (done) => {
26 | const fieldsToCheckFor = ['date', 'description', 'holiday']
27 | const params = helpers.testRequestParams('/calendar/2016/06/17')
28 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
29 | request.get(params, resultHandler)
30 | })
31 | })
32 |
33 | describe('validateParams', () => {
34 | it('should return an error if a parameter could not be parsed as an Int', () => {
35 | expect(normalizeParams('bogusYear', 1, 1))
36 | .toEqual({ error: 'Year must be a number' })
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/endpoints/car/documentation.md:
--------------------------------------------------------------------------------
1 | # Cars in Iceland
2 |
3 | Source: [The Road Traffic Directorate](http://www.samgongustofa.is/umferd/okutaeki/okutaekjaskra/uppfletting)
4 |
5 | - GET [/car](https://apis.is/car)
6 |
7 | Search the Icelandic vehicle registry.
8 |
9 | | Parameters | Description | Example |
10 | |--------------------|-----------------|-------------------------------------------|
11 | | Number (required) | Registry number | [AA031](https://apis.is/car?number=AA031) |
12 |
13 | ---
14 |
--------------------------------------------------------------------------------
/endpoints/car/graphql_schema.js:
--------------------------------------------------------------------------------
1 | const {
2 | GraphQLObjectType,
3 | GraphQLString,
4 | } = require('graphql')
5 |
6 | const lookupCar = require('.')
7 |
8 | const car = new GraphQLObjectType({
9 | name: 'Car',
10 | description: 'Holds information about a registered Icelandic car',
11 | fields: {
12 | type: {
13 | type: GraphQLString,
14 | description: 'The car brand, model and color ',
15 | },
16 | subType: {
17 | type: GraphQLString,
18 | description: 'The car model',
19 | },
20 | color: {
21 | type: GraphQLString,
22 | description: 'The cars color',
23 | },
24 | registryNumber: {
25 | type: GraphQLString,
26 | description: 'The number on the cars plate',
27 | },
28 | number: {
29 | type: GraphQLString,
30 | description: 'The original number on the car is registered under',
31 | },
32 | factoryNumber: {
33 | type: GraphQLString,
34 | description: 'The factory number of the car',
35 | },
36 | registeredAt: {
37 | type: GraphQLString,
38 | description: 'The date when the car was registered on the format DD.MM.YYYY',
39 | },
40 | pollution: {
41 | type: GraphQLString,
42 | description: 'The polution rate of the car in g/km',
43 | },
44 | weight: {
45 | type: GraphQLString,
46 | description: 'The weight of the car in kg',
47 | },
48 | status: {
49 | type: GraphQLString,
50 | description: 'The status of the car. Example: `Í lagi`',
51 | },
52 | nextCheck: {
53 | type: GraphQLString,
54 | description: 'The date of this cars next routine check on the format DD.MM.YYYY',
55 | },
56 | },
57 | })
58 |
59 | module.exports = {
60 | type: car,
61 | args: {
62 | carPlate: {
63 | type: GraphQLString,
64 | description: 'The numer on the cars plate',
65 | },
66 | },
67 | resolve: async (_, { carPlate }) => {
68 | try {
69 | return await lookupCar(carPlate)
70 | } catch (error) {
71 | throw new Error(error)
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/endpoints/car/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-promise-reject-errors */
2 | const request = require('request')
3 | const $ = require('cheerio')
4 | const h = require('apis-helpers')
5 | const app = require('../../server')
6 |
7 | const lookupCar = plate => new Promise((resolve, reject) => {
8 | // Encode carPlate so that Icelandic characters will work
9 | const carPlate = encodeURIComponent(plate)
10 | const url = `http://www.samgongustofa.is/umferd/okutaeki/okutaekjaskra/uppfletting?vq=${carPlate}`
11 |
12 | request.get({
13 | headers: { 'User-Agent': h.browser() },
14 | url,
15 | }, (error, response, body) => {
16 | if (error || response.statusCode !== 200) {
17 | reject('www.samgongustofa.is refuses to respond or give back data')
18 | }
19 |
20 | const data = $(body)
21 | const fields = []
22 |
23 | data.find('.vehicleinfo ul li').each(function () {
24 | const val = $(this).find('span').text()
25 | fields.push(val)
26 | })
27 |
28 | if (fields.length > 0) {
29 | resolve({
30 | type: fields[0],
31 | subType: fields[0].substring(fields[0].indexOf('-') + 2, fields[0].indexOf('(') - 1),
32 | color: fields[0].substring(fields[0].indexOf('(') + 1, fields[0].indexOf(')')),
33 | registryNumber: fields[1],
34 | number: fields[2],
35 | factoryNumber: fields[3],
36 | registeredAt: fields[4],
37 | pollution: fields[5],
38 | weight: fields[6],
39 | status: fields[7],
40 | nextCheck: fields[8],
41 | })
42 | } else {
43 | reject(`No car found with the registry number ${plate}`)
44 | }
45 | })
46 | })
47 |
48 | app.get('/car', async (req, res) => {
49 | const carPlate = req.query.number || req.query.carPlate || ''
50 |
51 | if (!carPlate) {
52 | return res.status(431).json({ error: 'Please provide a valid carPlate to lookup' })
53 | }
54 |
55 | try {
56 | const car = await lookupCar(carPlate)
57 | res.cache().json({ results: [car] })
58 | } catch (error) {
59 | res.status(500).json({ error })
60 | }
61 | })
62 |
63 | module.exports = lookupCar
64 |
--------------------------------------------------------------------------------
/endpoints/car/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/extensions */
2 | const request = require('request')
3 | const helpers = require('../../../lib/test_helpers.js')
4 |
5 | describe('car', () => {
6 | it('should return an array of objects containing correct fields', (done) => {
7 | const fieldsToCheckFor = [
8 | 'registryNumber',
9 | 'number',
10 | 'factoryNumber',
11 | 'type',
12 | 'subType',
13 | 'color',
14 | 'registeredAt',
15 | 'status',
16 | 'nextCheck',
17 | 'pollution',
18 | 'weight',
19 | ]
20 | const params = helpers.testRequestParams('/car', { carPlate: 'AA031' })
21 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
22 | request.get(params, resultHandler)
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/endpoints/carparks/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-globals */
2 | /* eslint-disable no-plusplus */
3 | const request = require('request')
4 | const $ = require('cheerio')
5 | const h = require('apis-helpers')
6 | const app = require('../../server')
7 |
8 | app.get('/carparks', (req, res) => {
9 | const url = 'http://www.bilastaedasjodur.is/'
10 |
11 | request.get({
12 | headers: { 'User-Agent': h.browser() },
13 | url,
14 | }, (error, response, body) => {
15 | if (error || response.statusCode !== 200) {
16 | return res.status(500).json({
17 | error: 'www.bilastaedasjodur.is refuses to respond or give back data',
18 | })
19 | }
20 |
21 | try {
22 | // FIXME: What is the point of this? It doesn't seem to be used anywhere
23 | // eslint-disable-next-line no-unused-vars
24 | const data = $(body)
25 | } catch (exc) {
26 | return res.status(500).json({ error: 'Could not parse body' })
27 | }
28 |
29 | const obj = { results: [] }
30 | const hus = $('.hus', $(body))
31 |
32 | const js = body.match(/LatLng\((.*?)\)/g)
33 |
34 | function parseCoordinates(str) {
35 | try {
36 | const Regexp = /(?:^|\s)LatLng.(.*?)\)(?:\s|$)/g
37 | const match = Regexp.exec(str)
38 | return match[1].split(', ')
39 | } catch (exc) {
40 | return null
41 | }
42 | }
43 |
44 | for (let i = 0; i < hus.length; i++) {
45 | const that = hus[i]
46 | const freeSpaces = parseInt($(that).find('.ib.free h1').text(), 10)
47 | const totalSpaces = parseInt($(that).find('.ib.total h1').text(), 10)
48 |
49 | obj.results.push({
50 | name: $(that).find('aside h2').text(),
51 | address: $(that).find('h5').text(),
52 | parkingSpaces: {
53 | free: !isNaN(freeSpaces) ? freeSpaces : null,
54 | total: !isNaN(totalSpaces) ? totalSpaces : null,
55 | },
56 | coordinates: parseCoordinates(js[i]),
57 | })
58 | }
59 |
60 | return res.cache(180).json(obj)
61 | })
62 | })
63 |
--------------------------------------------------------------------------------
/endpoints/carparks/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/extensions */
2 | const request = require('request')
3 | const helpers = require('../../../lib/test_helpers.js')
4 |
5 | describe('carparks', () => {
6 | it('should return an array of objects containing correct fields', (done) => {
7 | const fieldsToCheckFor = ['name', 'address', 'parkingSpaces', 'coordinates']
8 | const params = helpers.testRequestParams('/carparks')
9 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
10 | request.get(params, resultHandler)
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/endpoints/cinema/index.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const cheerio = require('cheerio')
3 | const app = require('../../server')
4 |
5 | /**
6 | * Fetches movies for show today in Icelandic cinemas.
7 | * response - JSON: Movie data within an 'results' array.
8 | */
9 | app.get('/cinema', (req, res) => {
10 | const url = 'http://kvikmyndir.is/bio/syningatimar/'
11 |
12 | request(url, (error, response, body) => {
13 | if (error) {
14 | return res.status(500).json({ error: `${url} not responding correctly...` })
15 | }
16 |
17 | let $
18 |
19 | try {
20 | $ = cheerio.load(body)
21 | } catch (e) {
22 | return res.status(500).json({ error: 'Could not load the body with cherrio.' })
23 | }
24 |
25 | // Base object to be added to
26 | // and eventually sent as a JSON response.
27 | const obj = {
28 | results: [],
29 | }
30 |
31 | // DOM elements array containing all movies.
32 | const movies = $('.stimar')
33 |
34 | // Loop through movies
35 | movies.each(function () {
36 | // This movie.
37 | const movie = $(this)
38 |
39 | // Showtimes for JSON
40 | const showtimes = []
41 |
42 | // Find all theaters and loop through them.
43 | const theaters = movie.find('[id^="myndbio"]')
44 |
45 | theaters.each(function () {
46 | // Single theater
47 | const theater = {
48 | theater: $(this).find('#bio a').text().trim(),
49 | schedule: [],
50 | }
51 |
52 | // Loop through each showtime and
53 | // add them to the theater schedule.
54 | $(this).find('.syningartimi_item').each(function () {
55 | theater.schedule.push($(this).text().trim())
56 | })
57 |
58 | // Add theater to showtimes array.
59 | showtimes.push(theater)
60 | })
61 |
62 | const src = movie.find('img').attr('src')
63 | if (src) {
64 | const urls = src.match(/\/images\/poster\/.+\.(jpg|jpeg|png)/ig) || []
65 | const imgUrl = `http://kvikmyndir.is${urls[0]}`
66 | const realeasedYear = movie
67 | .find('.mynd_titill_artal')
68 | .text()
69 | .replace('/[()]/g', '')
70 |
71 | // Create an object of info
72 | // and add it to the 'results' array.
73 | obj.results.push({
74 | title: movie.find('.title').remove('.year').html().trim(),
75 | released: realeasedYear,
76 | restricted: null,
77 | imdb: movie.find('.imdbEinkunn').text().trim(),
78 | imdbLink: movie.find('.imdbEinkunn a').attr('href') ? movie.find('.imdbEinkunn a').attr('href').trim() : '',
79 | image: imgUrl,
80 | showtimes,
81 | })
82 | }
83 | })
84 |
85 | return res.cache().json(obj)
86 | })
87 | })
88 |
89 | /**
90 | * Fetches theaters that are showing movies today.
91 | * response - JSON: Theater data within an 'results' array.
92 | */
93 | app.get('/cinema/theaters', (req, res) => {
94 | const url = 'http://kvikmyndir.is/bio/syningatimar_bio/'
95 |
96 | request(url, (error, response, body) => {
97 | if (error) return res.status(500).json({ error: `${url} not responding correctly...` })
98 |
99 | // Cheerio declared and then attemted to load.
100 | let $
101 |
102 | try {
103 | $ = cheerio.load(body)
104 | } catch (e) {
105 | return res.status(500).json({ error: 'Could not load the body with cherrio.' })
106 | }
107 |
108 | // Base object to be added to
109 | // and eventually sent as a JSON response.
110 | const obj = {
111 | results: [],
112 | }
113 |
114 | // DOM elements array containing all theaters.
115 | const theaters = $('.stimar')
116 |
117 | // Loop through theaters
118 | theaters.each(function () {
119 | // This theater.
120 | const theater = $(this)
121 |
122 | // List of movies.
123 | const movies = []
124 |
125 | // Loop through movies.
126 | theater.find('#myndbio_new').each(function () {
127 | // This movie.
128 | const movie = $(this)
129 |
130 | // Time schedule.
131 | const schedule = []
132 |
133 | // Loop through each showtime on schedule today.
134 | movie.find('#timi_new div').each(function () {
135 | // Add time to the schedule.
136 | schedule.push($(this).find('.syningartimi_item').text().trim())
137 | })
138 |
139 | // Append new movie to the list of movies.
140 | movies.push({
141 | title: movie.find('#bio a').text().trim(),
142 | schedule,
143 | })
144 | })
145 |
146 | // Create an object of info
147 | // and add it to the 'results' array.
148 | obj.results.push({
149 | name: theater.find('#mynd_titill a').text().trim(),
150 | location: theater.find('.mynd_titill_artal').text().trim().replace(/(^\()|(\)$)/g, ''),
151 | image: `http://kvikmyndir.is${theater.find('.mynd_plakat img').attr('src')}`,
152 | movies,
153 | })
154 | })
155 |
156 | return res.cache().json(obj)
157 | })
158 | })
159 |
--------------------------------------------------------------------------------
/endpoints/cinema/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const helpers = require('../../../lib/test_helpers')
3 |
4 | describe('/cinema', () => {
5 | it('should return an array of objects containing correct fields', (done) => {
6 | const fieldsToCheckFor = ['title', 'released', 'restricted', 'imdb', 'imdbLink', 'image', 'showtimes']
7 | const params = helpers.testRequestParams('/cinema')
8 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
9 | request.get(params, resultHandler)
10 | })
11 | })
12 | describe('cinema theaters', () => { })
13 |
--------------------------------------------------------------------------------
/endpoints/company/documentation.md:
--------------------------------------------------------------------------------
1 | # Icelandic companies
2 |
3 | Source: [Directorate of Internal Revenue in Iceland](http://rsk.is/)
4 |
5 | - GET [/company](https://apis.is/company)
6 |
7 | Search the Icelandic company registry
8 | **NB: At least one parameter is required.**
9 |
10 | | Parameters | Description | Example |
11 | |---------------|-----------------------------------------------|-------------------------------------------------|
12 | | Name | Company name | [Blendin](https://apis.is/company?name=blendin) |
13 | | Address | Company's address | |
14 | | SocialNumber | Company's social security number / ID number | |
15 | | VSKNR | Company's VAT-number (VSK in icelandic) || |
16 |
17 | ---
18 |
--------------------------------------------------------------------------------
/endpoints/company/index.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const cheerio = require('cheerio')
3 | const h = require('apis-helpers')
4 | const app = require('../../server')
5 |
6 | app.get('/company', (req, res) => {
7 | const queryString = {
8 | nafn: req.query.name || '',
9 | heimili: req.query.address || '',
10 | kt: req.query.socialnumber || '',
11 | vsknr: req.query.vsknr || '',
12 | }
13 |
14 | request.get({
15 | headers: { 'User-Agent': h.browser() },
16 | url: 'http://www.rsk.is/fyrirtaekjaskra/leit',
17 | qs: queryString,
18 | }, (error, response, body) => {
19 | if (error || response.statusCode !== 200) {
20 | return res.status(500).json({ error: 'www.rsk.is refuses to respond or give back data' })
21 | }
22 |
23 | const obj = { results: [] }
24 | const $ = cheerio.load(body, { decodeEntities: false })
25 |
26 | if ($('.resultnote').length === 0) {
27 | const tr = $('.boxbody > .nozebra tbody tr').first()
28 | if (tr.length > 0) {
29 | const name = $('.boxbody > h1').html()
30 | const sn = $('.boxbody > h1').html()
31 |
32 | obj.results.push({
33 | name: name.substring(0, name.indexOf('(') - 1),
34 | sn: sn.substring(sn.length - 11, sn.length - 1),
35 | active: $('p.highlight').text().length === 0 ? 1 : 0,
36 | address: tr.find('td').eq(0).text(),
37 | })
38 | }
39 | } else {
40 | $('table tr').slice(1).each((i, element) => {
41 | const td = $(element).find('td')
42 | const nameRoot = td.eq(1).text()
43 | const felagAfskrad = '(Félag afskráð)'
44 |
45 | obj.results.push({
46 | name: nameRoot
47 | .replace('\n', '')
48 | .replace(felagAfskrad, '')
49 | .replace(/^\s\s*/, '')
50 | .replace(/\s\s*$/, ''),
51 | sn: td.eq(0).text(),
52 | active: nameRoot.indexOf(felagAfskrad) > -1 ? 0 : 1,
53 | address: td.eq(2).text(),
54 | })
55 | })
56 | }
57 |
58 | return res.cache(86400).json(obj)
59 | })
60 | })
61 |
--------------------------------------------------------------------------------
/endpoints/company/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/extensions */
2 | const request = require('request')
3 | const helpers = require('../../../lib/test_helpers.js')
4 |
5 | describe('company', () => {
6 | const fieldsToCheckFor = ['name', 'sn', 'active', 'address']
7 |
8 | describe('searching by name', () => {
9 | it('should return an array of objects containing correct fields', (done) => {
10 | const params = helpers.testRequestParams('/company', { name: 'hagar' })
11 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
12 | request.get(params, resultHandler)
13 | })
14 | })
15 | describe('searching by address', () => {
16 | it('should return an array of objects containing correct fields', (done) => {
17 | const params = helpers.testRequestParams('/company', { address: 'Hagasmára' })
18 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
19 | request.get(params, resultHandler)
20 | })
21 | })
22 | describe('search by socialnumber', () => {
23 | it('should return an array of objects containing correct fields', (done) => {
24 | const params = helpers.testRequestParams('/company', { socialnumber: '4307003590' })
25 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
26 | request.get(params, resultHandler)
27 | })
28 | })
29 | describe('search by vsknr', () => {
30 | it('should return an array of objects containing correct fields', (done) => {
31 | const params = helpers.testRequestParams('/company', { vsknr: '78874' })
32 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
33 | request.get(params, resultHandler)
34 | })
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/endpoints/concerts/documentation.md:
--------------------------------------------------------------------------------
1 | # Concerts in Iceland
2 |
3 | Source: [midi.is](https://midi.is/)
4 |
5 | - GET [/concerts](https://apis.is/concerts)
6 |
7 | Get a list of all the concerts in Iceland sorted by date
8 |
9 | ---
10 |
--------------------------------------------------------------------------------
/endpoints/concerts/index.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const _ = require('lodash')
3 | const app = require('../../server')
4 |
5 | app.get('/concerts', (req, res) => {
6 | const url = 'http://midi.is/Home/LoadMoreEventsByDate?eventType=Concerts&pageNumber='
7 | const page = req.query.page || 1
8 |
9 | request.get(url + page, (error, response, body) => {
10 | if (error || response.statusCode !== 200) {
11 | return res.status(500).json({
12 | error: 'Something came up when contacting the midi.is server!',
13 | })
14 | }
15 | const events = JSON.parse(body)
16 | const filtered = _.map(events, event => (
17 | _.pick(
18 | event,
19 | 'eventDateName',
20 | 'name',
21 | 'dateOfShow',
22 | 'userGroupName',
23 | 'eventHallName',
24 | 'imageSource'
25 | )
26 | ))
27 | return res.cache(60).json({ results: filtered })
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/endpoints/concerts/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/extensions */
2 | const request = require('request')
3 | const helpers = require('../../../lib/test_helpers.js')
4 |
5 | describe('concerts', () => {
6 | const fieldsToCheckFor = [
7 | 'eventDateName', 'name', 'dateOfShow', 'userGroupName', 'eventHallName',
8 | 'imageSource',
9 | ]
10 | it('should return an array of objects containing correct fields', (done) => {
11 | const params = helpers.testRequestParams('/concerts')
12 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
13 | request(params, resultHandler)
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/endpoints/currency/arion.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const moment = require('moment')
3 | const h = require('apis-helpers')
4 | const app = require('../../server')
5 |
6 | app.get('/currency/arion/:type?', (req, res) => {
7 | // types: AlmenntGengi,KortaGengi(valitor),SedlaGengi,AirportGengi
8 | const type = req.params.type || 'AlmenntGengi'
9 | let toSend = 'm=GetCurrencies'
10 |
11 | toSend += `&beginDate=${moment().format('YYYY-MM-DD')}`
12 | toSend += `&finalDate=${moment().format('YYYY-MM-DD')}`
13 | toSend += `¤cyType=${type}`
14 | toSend += '¤ciesAvailable=ISK%2CUSD%2CGBP%2CEUR%2CCAD%2CDKK%2CNOK%2CSEK%2CCHF%2CJPY%2CXDR'
15 | request.get({
16 | headers: { 'content-type': 'application/x-www-form-urlencoded' },
17 | url: 'https://www.arionbanki.is/Webservice/PortalCurrency.ashx',
18 | body: toSend,
19 | }, (error, response, body) => {
20 | if (error || response.statusCode !== 200) {
21 | return res.status(500).json({ error: 'www.arionbanki.is refuses to respond or give back data' })
22 | }
23 |
24 | const jsonObject = JSON.parse(body)
25 | const currencies = []
26 |
27 | jsonObject.forEach((object) => {
28 | const changePer = parseFloat(object.LastValueChange) / parseFloat(object.MidValue)
29 | const currency = {
30 | shortName: object.Ticker,
31 | longName: h.currency[object.Ticker].long,
32 | value: object.MidValue,
33 | askValue: object.AskValue,
34 | bidValue: object.BidValue,
35 | changeCur: object.LastValueChange,
36 | changePer: changePer.toFixed(2),
37 | }
38 |
39 | if (currency.changePer === '-0.00') {
40 | currency.changePer = 0
41 | }
42 |
43 | currencies.push(currency)
44 | })
45 |
46 | return res.cache(60).json({ results: currencies })
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/endpoints/currency/borgun.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-plusplus */
2 | /* eslint-disable prefer-destructuring */
3 | const request = require('request')
4 | const xml2js = require('xml2js')
5 | const app = require('../../server')
6 |
7 | const parseString = xml2js.parseString
8 |
9 | app.get('/currency/borgun', (req, res) => {
10 | request.get(
11 | { url: 'https://www.borgun.is/currency/Default.aspx?function=all' },
12 | (err, response, xml) => {
13 | if (err || response.statusCode !== 200) {
14 | return res.status(500).json({ error: 'www.borgun.is refuses to respond or give back data' })
15 | }
16 |
17 | const currencies = []
18 | parseString(xml, { explicitRoot: false }, (parseError, result) => {
19 | if (parseError || result.Status[0].ResultCode[0] !== '0') {
20 | return res.status(500).json({ error: `Unable to parse Borgun data: ${JSON.stringify(err)}` })
21 | }
22 |
23 | for (let i = 0; i < result.Rate.length; i++) {
24 | const rate = result.Rate[i]
25 | currencies.push({
26 | currencyCode: rate.CurrencyCode[0],
27 | currencyDescription: rate.CurrencyDescription[0],
28 | currencyRate: parseFloat(rate.CurrencyRate[0]),
29 | country: rate.Country[0],
30 | countryEnglish: rate.CountryEnglish[0],
31 | countryCode: rate.CountryCode[0],
32 | rateDate: rate.RateDate[0],
33 | })
34 | }
35 | return res.cache(60).json({ results: currencies })
36 | })
37 | }
38 | )
39 | })
40 |
--------------------------------------------------------------------------------
/endpoints/currency/documentation.md:
--------------------------------------------------------------------------------
1 | # Currency in relation to ISK
2 |
3 | Source: [m5.is](http://m5.is/), [Arion banki](https://arionbanki.is/) and [Landsbankinn](https://landsbankinn.is/)
4 |
5 | - GET [/currency/:source](https://apis.is/currency/:source)
6 |
7 | Get currency data in relation to ISK
8 |
9 | | Parameters | Description | Example |
10 | |--------------------|---------------------|-----------------------|
11 | | :source (required) | Which source to use | [m5](https://apis.is/currency/m5), [arion](https://apis.is/currency/arion) or [lb](https://apis.is/currency/lb) |
12 |
13 |
14 | ---
15 |
--------------------------------------------------------------------------------
/endpoints/currency/index.js:
--------------------------------------------------------------------------------
1 | const app = require('../../server')
2 |
3 | app.get('/currency', (req, res) => {
4 | const provider = req.query.provider || 'arion'
5 | const providers = ['m5', 'arion', 'lb', 'borgun']
6 |
7 | if (providers.indexOf(provider) >= 0) {
8 | return res.redirect(301, `/currency/${provider}`)
9 | }
10 |
11 | return res.status(404).json({ error: 'This provider does not exist', code: 2 })
12 | })
13 |
--------------------------------------------------------------------------------
/endpoints/currency/lb.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-plusplus */
2 | /* eslint-disable prefer-destructuring */
3 | const request = require('request')
4 | const xml2js = require('xml2js')
5 | const app = require('../../server')
6 |
7 | const parseString = xml2js.parseString
8 |
9 | app.get('/currency/lb/:type?', (req, res) => {
10 | // A = Almennt gengi, S = Seðlagengi
11 | const type = req.params.type || 'A'
12 |
13 | request.get({
14 | url: `https://www.landsbankinn.is/modules/markets/services/XMLGengi.asmx/NyjastaGengiByType?strTegund=${type}`,
15 | }, (err, response, xml) => {
16 | if (err || response.statusCode !== 200) {
17 | return res.status(500).json({ error: 'www.landsbankinn.is refuses to respond or give back data' })
18 | }
19 |
20 | const currencies = []
21 | parseString(xml, { explicitRoot: false }, (parseError, result) => {
22 | const arr = result.GjaldmidillRow
23 | for (let i = 0; i < arr.length; i++) {
24 | currencies.push({
25 | shortName: arr[i].Mynt[0],
26 | longName: arr[i].Heiti[0],
27 | value: parseFloat(arr[i].Midgengi),
28 | askValue: parseFloat(arr[i].Sala),
29 | bidValue: parseFloat(arr[i].Kaup),
30 | changeCur: parseFloat(arr[i].Breyting[0]),
31 | changePer: parseFloat((parseFloat(arr[i].Breyting) / parseFloat(arr[i].Midgengi)).toFixed(2)),
32 | })
33 | }
34 | return res.cache(60).json({ results: currencies })
35 | })
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/endpoints/currency/m5.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/first */
2 | const request = require('request')
3 | const h = require('apis-helpers')
4 | const cheerio = require('cheerio')
5 | const app = require('../../server')
6 |
7 | app.get('/currency/m5', (req, res) => {
8 | // FIXME: Not being used, comment out for now
9 | // const currencyNames = {
10 | // s: ['USD', 'DKK', 'EUR', 'JPY', 'CAD', 'NOK', 'GBP', 'CHF', 'SEK', 'TWI', 'XDR', 'ISK'],
11 | // l: [
12 | // 'Bandarískur dalur',
13 | // 'Dönsk króna',
14 | // 'Evra',
15 | // 'Japanskt jen',
16 | // 'Kanadískur dalur',
17 | // 'Norsk króna',
18 | // 'Sterlingspund',
19 | // 'Svissneskur franki',
20 | // 'Sænsk króna',
21 | // 'Gengisvísitala',
22 | // 'SDR',
23 | // 'Íslensk króna',
24 | // ],
25 | // }
26 |
27 | request.get({
28 | headers: { 'User-Agent': h.browser() },
29 | url: 'http://www.m5.is/?gluggi=gjaldmidlar',
30 | }, (err, response, body) => {
31 | if (err || response.statusCode !== 200) {
32 | return res.status(500).json({ error: 'www.m5.is refuses to respond or give back data' })
33 | }
34 |
35 | const $ = cheerio.load(body)
36 | const currencies = []
37 |
38 | $('.table-striped tr').each(function () {
39 | const tds = $(this).find('td')
40 | const name = tds.eq(0).text()
41 |
42 | if (name) {
43 | currencies.push({
44 | shortName: name,
45 | longName: h.currency[name].long,
46 | value: parseFloat(tds.eq(2).text().replace(',', '.')),
47 | askValue: 0,
48 | bidValue: 0,
49 | changeCur: parseFloat(tds.eq(4).text().replace(',', '.')),
50 | changePer: parseFloat(tds.eq(5).text().replace(/\((.*)%\)/, '$1').replace(',', '.')),
51 | })
52 | }
53 | })
54 |
55 | return res.cache(60).json({ results: currencies })
56 | })
57 | })
58 |
--------------------------------------------------------------------------------
/endpoints/currency/tests/arion.fixture:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "AskValue": 1,
4 | "BidValue": 1,
5 | "CustomsRate": 0,
6 | "LastValueChange": 0,
7 | "MainTicker": null,
8 | "MidValue": 1,
9 | "Ticker": "ISK",
10 | "Time": "2017-12-28T11:00:25.857",
11 | "Title": null
12 | },
13 | {
14 | "AskValue": 105.29,
15 | "BidValue": 104.66,
16 | "CustomsRate": 0,
17 | "LastValueChange": -0.094886,
18 | "MainTicker": null,
19 | "MidValue": 104.975,
20 | "Ticker": "USD",
21 | "Time": "2017-12-28T11:00:25.857",
22 | "Title": null
23 | },
24 | {
25 | "AskValue": 141.53,
26 | "BidValue": 140.69,
27 | "CustomsRate": 0,
28 | "LastValueChange": -0.155203,
29 | "MainTicker": null,
30 | "MidValue": 141.11,
31 | "Ticker": "GBP",
32 | "Time": "2017-12-28T11:00:25.857",
33 | "Title": null
34 | },
35 | {
36 | "AskValue": 125.63,
37 | "BidValue": 124.87,
38 | "CustomsRate": 0,
39 | "LastValueChange": -0.158945,
40 | "MainTicker": null,
41 | "MidValue": 125.25,
42 | "Ticker": "EUR",
43 | "Time": "2017-12-28T11:00:25.857",
44 | "Title": null
45 | },
46 | {
47 | "AskValue": 83.472,
48 | "BidValue": 82.973,
49 | "CustomsRate": 0,
50 | "LastValueChange": -0.145943,
51 | "MainTicker": null,
52 | "MidValue": 83.2225,
53 | "Ticker": "CAD",
54 | "Time": "2017-12-28T11:00:25.857",
55 | "Title": null
56 | },
57 | {
58 | "AskValue": 16.873,
59 | "BidValue": 16.772,
60 | "CustomsRate": 0,
61 | "LastValueChange": -0.177483,
62 | "MainTicker": null,
63 | "MidValue": 16.8225,
64 | "Ticker": "DKK",
65 | "Time": "2017-12-28T11:00:25.857",
66 | "Title": null
67 | },
68 | {
69 | "AskValue": 12.742,
70 | "BidValue": 12.666,
71 | "CustomsRate": 0,
72 | "LastValueChange": -0.437568,
73 | "MainTicker": null,
74 | "MidValue": 12.704,
75 | "Ticker": "NOK",
76 | "Time": "2017-12-28T11:00:25.857",
77 | "Title": null
78 | },
79 | {
80 | "AskValue": 12.755,
81 | "BidValue": 12.679,
82 | "CustomsRate": 0,
83 | "LastValueChange": -0.195618,
84 | "MainTicker": null,
85 | "MidValue": 12.717,
86 | "Ticker": "SEK",
87 | "Time": "2017-12-28T11:00:25.857",
88 | "Title": null
89 | },
90 | {
91 | "AskValue": 107.37,
92 | "BidValue": 106.73,
93 | "CustomsRate": 0,
94 | "LastValueChange": -0.111638,
95 | "MainTicker": null,
96 | "MidValue": 107.05,
97 | "Ticker": "CHF",
98 | "Time": "2017-12-28T11:00:25.857",
99 | "Title": null
100 | },
101 | {
102 | "AskValue": 0.9327,
103 | "BidValue": 0.9272,
104 | "CustomsRate": 0,
105 | "LastValueChange": -0.224647,
106 | "MainTicker": null,
107 | "MidValue": 0.92995,
108 | "Ticker": "JPY",
109 | "Time": "2017-12-28T11:00:25.857",
110 | "Title": null
111 | },
112 | {
113 | "AskValue": 149.63,
114 | "BidValue": 148.73,
115 | "CustomsRate": 0,
116 | "LastValueChange": -0.133485,
117 | "MainTicker": null,
118 | "MidValue": 149.18,
119 | "Ticker": "XDR",
120 | "Time": "2017-12-28T11:00:25.857",
121 | "Title": null
122 | }
123 | ]
124 |
--------------------------------------------------------------------------------
/endpoints/currency/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/extensions */
2 | const fs = require('fs')
3 | const nock = require('nock')
4 | const request = require('request')
5 | const helpers = require('../../../lib/test_helpers.js')
6 |
7 | describe('currency', () => {
8 | // The only thing that changes is the form attribute, so why not just re-use the object
9 | const fieldsToCheckFor = ['shortName', 'longName', 'value', 'askValue', 'bidValue', 'changeCur', 'changePer']
10 |
11 | describe('searching using provider "m5"', () => {
12 | before(() => {
13 | nock('http://www.m5.is')
14 | .get('/')
15 | .query({ gluggi: 'gjaldmidlar' })
16 | .reply(200, fs.readFileSync(`${__dirname}/m5.fixture`))
17 | })
18 |
19 | it('should return an array of objects containing correct fields', (done) => {
20 | const params = helpers.testRequestParams('/currency/m5')
21 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
22 | request(params, resultHandler)
23 | })
24 | })
25 |
26 | describe('searching using provider "arion"', () => {
27 | before(() => {
28 | nock('https://www.arionbanki.is')
29 | .get('/Webservice/PortalCurrency.ashx')
30 | .reply(200, fs.readFileSync(`${__dirname}/arion.fixture`))
31 | })
32 |
33 | it('should return an array of objects containing correct fields', (done) => {
34 | const params = helpers.testRequestParams('/currency/arion')
35 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
36 | request(params, resultHandler)
37 | })
38 | })
39 |
40 | describe('searching using provider "lb"', () => {
41 | it('should return an array of objects containing correct fields', (done) => {
42 | const params = helpers.testRequestParams('/currency/lb')
43 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
44 | request(params, resultHandler)
45 | })
46 | })
47 |
48 | describe('searching using provider "borgun"', () => {
49 | it('should return an array of objects containing correct fields', (done) => {
50 | const params = helpers.testRequestParams('/currency/borgun')
51 | const fields = [
52 | 'currencyCode',
53 | 'currencyDescription',
54 | 'currencyRate',
55 | 'country',
56 | 'countryEnglish',
57 | 'countryCode',
58 | 'rateDate',
59 | ]
60 | const resultHandler = helpers.testRequestHandlerForFields(done, fields)
61 | request(params, resultHandler)
62 | })
63 | })
64 | })
65 |
--------------------------------------------------------------------------------
/endpoints/currency/tests/m5.fixture:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apis-is/apis/96a23ab30d5b498a0805da06cf87e84d61422c27/endpoints/currency/tests/m5.fixture
--------------------------------------------------------------------------------
/endpoints/cyclecounter/documentation.md:
--------------------------------------------------------------------------------
1 | # Bicyclecounter in Reykjavik
2 |
3 | Source: [Bicycle Counter](http://bicyclecounter.dk/)
4 |
5 | - GET [/cyclecounter](https://apis.is/cyclecounter)
6 |
7 | Get current status of bicycle counters in Iceland, currently only one located by Suðurlandsbraut in Reykjavík.
8 |
9 | ---
10 |
--------------------------------------------------------------------------------
/endpoints/cyclecounter/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-destructuring */
2 | const request = require('request')
3 | const xml2js = require('xml2js')
4 | const app = require('../../server')
5 |
6 | const parseString = xml2js.parseString
7 |
8 | app.get('/cyclecounter', (req, res) => {
9 | request.get({
10 | url: 'http://www.bicyclecounter.dk/BicycleCounter/GetCycleInfo?ran=1379500208853&StationId=235&LaneId=0',
11 | }, (err, response, xml) => {
12 | if (err || response.statusCode !== 200) {
13 | return res.status(500).json({ error: 'www.bicyclecounter.dk refuses to respond or give back data' })
14 | }
15 |
16 | const cyclecounter = []
17 | parseString(xml, { explicitRoot: false }, (parseError, result) => {
18 | cyclecounter.push({
19 | DayCount: result.DayCount[0],
20 | YearCount: result.YearCount[0],
21 | Time: result.Time[0],
22 | Date: result.Date[0],
23 | })
24 |
25 | return res.cache(5).json({ results: cyclecounter })
26 | })
27 | })
28 | })
29 |
--------------------------------------------------------------------------------
/endpoints/cyclecounter/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/extensions */
2 | const request = require('request')
3 | const helpers = require('../../../lib/test_helpers.js')
4 |
5 | describe('cyclecounter', () => {
6 | // The only thing that changes is the form attribute, so why not just re-use the object
7 | const fieldsToCheckFor = ['DayCount', 'YearCount', 'Time', 'Date']
8 |
9 | it('should return an array of objects containing correct fields', (done) => {
10 | const params = helpers.testRequestParams('/cyclecounter')
11 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
12 | request(params, resultHandler)
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/endpoints/declension/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-destructuring */
2 | /* eslint-disable no-mixed-operators */
3 | /* eslint-disable import/first */
4 | const url = require('url')
5 | const request = require('request')
6 | const helper = require('apis-helpers')
7 | const cheerio = require('cheerio')
8 | const _ = require('lodash')
9 | const app = require('../../server')
10 |
11 | const baseUrl = url.parse('http://dev.phpbin.ja.is/ajax_leit.php')
12 |
13 | // return permutation of a given word
14 | function getDeclensions(callback, providedParams) {
15 | const params = Object.assign({}, providedParams)
16 | request.get(params, (err, res, body) => {
17 | if (err || res.statusCode !== 200) {
18 | return res.status(500).json({
19 | error: 'A request to dev.phpbin.ja.is resulted in a error',
20 | })
21 | }
22 |
23 | let $
24 | const sanitisedBody = body.replace(//g, '')
25 |
26 | try {
27 | $ = cheerio.load(sanitisedBody)
28 | } catch (error) {
29 | return res.status(500).json({
30 | error: 'Parsing the data from dev.phpbin.ja.is resulted in a error',
31 | moreinfo: error,
32 | })
33 | }
34 |
35 | // Links mean results!
36 | const result = $('a')
37 |
38 | // more than 1 result from request (ex: 'hús')
39 | if (result.length > 1) {
40 | // call recursively again with new url
41 | const id = result[0].attribs.on_click.match(/\d+/)[0]
42 | baseUrl.query = { id }
43 | params.url = url.format(baseUrl)
44 | return getDeclensions(callback, params)
45 | }
46 |
47 | // else just call func to return data
48 | return callback($)
49 | })
50 | }
51 |
52 | // Creates a sequence of integers, each iteration creates a value and increments that value by 1
53 | // step: specify how often to run the iteration
54 | // increment: how much to increment after each iteration
55 | function generateSequence(start, step, increment) {
56 | // ex:
57 | // input: start: 0, step: 4, increment: 3
58 | // output: [ 0, 1, 4, 5, 8, 9, 12, 13 ]
59 | const results = []
60 |
61 | _.each(_.range(start, step), (i) => {
62 | const value = (i + increment * i)
63 |
64 | results.push(value, value + 1)
65 | })
66 |
67 | return results
68 | }
69 |
70 | function parseTable($) {
71 | const type = $('small').contents().text().trim()
72 |
73 | // create a sequence which is the same as the index of 'Eintala' of the td's
74 | // in the HTML table.
75 | const singular = generateSequence(0, 4, 3)
76 | const results = []
77 |
78 | $('table tr td span').each((i, element) => {
79 | const predicate = (
80 | element
81 | .parent
82 | .parent
83 | .children
84 | .filter(node => node.name === 'td')[0]
85 | .children[0]
86 | .data
87 | )
88 |
89 | results.push({
90 | predicate,
91 | value: element.children[0].data,
92 | category: i in singular ? 'Eintala' : 'Fleirtala',
93 | })
94 | })
95 |
96 | return { results, type }
97 | }
98 |
99 | app.get('/declension/:word', (req, res) => {
100 | const word = req.params.word
101 | baseUrl.query = { q: word }
102 |
103 | const params = {
104 | url: url.format(baseUrl),
105 | headers: {
106 | 'User-Agent': helper.browser(),
107 | },
108 | }
109 |
110 | getDeclensions((body) => {
111 | return res.cache(86400).json(parseTable(body))
112 | }, params)
113 | })
114 |
--------------------------------------------------------------------------------
/endpoints/declension/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/extensions */
2 | const request = require('request')
3 | const helpers = require('../../../lib/test_helpers.js')
4 |
5 | describe('declension', () => {
6 | it('should return an array of objects containing correct fields', (done) => {
7 | const fieldsToCheckFor = ['predicate', 'value', 'category']
8 | const params = helpers.testRequestParams('/declension/laugavegur')
9 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
10 | request.get(params, resultHandler)
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/endpoints/earthquake/documentation.md:
--------------------------------------------------------------------------------
1 | # Earthquakes in Iceland
2 |
3 | Source: [Icelandic Meteorological Office](http://vedur.is)
4 |
5 | - GET [/earthquake/is](https://apis.is/earthquake/is)
6 |
7 | Get earthquake monitoring data for the last 48 hours.
8 |
9 | ---
10 |
--------------------------------------------------------------------------------
/endpoints/earthquake/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-prototype-builtins */
2 | /* eslint-disable no-restricted-syntax */
3 | /* eslint-disable no-useless-escape */
4 | /* eslint-disable prefer-destructuring */
5 | const request = require('request')
6 | const cheerio = require('cheerio')
7 | const helpers = require('apis-helpers')
8 | const app = require('../../server')
9 |
10 | const browser = helpers.browser
11 |
12 | /*
13 | This function only handles the request part and calls callback with
14 | the body
15 | */
16 | function getEarthquakes(callback, params) {
17 | // eslint-disable-next-line eqeqeq
18 | const reqParams = !params ? {
19 | url: 'http://hraun.vedur.is/ja/skjalftar/skjlisti.html',
20 | headers: { 'User-Agent': browser() },
21 | // needed for some reason.. defaulting to ISO-8859-1
22 | encoding: 'binary',
23 | } : params
24 |
25 | request(reqParams, (error, res, body) => {
26 | if (error || res.statusCode !== 200) {
27 | return callback(new Error('Could not retrieve the data from the data source'))
28 | }
29 |
30 | return callback(error, body)
31 | })
32 | }
33 |
34 | /*
35 | * This function traverses the DOM, and looks for a JS variable that is included
36 | * in the source of vedur.is.
37 | * Note that it is synchronous.
38 | */
39 | function parseJavaScriptVariable(body) {
40 | // Work with empty string if scraping fails.
41 | let res
42 | let jsonString = ''
43 | // Create a cheerio object from response body.
44 | const $ = cheerio.load(body)
45 |
46 | // Find the variable inside one of the