├── docs
├── .gitignore
└── header.md
├── index.js
├── .gitignore
├── endpoints
├── currency
│ ├── tests
│ │ ├── m5.fixture
│ │ ├── integration_test.js
│ │ └── arion.fixture
│ ├── index.js
│ ├── documentation.md
│ ├── lb.js
│ ├── borgun.js
│ ├── arion.js
│ └── m5.js
├── isbolti
│ ├── tests
│ │ ├── test.fixture
│ │ └── integration_test.js
│ └── index.js
├── earthquake
│ ├── tests
│ │ ├── test.fixture
│ │ └── integration_test.js
│ ├── documentation.md
│ └── index.js
├── sarschool
│ ├── tests
│ │ ├── test.fixture
│ │ └── integration_test.js
│ ├── documentation.md
│ └── index.js
├── concerts
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── aur
│ ├── documentation.md
│ ├── index.js
│ └── tests
│ │ └── integration_test.js
├── lottery
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── particulates
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── petrol
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── hospital
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── cyclecounter
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── bus
│ ├── index.js
│ ├── documentation.md
│ ├── search.js
│ ├── graphql_schema.js
│ ├── tests
│ │ └── integration_test.js
│ └── realtime.js
├── frontpage
│ └── index.js
├── rides
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── static_endpoints
│ ├── frontpage.js
│ ├── help_out.js
│ └── phone.js
├── sports
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── address
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ ├── graphql_schema.js
│ └── index.js
├── car
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ ├── graphql_schema.js
│ └── index.js
├── carparks
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── declension
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── cinema
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── tv
│ ├── documentation.md
│ ├── index.js
│ ├── skjarinn.js
│ ├── ruv.js
│ ├── 365.js
│ └── tests
│ │ └── integration_test.js
├── flight
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── hljomaholl
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── ship
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── isnic
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── company
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── golf
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── calendar
│ ├── graphql_schema.js
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
├── weather
│ ├── documentation.md
│ ├── tests
│ │ └── integration_test.js
│ └── index.js
└── names
│ ├── index.js
│ └── tests
│ └── integration_test.js
├── .github
├── PULL_REQUEST_TEMPLATE
├── ISSUE_TEMPLATE
├── CODEOWNERS
└── stale.yml
├── lib
├── expressMetrics.js
├── redis.js
├── cors.js
├── expressMetrics.spec.js
├── cache.js
└── test_helpers.js
├── make-docs.js
├── test.js
├── .circleci
└── config.yml
├── LICENSE
├── graphql
└── types
│ └── GraphQLDate.js
├── package.json
├── server.js
└── README.md
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./server.js')
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | npm-debug.log
--------------------------------------------------------------------------------
/endpoints/currency/tests/m5.fixture:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apis-is/apis/HEAD/endpoints/currency/tests/m5.fixture
--------------------------------------------------------------------------------
/endpoints/isbolti/tests/test.fixture:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apis-is/apis/HEAD/endpoints/isbolti/tests/test.fixture
--------------------------------------------------------------------------------
/endpoints/earthquake/tests/test.fixture:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apis-is/apis/HEAD/endpoints/earthquake/tests/test.fixture
--------------------------------------------------------------------------------
/endpoints/sarschool/tests/test.fixture:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/apis-is/apis/HEAD/endpoints/sarschool/tests/test.fixture
--------------------------------------------------------------------------------
/.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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/endpoints/lottery/documentation.md:
--------------------------------------------------------------------------------
1 | # Icelandic lottery
2 |
3 | Source: [The Icelandic Government Lottery](https://games.lotto.is/)
4 |
5 | - GET [/lottery](https://apis.is/lottery)
6 |
7 | Get the most recent numbers for the Icelandic lottery.
8 |
9 | ---
10 |
--------------------------------------------------------------------------------
/endpoints/particulates/documentation.md:
--------------------------------------------------------------------------------
1 | # Particulates in Reykjavik
2 |
3 | Source: [Reykjavik City](http://reykjavik.is/)
4 |
5 | - GET [/particulates](https://apis.is/particulates)
6 |
7 | Get current status of particulates in Reykjavik City
8 |
9 | ---
10 |
--------------------------------------------------------------------------------
/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/petrol/documentation.md:
--------------------------------------------------------------------------------
1 | # Petrol stations in Iceland
2 |
3 | Source: [github.com/gasvaktin](https://github.com/gasvaktin/gasvaktin)
4 |
5 | - GET [/petrol](https://apis.is/petrol)
6 |
7 | Lookup locations and gas prices for petrol stations in Iceland.
8 |
9 | ---
10 |
--------------------------------------------------------------------------------
/lib/expressMetrics.js:
--------------------------------------------------------------------------------
1 | const redis = require('./redis')
2 |
3 | module.exports = function () {
4 | return function (req, res, next) {
5 | if (req.path !== '/metrics') {
6 | redis.hincrby('metrics', `${req.path}.${req.method}.callCount`, 1)
7 | }
8 | next()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/endpoints/hospital/documentation.md:
--------------------------------------------------------------------------------
1 | # Status of Icelandic hospitals
2 |
3 | Source: [The National University Hospital of Iceland](http://landspitali.is)
4 |
5 | - GET [/hospital](https://apis.is/hospital)
6 |
7 | Get the current status of the National University Hospital of Iceland.
8 |
9 | ---
10 |
--------------------------------------------------------------------------------
/endpoints/sarschool/documentation.md:
--------------------------------------------------------------------------------
1 | # Landsbjörg SAR School course schedule
2 |
3 | Source: [SAR School](http://skoli.landsbjorg.is/)
4 |
5 | - GET [/sarschool](https://apis.is/sarschool)
6 |
7 | Get a list of all courses scheduled by the Search and Rescue school for training of SAR Squad members.
8 |
9 | ---
10 |
--------------------------------------------------------------------------------
/lib/redis.js:
--------------------------------------------------------------------------------
1 | const Redis = require('redis')
2 | const debug = require('debug')('redis')
3 |
4 | const redis = Redis.createClient(process.env.REDIS_URL)
5 |
6 | redis.on('connect', () => {
7 | debug('connected')
8 | })
9 |
10 | redis.on('error', (error) => {
11 | debug(error)
12 | })
13 |
14 | module.exports = redis
15 |
--------------------------------------------------------------------------------
/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/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/frontpage/index.js:
--------------------------------------------------------------------------------
1 | const app = require('../../server')
2 |
3 | app.get('/', (req, res) => {
4 | res.redirect(301, 'http://docs.apis.is')
5 | })
6 |
7 | app.post('/', (req, res) => {
8 | res.json({
9 | info: 'Velkominn á apis.is! Kíktu á docs.apis.is í vafranum þínum fyrir frekari upplýsingar!',
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/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/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/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/rides/documentation.md:
--------------------------------------------------------------------------------
1 | # Carpooling in Iceland
2 |
3 | Source: [samferda.net](http://samferda.net)
4 |
5 | - GET [/rides/samferda-drivers/](https://apis.is/rides/samferda-drivers/)
6 |
7 | Get a list of drivers requesting passengers, sorted by departure date.
8 |
9 | - GET [/rides/samferda-passengers/](https://apis.is/rides/samferda-passengers/)
10 |
11 | Get a list of passengers requesting rides, sorted by preferred departure date
12 |
13 | ---
14 |
--------------------------------------------------------------------------------
/endpoints/static_endpoints/frontpage.js:
--------------------------------------------------------------------------------
1 | const app = require('../../server')
2 |
3 | app.get('/', (req, res) => {
4 | res.redirect(301, 'http://docs.apis.is')
5 | })
6 |
7 | app.post('/', (req, res) => {
8 | res.json({
9 | info: {
10 | english: 'Hey there! Check out docs.apis.is in your browser for mor info',
11 | icelandic: 'Velkominn á apis.is! Kíktu á docs.apis.is í vafranum þínum fyrir frekari upplýsingar!',
12 | },
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/endpoints/sports/documentation.md:
--------------------------------------------------------------------------------
1 | # Sport events in Iceland
2 |
3 | Source: [KSÍ](http://ksi.is) and [HSÍ](http://hsi.is)
4 |
5 | - GET [/sports/:sport](https://apis.is/sports/:sport)
6 |
7 | Get events for Icelandic football and/or handball
8 |
9 | | Parameters | Description | Example |
10 | |-------------------|---------------------------|---------|
11 | | :sport (required) | Which sport events to get | [football](https://apis.is/sports/football), [handball](https://apis.is/sports/handball) |
12 |
13 | ---
14 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/static_endpoints/help_out.js:
--------------------------------------------------------------------------------
1 |
2 | function helpOut(req, res, next) {
3 | res.header('Access-Control-Allow-Origin', '*')
4 | res.header('Access-Control-Allow-Headers', 'X-Requested-With')
5 | res.status(200).json({ message: 'Send us mail: apis@apis.is ,thanks for your interest!' })
6 | next()
7 | }
8 |
9 | exports.setup = function (server) {
10 | server.post({ path: '/help_out', version: '1.0.0' }, helpOut)
11 | server.get({ path: '/help_out', version: '1.0.0' }, helpOut)
12 | server.post({ path: '/help', version: '1.0.0' }, helpOut)
13 | server.get({ path: '/help', version: '1.0.0' }, helpOut)
14 | }
15 |
--------------------------------------------------------------------------------
/make-docs.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const globby = require('globby')
3 | const marked = require('marked');
4 |
5 | (async () => {
6 | const paths = await globby(['./docs/*.md', './endpoints/**/*.md', '!node_modules/**'])
7 |
8 | let content = ''
9 | paths.forEach((path) => {
10 | content += fs.readFileSync(path, 'utf8')
11 | })
12 | const html = marked(content, { escapeMarkdown: false })
13 |
14 | if (!fs.existsSync('./docs/dist/')) {
15 | fs.mkdirSync('./docs/dist/')
16 | }
17 | fs.writeFileSync('./docs/dist/index.html', html, 'utf8')
18 | })().catch((error) => {
19 | console.error(error)
20 | })
21 |
--------------------------------------------------------------------------------
/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/tv/documentation.md:
--------------------------------------------------------------------------------
1 | # Icelandic television schedules
2 |
3 | Source: [RÚV](http://ruv.is), [Stöð 2](http://stod2.is) and [SkjárEinn](http://skjarinn.is/einn)
4 |
5 | - GET [/tv/:channel](https://apis.is/tv/:channel)
6 |
7 | Get schedules for Icelandic tv stations.
8 |
9 | | Parameters | Description | Example |
10 | |------------|---------------------------------|------------------------------------------------------------------|
11 | | :channel | Which channel's schedule to get | [ruv](https://apis.is/tv/ruv), [stod2](https://apis.is/tv/stod2) |
12 |
13 | ---
14 |
--------------------------------------------------------------------------------
/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/static_endpoints/phone.js:
--------------------------------------------------------------------------------
1 | function phone(req, res, next) {
2 | res.header('Access-Control-Allow-Origin', '*')
3 | res.header('Access-Control-Allow-Headers', 'X-Requested-With')
4 | res.status(410).json({
5 | error: 'This api endpoint has been closed and it will not be available in the foreseeable future.',
6 | })
7 | next()
8 | }
9 |
10 | exports.setup = function (server) {
11 | server.post({ path: '/phone', version: '1.0.0' }, phone)
12 | server.get({ path: '/phone', version: '1.0.0' }, phone)
13 | server.post({ path: '/phone', version: '2.0.0' }, phone)
14 | server.get({ path: '/phone', version: '2.0.0' }, phone)
15 | }
16 |
--------------------------------------------------------------------------------
/endpoints/flight/documentation.md:
--------------------------------------------------------------------------------
1 | # International flights in Iceland
2 |
3 | Source: [Keflavik Airport](https://kefairport.is/)
4 |
5 | - GET [/flight](https://apis.is/flight)
6 |
7 | Get a list of all international flights departing and arriving at Keflavik Airport today.
8 |
9 | | Parameters | Description | Example |
10 | |---------------------|------------------------------------|----------------------------|
11 | | Language (required) | The language to get the results in | 'en' or 'is' |
12 | | Type (required) | The type of flights to fetch | 'departures' or 'arrivals' |
13 |
14 | ---
15 |
--------------------------------------------------------------------------------
/lib/cors.js:
--------------------------------------------------------------------------------
1 | module.exports = function () {
2 | return (req, res, next) => {
3 | const allowedHeaders = [
4 | 'X-Requested-With',
5 | 'Accept',
6 | 'Origin',
7 | 'Referer',
8 | 'User-Agent',
9 | 'Content-Type',
10 | 'Authorization',
11 | 'X-Mindflash-SessionID',
12 | ]
13 |
14 | res.header('Access-Control-Allow-Origin', '*')
15 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
16 | res.header('Access-Control-Allow-Headers', allowedHeaders.join(', '))
17 |
18 | if (req.method !== 'OPTIONS') {
19 | return next()
20 | }
21 |
22 | return res.sendStatus(200)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/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/flight/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('flight', () => {
6 | it('should return an array of objects containing correct fields', (done) => {
7 | const fieldsToCheckFor = ['date', 'flightNumber', 'airline', 'to', 'plannedArrival', 'realArrival', 'status']
8 | const params = helpers.testRequestParams('/flight', {
9 | language: 'en',
10 | type: 'departures',
11 | })
12 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
13 | request.get(params, resultHandler)
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/endpoints/particulates/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('particulates', () => {
6 | // The only thing that changes is the form attribute, so why not just re-use the object
7 | const fieldsToCheckFor = ['PM10nuna', 'PM10medaltal', 'Counter', 'Dags', 'nanariuppl']
8 |
9 | it.skip('should return an array of objects containing correct fields', (done) => {
10 | const params = helpers.testRequestParams('/particulates')
11 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
12 | request(params, resultHandler)
13 | })
14 | })
15 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/endpoints/petrol/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/extensions */
2 | const request = require('request')
3 | const helpers = require('../../../lib/test_helpers.js')
4 |
5 | const fieldsToCheckFor = [
6 | 'bensin95',
7 | 'bensin95_discount',
8 | 'company',
9 | 'diesel',
10 | 'diesel_discount',
11 | 'geo',
12 | 'key',
13 | 'name',
14 | ]
15 |
16 | describe('get-petrol-stations', function () {
17 | this.timeout(20000)
18 | it('should return an array of objects containing correct fields', (done) => {
19 | const params = helpers.testRequestParams('/petrol')
20 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
21 | request.get(params, resultHandler)
22 | })
23 | })
24 |
--------------------------------------------------------------------------------
/endpoints/hljomaholl/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('hljomaholl', () => {
6 | // The only thing that changes is the form attribute, so why not just re-use the object
7 | const fieldsToCheckFor = [
8 | 'date', 'time', 'image', 'title', 'description', 'location', 'buyTicketURL', 'moreInfoURL',
9 | ]
10 |
11 | it('should return an array of items with correct fields', (done) => {
12 | const params = helpers.testRequestParams('/hljomaholl')
13 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor, null, true)
14 | request(params, resultHandler)
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/endpoints/ship/documentation.md:
--------------------------------------------------------------------------------
1 | # Icelandic Ship Registry
2 |
3 | Source: [The Icelandic Transport Authority](https://www.samgongustofa.is/siglingar/skrar-og-utgafa/skipaskra/uppfletting/)
4 |
5 | - GET [/ship](https://apis.is/ship)
6 |
7 | Search the Icelandic ship registry
8 |
9 | | Parameters | Description | Example |
10 | |-------------------|---------------------------------------------------------------|---------------|
11 | | Search (required) | Ship name, regional code or the registy's registration number | [engey](https://apis.is/ship?search=engey), [RE-100](https://apis.is/ship?search=RE-100) |
12 |
13 | ---
14 |
--------------------------------------------------------------------------------
/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/isbolti/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('isboltinn', () => {
8 | it('Should return an array of objects with fixed amount of fields.', (done) => {
9 | before(() => {
10 | nock('http://fotbolti.net')
11 | .get('/isboltinn.php')
12 | .reply(200, fs.readFileSync(`${__dirname}/test.fixture`))
13 | })
14 | const fields = [
15 | 'place', 'team', 'gamesPlayed',
16 | 'gamesWon', 'gamesDraw', 'gamesLost',
17 | 'goals', 'goalDifference', 'points',
18 | ]
19 |
20 | const params = helpers.testRequestParams('/isbolti', {})
21 | const resultHandler = helpers.testRequestHandlerForFields(done, fields)
22 | request.get(params, resultHandler)
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/endpoints/isnic/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('isnic', () => {
6 | it('should return an array of objects containing correct fields', (done) => {
7 | const fieldsToCheckFor = [
8 | 'domain',
9 | 'registrantname',
10 | 'address',
11 | 'city',
12 | 'postalCode',
13 | 'country',
14 | 'phone',
15 | 'email',
16 | 'registered',
17 | 'expires',
18 | 'lastChange',
19 | ]
20 | const params = {
21 | url: 'http://localhost:3101/isnic',
22 | method: 'GET',
23 | qs: { domain: 'apis.is' },
24 | headers: ['Content-Type: application/json'],
25 | }
26 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
27 | request.get(params, resultHandler)
28 | })
29 | })
30 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/docs/header.md:
--------------------------------------------------------------------------------
1 |
2 |
47 |
48 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs')
2 | const nock = require('nock')
3 |
4 | const mockDataFilename = './mock-data.json'
5 |
6 | before(() => {
7 | if (process.env.RECORD_MOCK_DATA) {
8 | nock.recorder.rec({
9 | output_objects: true,
10 | dont_print: true,
11 | })
12 | } else {
13 | nock.load(mockDataFilename)
14 | }
15 | })
16 |
17 | after(() => {
18 | if (process.env.RECORD_MOCK_DATA) {
19 | const nockCallObjects = nock.recorder.play()
20 | const noLocalhost = nockCallObjects.filter((o) => {
21 | return ![
22 | 'http://localhost:3101',
23 | 'http://www.m5.is:80',
24 | 'http://hraun.vedur.is:80',
25 | 'http://www.vedur.is:80',
26 | 'http://www.landspitali.is:80',
27 | 'http://fotbolti.net:80',
28 | 'http://skoli.landsbjorg.is:80',
29 | 'http://www.worldfengur.com:80',
30 | ].includes(o.scope)
31 | })
32 | fs.writeFileSync(mockDataFilename, JSON.stringify(noLocalhost, null, 2))
33 | }
34 | })
35 |
--------------------------------------------------------------------------------
/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/sarschool/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('sarschool', () => {
8 | before(() => {
9 | nock('http://skoli.landsbjorg.is')
10 | .get('/Open/Seminars.aspx?')
11 | .reply(200, fs.readFileSync(`${__dirname}/test.fixture`))
12 | })
13 |
14 | // The only thing that changes is the form attribute, so why not just re-use the object
15 | const fieldsToCheckFor = [
16 | 'id', 'name', 'time_start', 'time_end', 'sar_members_only', 'host',
17 | 'location', 'price_regular', 'price_members', 'link',
18 | ]
19 |
20 | it('should return an array of objects containing correct fields', (done) => {
21 | const params = helpers.testRequestParams('/sarschool')
22 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
23 | request(params, resultHandler)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/lib/expressMetrics.spec.js:
--------------------------------------------------------------------------------
1 | const sinon = require('sinon')
2 | const expect = require('expect')
3 | const redis = require('./redis')
4 | const metricsMiddleware = require('./expressMetrics')
5 |
6 | describe('Metrics Middleware', () => {
7 | let redisSpy = null
8 |
9 | beforeEach(() => {
10 | redisSpy = sinon.spy(redis, 'hincrby')
11 | })
12 |
13 | afterEach(() => {
14 | redis.hincrby.restore()
15 | })
16 |
17 | it('should record how often a endpoint is called', () => {
18 | // Call the middleware directly threee times
19 | metricsMiddleware()({ path: '/car?carPlate=foo' }, null, () => {})
20 | metricsMiddleware()({ path: '/car?carPlate=bar' }, null, () => {})
21 | metricsMiddleware()({ path: '/car?carPlate=baz' }, null, () => {})
22 |
23 | expect(redisSpy.callCount).toBe(3)
24 | })
25 |
26 | it('should not record how often a endpoint if it\'s /metrics', () => {
27 | metricsMiddleware()({ path: '/metrics' }, null, () => {})
28 |
29 | expect(redisSpy.callCount).toBe(0)
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/endpoints/particulates/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('/particulates', (req, res) => {
9 | request.get({
10 | url: 'http://www.loft.rvk.is/xml/Xsvifryk.xml',
11 | }, (err, response, xml) => {
12 | if (err || response.statusCode !== 200) {
13 | return res.status(500).json({
14 | error: 'www.loft.rvk.is refuses to respond or give back data',
15 | })
16 | }
17 |
18 | const particulates = []
19 | parseString(xml, { explicitRoot: false }, (parseError, result) => {
20 | particulates.push({
21 | PM10nuna: result.PM10nuna[0],
22 | PM10medaltal: result.PM10medaltal[0],
23 | Counter: result.Counter[0],
24 | Dags: result.Dags[0],
25 | nanariuppl: result.nanariuppl[0],
26 | })
27 |
28 | return res.json({ results: particulates })
29 | })
30 | })
31 | })
32 |
--------------------------------------------------------------------------------
/endpoints/rides/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('rides root', () => {
6 | it('should return info and endpoints', (done) => {
7 | const fieldsToCheckFor = ['info', 'endpoints']
8 | const params = helpers.testRequestParams('/rides/')
9 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
10 | request.get(params, resultHandler)
11 | })
12 | });
13 |
14 | ['samferda-drivers', 'samferda-passengers'].forEach((api) => {
15 | const apiName = `rides - ${api}`
16 | describe(apiName, () => {
17 | const fieldsToCheckFor = ['link', 'from', 'to', 'date', 'time']
18 | it('should return an array of objects containing correct fields', (done) => {
19 | const params = helpers.testRequestParams(`/rides/${api}`)
20 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
21 | request.get(params, resultHandler)
22 | })
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/endpoints/hospital/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('hospital', () => {
8 | before(() => {
9 | nock('http://www.landspitali.is/')
10 | .get('/')
11 | .reply(200, fs.readFileSync(`${__dirname}/test.fixture`))
12 | })
13 | // The only thing that changes is the form attribute, so why not just re-use the object
14 | const fieldsToCheckFor = [
15 | 'birthNumbers', 'surgeries', 'dischargedNumbers', 'hospitalizedNumbers', 'atwork', 'patients-child',
16 | 'patients-er', 'patients-walk', 'patients-icu', 'donors', 'patients-skilun', 'patients-heart2',
17 | ]
18 |
19 | it('should return an array of objects containing correct fields', (done) => {
20 | const params = helpers.testRequestParams('/hospital')
21 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
22 | request(params, resultHandler)
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/endpoints/golf/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('golf', () => {
6 | // The only thing that changes is the form attribute, so why not just re-use the object
7 | const fieldsToCheckFor = ['abbreviation', 'club', 'location']
8 |
9 | it('should return an array of objects containing correct fields', (done) => {
10 | const params = helpers.testRequestParams('/golf/clubs')
11 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
12 | request(params, resultHandler)
13 | })
14 | })
15 |
16 | describe('golf', () => {
17 | // The only thing that changes is the form attribute, so why not just re-use the object
18 | const fieldsToCheckFor = ['time', 'name', 'club', 'handicap']
19 |
20 | it('should return an array of objects containing correct fields', (done) => {
21 | const params = helpers.testRequestParams('/golf/teetimes', { club: 61 })
22 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
23 | request(params, resultHandler)
24 | })
25 | })
26 |
--------------------------------------------------------------------------------
/endpoints/hospital/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/first */
2 | const request = require('request')
3 | const cheerio = require('cheerio')
4 | const _ = require('lodash')
5 | const app = require('../../server')
6 |
7 | app.get('/hospital', (req, res) => {
8 | request.get(
9 | { url: 'http://www.landspitali.is/' },
10 | (err, response, body) => {
11 | if (err || response.statusCode !== 200) {
12 | return res.status(500).json({
13 | error: 'www.landspitali.is refuses to respond or give back data',
14 | })
15 | }
16 |
17 | let $
18 | try {
19 | $ = cheerio.load(body)
20 | } catch (e) {
21 | return res.status(500).json({
22 | error: 'An error occured when parsing the data from landspitali.is',
23 | })
24 | }
25 |
26 | const data = {}
27 | _.each(
28 | $('.activityNumbers.activityNumbersNew').children('div'),
29 | (elem) => {
30 | data[elem.attribs.class] = parseInt($(elem).children().eq(1).html(), 10)
31 | }
32 | )
33 | // Cache for a hour.
34 | return res.cache(3600).json({ results: [data] })
35 | }
36 | )
37 | })
38 |
--------------------------------------------------------------------------------
/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/tv/index.js:
--------------------------------------------------------------------------------
1 | const app = require('../../server')
2 |
3 | /* Root TV */
4 | app.get('/tv', (req, res) => {
5 | return res.json({
6 | results: [
7 | {
8 | info: 'This is an api for Icelandic tv channel schedule\'s',
9 | endpoints: {
10 | ruv: '/tv/ruv/',
11 | ruvithrottir: '/tv/ruvithrottir/',
12 | stod2: '/tv/stod2/',
13 | stod2sport: '/tv/stod2sport',
14 | stod2sport2: '/tv/stod2sport2',
15 | stod2gull: '/tv/stod2gull',
16 | stod2bio: '/tv/stod2bio',
17 | stod3: '/tv/stod3',
18 | skjar1: '/tv/skjar1',
19 | },
20 | channels: [
21 | { name: 'Rúv', endpoint: '/tv/ruv/' },
22 | { name: 'Rúv íþróttir', endpoint: '/tv/ruvithrottir/' },
23 | { name: 'Stöð 2', endpoint: '/tv/stod2/' },
24 | { name: 'Stöð 2 Sport', endpoint: '/tv/stod2sport' },
25 | { name: 'Stöð 2 Sport 2', endpoint: '/tv/stod2sport2' },
26 | { name: 'Stöð 2 Gull', endpoint: '/tv/stod2gull' },
27 | { name: 'Stöð 2 Bíó', endpoint: '/tv/stod2bio' },
28 | { name: 'Stöð 2', endpoint: '/tv/stod3' },
29 | { name: 'Skjár 1', endpoint: '/tv/skjar1' },
30 | ],
31 | },
32 | ],
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/graphql/types/GraphQLDate.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | /* eslint-disable no-restricted-globals */
3 | const { GraphQLScalarType } = require('graphql')
4 | const { GraphQLError } = require('graphql/error')
5 | const { Kind } = require('graphql/language')
6 |
7 | const coerceDate = (value) => {
8 | if (!(value instanceof Date)) {
9 | throw new Error('[Field error]: value is not an instance of Date')
10 | }
11 | if (isNaN(value.getTime())) {
12 | throw new Error('[Field error]: value is an invalid Date')
13 | }
14 | return value.toJSON()
15 | }
16 |
17 | exports.GraphQLDate = new GraphQLScalarType({
18 | name: 'DateTime',
19 | serialize: coerceDate,
20 | parseValue: coerceDate,
21 | parseLiteral(ast) {
22 | if (ast.kind !== Kind.STRING) {
23 | throw new GraphQLError(`[Query error]: Can only parse strings to dates but got a: ${ast.kind}`, [ast])
24 | }
25 | const result = new Date(ast.value)
26 | if (isNaN(result.getTime())) {
27 | throw new GraphQLError('[Query error]: Invalid date', [ast])
28 | }
29 | if (ast.value !== result.toJSON()) {
30 | throw new GraphQLError('[Query error]: Invalid date format, only accepts: YYYY-MM-DDTHH:MM:SS.SSSZ', [ast])
31 | }
32 | return result
33 | },
34 | })
35 |
--------------------------------------------------------------------------------
/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/ship/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/extensions */
2 | const assert = require('assert')
3 | const request = require('request')
4 | const helpers = require('../../../lib/test_helpers.js')
5 |
6 | describe('ship', () => {
7 | it('should return an array of objects containing correct fields', (done) => {
8 | const fieldsToCheckFor = [
9 | 'name',
10 | 'type',
11 | 'registrationNumber',
12 | 'regionalCode',
13 | 'homePort',
14 | 'registrationStatus',
15 | 'grossRegisterTonnage',
16 | 'grossTonnage',
17 | 'length',
18 | 'buildYear',
19 | 'buildYard',
20 | 'owners',
21 | ]
22 | const params = helpers.testRequestParams('/ship', { search: 'helga maría' })
23 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
24 | request.get(params, resultHandler)
25 | })
26 | it('should return a 404 when a ship is not found', (done) => {
27 | const params = helpers.testRequestParams('/ship', { search: 'koddsson' })
28 | request.get(params, (error, response, body) => {
29 | if (error) {
30 | return done(error)
31 | }
32 | const json = JSON.parse(body)
33 | assert.equal(json.error, 'No ship found with the query koddsson')
34 | done()
35 | })
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/lib/cache.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-destructuring */
2 | // TODO: Find a way to enable no-param-reassign
3 | /* eslint-disable no-param-reassign */
4 | const debug = require('debug')('cache')
5 | const redis = require('./redis')
6 |
7 | module.exports = function () {
8 | return function (req, res, next) {
9 | if (process.env.NODE_ENV !== 'production' || !redis.connected) {
10 | res.cache = function () {
11 | return this
12 | }
13 | return next()
14 | }
15 |
16 | const end = res.end
17 | const write = res.write
18 | const chunkCache = []
19 | const key = req.originalUrl
20 |
21 | redis.get(key, (error, reply) => {
22 | if (error) {
23 | debug('Error in caching layer:', error)
24 | return next()
25 | }
26 |
27 | if (reply) {
28 | debug('cache HIT for %s', key)
29 | res.type('json')
30 | res.send(reply)
31 | } else {
32 | debug('cache MISS for %s', key)
33 | res.cache = function (timeout = 21600) {
34 | debug('will cache response of %s for %d seconds', key, timeout)
35 |
36 | res.write = function (chunk, encoding) {
37 | chunkCache.push(chunk)
38 | write.call(res, chunk, encoding)
39 | }
40 |
41 | res.end = function (chunk, encoding) {
42 | if (chunk) {
43 | this.write(chunk, encoding)
44 | }
45 |
46 | redis.setex(key, timeout, chunkCache.join(''))
47 | end.call(res)
48 | }
49 | return this
50 | }
51 | return next()
52 | }
53 | })
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/endpoints/isbolti/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/first */
2 | const request = require('request')
3 | const cheerio = require('cheerio')
4 | const iconv = require('iconv-lite')
5 | const app = require('../../server')
6 |
7 | // returns the stats table of Icelandic football teams.
8 | // data retrieved from fotbolti.net.
9 | app.get('/isbolti', (req, res) => {
10 | const stats = { results: [] }
11 |
12 | request({
13 | url: 'http://fotbolti.net/isboltinn.php',
14 | encoding: null,
15 | }, (error, response, html) => {
16 | let $
17 | if (error || response.statusCode !== 200) {
18 | return res.status(500).json({ error: 'Could not retreive data from fotbolti.net' })
19 | }
20 | const body = iconv.decode(html, 'iso-8859-1')
21 |
22 | try {
23 | $ = cheerio.load(body)
24 | } catch (err) {
25 | return res.status(500).json({ error: 'Could not parse body' })
26 | }
27 |
28 | $('table tr').each(function (key) {
29 | if (key !== 0) {
30 | const temp = {
31 | place: $(this).children('td').slice(0).html(),
32 | team: $(this).children('td').slice(1).html(),
33 | gamesPlayed: $(this).children('td').slice(2).html(),
34 | gamesWon: $(this).children('td').slice(3).html(),
35 | gamesDraw: $(this).children('td').slice(4).html(),
36 | gamesLost: $(this).children('td').slice(5).html(),
37 | goals: $(this).children('td').slice(6).html(),
38 | goalDifference: $(this).children('td').slice(7).html(),
39 | points: $(this).children('td').slice(8).html(),
40 | }
41 | stats.results.push(temp)
42 | }
43 | })
44 | return res.send(stats)
45 | })
46 | })
47 |
--------------------------------------------------------------------------------
/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/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/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/earthquake/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('earthquake/is', () => {
8 | before(() => {
9 | nock('http://hraun.vedur.is')
10 | .get('/ja/skjalftar/skjlisti.html')
11 | .reply(200, fs.readFileSync(`${__dirname}/test.fixture`))
12 | })
13 | // Which fields we expect and of which type they should be
14 | const fieldsToCheckFor = {
15 | timestamp: Date,
16 | latitude: Number,
17 | longitude: Number,
18 | depth: Number,
19 | size: Number,
20 | quality: Number,
21 | humanReadableLocation: String,
22 | }
23 |
24 | it('should return an array of objects containing correct fields', (done) => {
25 | const params = helpers.testRequestParams('/earthquake/is')
26 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
27 | request(params, resultHandler)
28 | })
29 | })
30 |
31 | describe('earthquake/is/sec', () => {
32 | before(() => {
33 | nock('http://hraun.vedur.is')
34 | .get('/ja/skjalftar/skjlisti.html')
35 | .reply(200, fs.readFileSync(`${__dirname}/test.fixture`))
36 | })
37 |
38 | // Which fields we expect and of which type they should be
39 | const fieldsToCheckFor = {
40 | timestamp: Date,
41 | latitude: Number,
42 | longitude: Number,
43 | depth: Number,
44 | size: Number,
45 | quality: Number,
46 | humanReadableLocation: String,
47 | }
48 |
49 | it('should return an array of objects containing correct fields', (done) => {
50 | const params = helpers.testRequestParams('/earthquake/is')
51 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
52 | request(params, resultHandler)
53 | })
54 | })
55 |
--------------------------------------------------------------------------------
/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/sports/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.skip('handball', () => {
6 | it('should return an array of objects containing correct fields', (done) => {
7 | const fieldsToCheckFor = ['Date', 'Time', 'Tournament', 'Venue', 'Teams']
8 | const params = helpers.testRequestParams('/sports/handball', { language: 'en' })
9 |
10 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
11 | request.get(params, resultHandler)
12 | })
13 | })
14 |
15 | describe.skip('football', () => {
16 | it('should return an array of objects containing correct fields', (done) => {
17 | const fieldsToCheckFor = ['counter', 'date', 'time', 'tournament', 'location', 'homeTeam', 'awayTeam']
18 | const params = helpers.testRequestParams('/sports/football', { language: 'en' })
19 |
20 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
21 | request.get(params, resultHandler)
22 | })
23 | })
24 |
25 | function testFootballLeague(leagueParams) {
26 | describe(`football/${leagueParams}`, () => {
27 | it('should return an array of objects containing correct fields', (done) => {
28 | const fieldsToCheckFor = ['counter', 'date', 'time', 'teams', 'location', 'scores']
29 | const params = helpers.testRequestParams(`/sports/football/${leagueParams}`, { language: 'en' })
30 |
31 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
32 | request.get(params, resultHandler)
33 | })
34 | })
35 | }
36 |
37 | testFootballLeague('male-leagues/borgun')
38 | testFootballLeague('male-leagues/pepsi')
39 | testFootballLeague('male-leagues/1st')
40 | testFootballLeague('male-leagues/2nd')
41 | testFootballLeague('male-leagues/3rd')
42 |
43 | testFootballLeague('female-leagues/borgun')
44 | testFootballLeague('female-leagues/pepsi')
45 |
--------------------------------------------------------------------------------
/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/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/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/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/hljomaholl/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-plusplus */
2 | const request = require('request')
3 | const cheerio = require('cheerio')
4 | const app = require('../../server')
5 |
6 | app.get('/hljomaholl', (req, res) => {
7 | const url = 'http://www.hljomaholl.is/vidburdir'
8 |
9 | request.get(url, (error, response, body) => {
10 | if (error || response.statusCode !== 200) {
11 | return res.status(500).json({
12 | error: 'www.hljomaholl.is refuses to respond or give back data',
13 | })
14 | }
15 |
16 | let $
17 |
18 | try {
19 | $ = cheerio.load(body)
20 | } catch (err) {
21 | return res.status(500).json({
22 | error: 'Could not parse body',
23 | })
24 | }
25 |
26 | const obj = { results: [] }
27 | const fields = [
28 | 'date',
29 | 'time',
30 | 'image',
31 | 'title',
32 | 'description',
33 | 'location',
34 | 'buyTicketURL',
35 | 'moreInfoURL',
36 | ]
37 |
38 | try {
39 | $('.main-content-body ul').find('li').each(() => {
40 | const event = {}
41 | let counter = 0
42 | $('.time', this).find('time').each((key) => {
43 | if (counter === 2) {
44 | return false
45 | }
46 |
47 | const val = $(this).text()
48 | event[fields[key]] = val
49 | counter++
50 | })
51 | event[fields[2]] = $('img', this).attr('src')
52 | event[fields[3]] = $('.time h1', this).text().trim()
53 | event[fields[4]] = $('p', this).text().trim()
54 | event[fields[5]] = $('.time h2', this).text().trim()
55 | event[fields[6]] = $('.btn-wrapper', this)
56 | .find('.btn-green').attr('href') || false
57 |
58 | const href = $('.btn-wrapper', this)
59 | .find('.btn-blue')
60 | .attr('href')
61 | event[fields[7]] = `http://www.hljomaholl.is${href}`
62 |
63 | obj.results.push(event)
64 | })
65 | } catch (err) {
66 | return res.status(500).json({
67 | error: 'Could not parse event data',
68 | })
69 | }
70 |
71 | return res.json(obj)
72 | })
73 | })
74 |
--------------------------------------------------------------------------------
/endpoints/isnic/index.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const $ = require('cheerio')
3 | const h = require('apis-helpers')
4 | const app = require('../../server')
5 |
6 | app.get('/isnic', (req, res) => {
7 | const domainName = req.query.domain || ''
8 |
9 | if (!domainName) {
10 | return res.status(431).json({ error: 'Please provide a valid domainName to lookup' })
11 | }
12 |
13 | const url = `https://www.isnic.is/en/whois/search?yt1972183=%C3%81fram&type=all&query=${domainName}`
14 |
15 | request.get({
16 | headers: { 'User-Agent': h.browser() },
17 | url,
18 | }, (error, response, body) => {
19 | if (error || response.statusCode !== 200) {
20 | return res.status(500).json({
21 | error: 'www.isnic.is refuses to respond or give back data',
22 | })
23 | }
24 |
25 | const data = $(body)
26 |
27 | const obj = {
28 | results: [],
29 | }
30 |
31 | const fields = []
32 |
33 | data.find('.miniWhois .row').each(function () {
34 | const label = $(this).find('label').text().trim()
35 | const val = $(this).text().replace(label, '').trim()
36 |
37 | fields.push({ val, label })
38 | })
39 |
40 | if (fields.length > 0) {
41 | obj.results.push({
42 | domain: (fields.find(x => x.label === 'Domain:') || { val: '' }).val,
43 | registrantname: (fields.find(x => x.label === 'Registrant name:') || { val: '' }).val,
44 | address: (fields.find(x => x.label === 'Address:') || { val: '' }).val,
45 | city: (fields.find(x => x.label === 'City/Municipality:') || { val: '' }).val,
46 | postalCode: (fields.find(x => x.label === 'Postal code:') || { val: '' }).val,
47 | country: (fields.find(x => x.label === 'Country:') || { val: '' }).val,
48 | phone: (fields.find(x => x.label === 'Phone:') || { val: '' }).val,
49 | email: (fields.find(x => x.label === 'E-mail:') || { val: '' }).val,
50 | registered: (fields.find(x => x.label === 'Registered:') || { val: '' }).val,
51 | expires: (fields.find(x => x.label === 'Expires:') || { val: '' }).val,
52 | lastChange: (fields.find(x => x.label === 'Last change:') || { val: '' }).val,
53 | })
54 | }
55 |
56 | return res.cache(86400).json(obj)
57 | })
58 | })
59 |
--------------------------------------------------------------------------------
/endpoints/lottery/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.skip('lottery', () => {
6 | describe('lotto', () => {
7 | // The only thing that changes is the form attribute, so why not just re-use the object
8 | const fieldsToCheckFor = ['date', 'lotto', 'joker', 'prize', 'link']
9 |
10 | it('should return an array of objects containing correct fields', (done) => {
11 | const params = helpers.testRequestParams('/lottery')
12 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
13 | request(params, resultHandler)
14 | })
15 | })
16 |
17 | describe('lotto', () => {
18 | // The only thing that changes is the form attribute, so why not just re-use the object
19 | const fieldsToCheckFor = ['date', 'lotto', 'joker', 'prize', 'link']
20 |
21 | it('should return an array of objects containing correct fields', (done) => {
22 | const params = helpers.testRequestParams('/lottery/lotto')
23 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
24 | request(params, resultHandler)
25 | })
26 | })
27 |
28 | describe('vikingalotto', () => {
29 | // The only thing that changes is the form attribute, so why not just re-use the object
30 | const fieldsToCheckFor = ['date', 'lotto', 'joker', 'prize', 'link']
31 |
32 | it('should return an array of objects containing correct fields', (done) => {
33 | const params = helpers.testRequestParams('/lottery/vikingalotto')
34 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
35 | request(params, resultHandler)
36 | })
37 | })
38 |
39 | describe('eurojackpot', () => {
40 | // The only thing that changes is the form attribute, so why not just re-use the object
41 | const fieldsToCheckFor = ['date', 'lotto', 'joker', 'prize', 'link']
42 |
43 | it('should return an array of objects containing correct fields', (done) => {
44 | const params = helpers.testRequestParams('/lottery/eurojackpot')
45 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
46 | request(params, resultHandler)
47 | })
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/endpoints/tv/skjarinn.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-plusplus */
2 | /* eslint-disable prefer-destructuring */
3 | const request = require('request')
4 | const moment = require('moment')
5 | const xml2js = require('xml2js')
6 | const h = require('apis-helpers')
7 | const app = require('../../server')
8 |
9 | const parseString = xml2js.parseString
10 |
11 | /* Parse feed from Skjarinn */
12 | const parseSkjar1 = function (callback, data) {
13 | parseString(data, (err, result, title) => {
14 | if (err) throw new Error(`Parsing of XML failed. Title ${title}`)
15 |
16 | const schedule = []
17 |
18 | for (let i = 0; i < result.schedule.service[0].event.length; ++i) {
19 | const event = result.schedule.service[0].event[i]
20 | if (moment().add('d', 1).startOf('day').hour(6) > moment(event.$['start-time'])) {
21 | schedule.push({
22 | title: event.title[0],
23 | originalTitle: event['original-title'][0],
24 | duration: event.$.duration,
25 | description: event.description[0],
26 | shortDescription: event['short-description'][0],
27 | live: event.live[0] === 'yes',
28 | premier: event.rerun[0] === 'yes',
29 | startTime: event.$['start-time'],
30 | aspectRatio: event['aspect-ratio'][0].size[0],
31 | series: {
32 | episode: event.episode[0].$.number,
33 | series: event.episode[0].$['number-of-episodes'],
34 | },
35 | })
36 | }
37 | }
38 | return callback(schedule)
39 | })
40 | }
41 |
42 | /* Skjar 1 */
43 | app.get('/tv/:var(skjar1|skjareinn)', (req, res) => {
44 | res.status(503).json({ error: 'Source page has changed. Scraping needs to be re-implemented' })
45 | return
46 | /* eslint-disable */
47 | const url = 'http://www.skjarinn.is/einn/dagskrarupplysingar/?channel_id=7&output_format=xml'
48 |
49 | request.get({
50 | headers: { 'User-Agent': h.browser() },
51 | url: url
52 | }, function (error, response, body) {
53 | if (error || response.statusCode !== 200) {
54 | return res.status(504).json({ error:'skjarinn.is is not responding with the right data' })
55 | }
56 |
57 | parseSkjar1(function (data) {
58 | res.cache(1800).json(200, {
59 | results: data
60 | })
61 | }, body)
62 | })
63 | })
64 |
--------------------------------------------------------------------------------
/endpoints/lottery/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/first */
2 | const request = require('request')
3 | const cheerio = require('cheerio')
4 | const app = require('../../server')
5 |
6 | const parseList = function (body) {
7 | let $
8 | try {
9 | $ = cheerio.load(body)
10 | } catch (error) {
11 | throw new Error('Could not parse body')
12 | }
13 |
14 | const results = []
15 |
16 | const tr = $('table').eq(1).find('tr')
17 |
18 | tr.each(function (i) {
19 | if (i === 0) return
20 | const td = $(this).find('td')
21 |
22 | results.push({
23 | date: td.eq(0).text().trim(),
24 | lotto: td.eq(1).html().match(/\d{1,2}/g).join(' ')
25 | .replace(/(\d{1,2})$/, '($1)'),
26 | joker: td.eq(2).text().trim(),
27 | prize: td.eq(3).text().trim(),
28 | link: `http://lotto.is${td.eq(4).find('a').attr('href').trim()}`,
29 | })
30 | })
31 |
32 | return results
33 | }
34 |
35 | const getLottery = function (callback, providedUrl) {
36 | const url = providedUrl || 'https://igvefur.lotto.is/lottoleikir/urslit/lotto/'
37 |
38 | const params = { url }
39 |
40 | request(params, (error, res, body) => {
41 | if (error) return callback(error)
42 |
43 | if (res.statusCode !== 200) {
44 | return callback(new Error(`HTTP error from endpoint, status code ${res.statusCode}`))
45 | }
46 |
47 | return callback(null, body)
48 | })
49 | }
50 |
51 | const getLotto = (req, res, next) => {
52 | getLottery((err, body) => {
53 | if (err) {
54 | return next(502)
55 | }
56 | return res.cache(3600).json({
57 | results: parseList(body),
58 | })
59 | })
60 | }
61 |
62 | app.get('/lottery', getLotto)
63 | app.get('/lottery/lotto', getLotto)
64 |
65 | app.get('/lottery/vikingalotto', (req, res, next) => {
66 | getLottery((err, body) => {
67 | if (err) {
68 | return next(502)
69 | }
70 | return res.cache(3600).json({
71 | results: parseList(body),
72 | })
73 | }, 'https://games.lotto.is/lottoleikir/urslit/vikingalotto/')
74 | })
75 | app.get('/lottery/eurojackpot', (req, res, next) => {
76 | getLottery((err, body) => {
77 | if (err) {
78 | return next(502)
79 | }
80 | return res.cache(3600).json({
81 | results: parseList(body),
82 | })
83 | }, 'https://games.lotto.is/lottoleikir/urslit/eurojackpot/')
84 | })
85 |
--------------------------------------------------------------------------------
/endpoints/rides/index.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const cheerio = require('cheerio')
3 | const app = require('../../server')
4 |
5 | /* Root Rides */
6 | app.get('/rides', (req, res) => {
7 | return res.json({
8 | results: [
9 | {
10 | info: 'This is an api for requests for rides/passengers in Iceland',
11 | endpoints: {
12 | 'samferda-drivers': '/rides/samferda-drivers/',
13 | 'samferda-passengers': '/rides/samferda-passengers/',
14 | },
15 | },
16 | ],
17 | })
18 | })
19 |
20 | const scrapeSamferdaFor = (requesting, res) => {
21 | const url = 'http://www.samferda.net/'
22 |
23 | request(url, (error, response, body) => {
24 | if (error) {
25 | return res.status(500).json({ error: `${url} not responding correctly...` })
26 | }
27 |
28 | // Cheerio declared and then attemted to load.
29 | let $
30 | try {
31 | $ = cheerio.load(body)
32 | } catch (e) {
33 | return res.status(500).json({ error: 'Could not load the body with cherrio.' })
34 | }
35 |
36 | // Base object to be added to
37 | // and eventually sent as a JSON response.
38 | const results = []
39 |
40 | $('table:has(thead) > tbody > tr[bgcolor]').each(function () {
41 | const cols = $(this).children()
42 | const rowType = cols.eq(1).text().trim()
43 |
44 | if (rowType === requesting) {
45 | results.push({
46 | link: cols.eq(0).find('a').attr('href'),
47 | from: cols.eq(2).text().trim(),
48 | to: cols.eq(3).text().trim(),
49 | // convert to YYYY-MM-DD for easier sorting
50 | date: cols.eq(4).text().trim().split('.')
51 | .reverse()
52 | .join('-'),
53 | time: cols.eq(5).text().trim(),
54 | })
55 | }
56 | })
57 |
58 | return res.cache(1800).json({ results })
59 | })
60 | }
61 |
62 | /**
63 | * Fetches list of Passenger requests on Samferda.net.
64 | * response - JSON: Passenger requests within an 'results' array.
65 | */
66 | app.get('/rides/samferda-drivers', (req, res) => {
67 | scrapeSamferdaFor('Passengers', res)
68 | })
69 | /**
70 | * Fetches list of Ride requests on Samferda.net.
71 | * response - JSON: Ride requests within an 'results' array.
72 | */
73 | app.get('/rides/samferda-passengers', (req, res) => {
74 | scrapeSamferdaFor('Ride', res)
75 | })
76 |
--------------------------------------------------------------------------------
/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/tv/ruv.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-plusplus */
2 | /* eslint-disable no-prototype-builtins */
3 | /* eslint-disable prefer-destructuring */
4 | const request = require('request')
5 | const moment = require('moment')
6 | const xml2js = require('xml2js')
7 | const h = require('apis-helpers')
8 | const app = require('../../server')
9 |
10 | const parseString = xml2js.parseString
11 |
12 | /* Parse feeds from RUV */
13 | const parseFeed = function (callback, data) {
14 | parseString(data, (err, result, title) => {
15 | if (err) return callback(new Error(`Parsing of XML failed. Title ${title}`))
16 |
17 | const schedule = []
18 |
19 | if (result.schedule.error || !result.schedule.service[0].hasOwnProperty('event')) {
20 | return (callback(schedule))
21 | }
22 |
23 | for (let i = 0; i < result.schedule.service[0].event.length; ++i) {
24 | const event = result.schedule.service[0].event[i]
25 | schedule.push({
26 | title: event.title[0],
27 | originalTitle: event['original-title'][0],
28 | duration: event.$.duration,
29 | description: event.description[0],
30 | shortDescription: event['short-description'][0],
31 | live: event.live[0] === 'yes',
32 | premier: event.rerun[0] === 'yes',
33 | startTime: event.$['start-time'],
34 | aspectRatio: event['aspect-ratio'][0].size[0],
35 | series: {
36 | episode: event.episode[0].$.number,
37 | series: event.episode[0].$['number-of-episodes'],
38 | },
39 | })
40 | }
41 | return callback(null, schedule)
42 | })
43 | }
44 |
45 | const getFeed = function (url, callback) {
46 | request.get({
47 | headers: { 'User-Agent': h.browser() },
48 | url,
49 | }, (error, response, body) => {
50 | if (error) return callback(new Error(`${url} did not respond`))
51 |
52 | parseFeed(callback, body)
53 | })
54 | }
55 |
56 | const serve = function (url, res, next) {
57 | getFeed(url, (err, data) => {
58 | if (err) {
59 | console.error(err)
60 | return next(502)
61 | }
62 |
63 | res.cache(1800).json({ results: data })
64 | })
65 | }
66 |
67 | /* RUV */
68 | app.get('/tv/ruv', (req, res, next) => {
69 | let url = 'http://muninn.ruv.is/files/xml/ruv/'
70 |
71 | if (req.params.date) {
72 | if (moment(req.params.date).isValid()) {
73 | const date = moment(req.params.date)
74 | // Example : http://muninn.ruv.is/files/xml/ruv/2013-06-11/
75 | url += date.format('YYYY-MM-DD')
76 | }
77 | }
78 |
79 | serve(url, res, next)
80 | })
81 |
82 | /* RUV Ithrottir */
83 | app.get('/tv/ruvithrottir', (req, res, next) => {
84 | const url = 'http://muninn.ruv.is/files/xml/ruvithrottir/'
85 | serve(url, res, next)
86 | })
87 |
--------------------------------------------------------------------------------
/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/golf/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-destructuring */
2 | const { parse: parseUrl } = require('url')
3 | const request = require('request')
4 | const cheerio = require('cheerio')
5 | const h = require('apis-helpers')
6 | const _ = require('lodash')
7 | const app = require('../../server')
8 |
9 | app.get('/golf/teetimes', (req, res) => {
10 | const clubId = req.query.club
11 | if (!clubId) {
12 | return res.status(500).json({
13 | error: 'Please provide a valid club id to lookup',
14 | })
15 | }
16 |
17 | request.get({
18 | // http://stackoverflow.com/a/20091919
19 | rejectUnauthorized: false,
20 | headers: { 'User-Agent': h.browser() },
21 | url: `http://mitt.golf.is/pages/rastimar/rastimayfirlit/?club=${clubId}`,
22 | }, (err, response, html) => {
23 | if (err || response.statusCode !== 200) {
24 | return res.status(500).json({
25 | error: 'mitt.golf.is refuses to respond or give back data',
26 | })
27 | }
28 | const $ = cheerio.load(html)
29 | const rows = $('table.teeTimeTable tbody').children()
30 | let time = ''
31 | return res.cache().json({
32 | results: _.map(rows, (row) => {
33 | const $row = $(row)
34 | time = $row.children('td.time').html() === null ? time : $row.children('td.time').html()
35 | return {
36 | time,
37 | name: $($row.children('td.name')).html(),
38 | club: $($row.children('td.club')).html(),
39 | handicap: $($row.children('td.handicap')).html(),
40 | }
41 | }),
42 | })
43 | })
44 | })
45 |
46 | app.get('/golf/clubs', (req, res) => {
47 | request.get({
48 | // http://stackoverflow.com/a/20091919
49 | rejectUnauthorized: false,
50 | headers: { 'User-Agent': h.browser() },
51 | url: 'http://mitt.golf.is/pages/rastimar/',
52 | }, (err, response, html) => {
53 | if (err || response.statusCode !== 200) {
54 | return res.status(500).json({
55 | error: 'mitt.golf.is refuses to respond or give back data',
56 | })
57 | }
58 |
59 | const $ = cheerio.load(html)
60 | // Skip the first element.
61 | const rows = $('table.golfTable tr').slice(2)
62 | return res.cache(3600).json({
63 | results: _.map(rows, (row) => {
64 | const $row = $(row)
65 | const url = (
66 | $row.children('td.club').children('a').attr('href')
67 | )
68 |
69 | const query = parseUrl(url, true).query
70 |
71 | return {
72 | abbreviation: $row.children('td.abbreviation').html(),
73 | club: {
74 | id: query.club,
75 | name: $row.children('td.club').children('a').html(),
76 | },
77 | location: $row.children('td.location').html(),
78 | }
79 | }),
80 | })
81 | })
82 | })
83 |
--------------------------------------------------------------------------------
/endpoints/flight/index.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('/flight', (req, res) => {
8 | const data = req.query
9 | let url = ''
10 | let $
11 |
12 | if (!data.type) data.type = ''
13 | if (!data.language) data.language = ''
14 |
15 | if (data.type === 'departures' && data.language === 'is') {
16 | url = 'http://www.kefairport.is/Flugaaetlun/Brottfarir/'
17 | } else if (data.type === 'departures' && data.language === 'en') {
18 | url = 'http://www.kefairport.is/English/Timetables/Departures/'
19 | } else if (data.type === 'arrivals' && data.language === 'is') {
20 | url = 'http://www.kefairport.is/Flugaaetlun/Komur/'
21 | } else if (data.type === 'arrivals' && data.language === 'en') {
22 | url = 'http://www.kefairport.is/English/Timetables/Arrivals/'
23 | } else {
24 | url = 'http://www.kefairport.is/English/Timetables/Arrivals/'
25 | }
26 |
27 | request.get({
28 | headers: { 'User-Agent': h.browser() },
29 | url,
30 | }, (error, response, body) => {
31 | if (error || response.statusCode !== 200) {
32 | return res.status(500).json({ error: 'www.kefairport.is refuses to respond or give back data' })
33 | }
34 |
35 | try {
36 | $ = cheerio.load(body)
37 | } catch (err) {
38 | return res.status(500).json({ error: 'Could not parse body' })
39 | }
40 |
41 | const obj = { results: [] }
42 |
43 | $('table tr').each(function (key) {
44 | if (key !== 0) {
45 | let flight = {}
46 | if (data.type === 'departures') {
47 | flight = {
48 | date: $(this).children('td').slice(0).html(),
49 | flightNumber: $(this).children('td').slice(1).html(),
50 | airline: $(this).children('td').slice(2).html(),
51 | to: $(this).children('td').slice(3).html(),
52 | plannedArrival: $(this).children('td').slice(4).html(),
53 | realArrival: $(this).children('td').slice(5).html(),
54 | status: $(this).children('td').slice(6).html(),
55 | }
56 | } else {
57 | flight = {
58 | date: $(this).children('td').slice(0).html(),
59 | flightNumber: $(this).children('td').slice(1).html(),
60 | airline: $(this).children('td').slice(2).html(),
61 | from: $(this).children('td').slice(3).html(),
62 | plannedArrival: $(this).children('td').slice(4).html(),
63 | realArrival: $(this).children('td').slice(5).html(),
64 | status: $(this).children('td').slice(6).html(),
65 | }
66 | }
67 |
68 | obj.results.push(flight)
69 | }
70 | })
71 |
72 | return res.cache(3600).json(obj)
73 | })
74 | })
75 |
--------------------------------------------------------------------------------
/endpoints/tv/365.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-plusplus */
2 | /* eslint-disable prefer-destructuring */
3 | const request = require('request')
4 | const xml2js = require('xml2js')
5 | const h = require('apis-helpers')
6 | const app = require('../../server')
7 |
8 | const parseString = xml2js.parseString
9 |
10 | /* Parse feeds from 365 midlar */
11 | const parseFeed = function (callback, data) {
12 | parseString(data, (err, result, title) => {
13 | if (err) return callback(new Error(`Parsing of XML failed. Title ${title}`))
14 |
15 | const schedule = []
16 |
17 | for (let i = 0; i < result.schedule.event.length; ++i) {
18 | const event = result.schedule.event[i]
19 |
20 | schedule.push({
21 | title: event.title[0],
22 | originalTitle: event.org_title[0],
23 | duration: event.$.duration,
24 | description: event.description[0],
25 | live: event.live[0].$.value === 'true',
26 | premier: event.premier[0].$.value === 'true',
27 | startTime: event.$.starttime,
28 | aspectRatio: event.aspectratio[0].$.value,
29 | series: {
30 | episode: event.series ? event.series[0].$.episode : '',
31 | series: event.series ? event.series[0].$.series : '',
32 | },
33 | })
34 | }
35 | return callback(null, schedule)
36 | })
37 | }
38 |
39 | const getFeed = function (url, callback) {
40 | request.get({
41 | headers: { 'User-Agent': h.browser() },
42 | url,
43 | }, (error, response, body) => {
44 | if (error) return callback(new Error(`${url} did not respond`))
45 |
46 | parseFeed(callback, body)
47 | })
48 | }
49 |
50 | const serve = function (url, res, next) {
51 | getFeed(url, (err, data) => {
52 | if (err) {
53 | console.error(err)
54 | return next(502)
55 | }
56 |
57 | res.cache(1800).json({
58 | results: data,
59 | })
60 | })
61 | }
62 |
63 | /* Stod 2 */
64 | app.get('/tv/stod2', (req, res, next) => {
65 | const url = 'http://stod2.is/XML--dagskrar-feed/XML-Stod-2-dagurinn'
66 | serve(url, res, next)
67 | })
68 |
69 | /* Stod 2 Sport */
70 | app.get('/tv/stod2sport', (req, res, next) => {
71 | const url = 'http://www.stod2.is/XML--dagskrar-feed/XML-Stod-2-Sport-dagurinn'
72 | serve(url, res, next)
73 | })
74 |
75 | /* Stod 2 Sport 2 */
76 | app.get('/tv/stod2sport2', (req, res, next) => {
77 | const url = 'http://www.stod2.is/XML--dagskrar-feed/XML-Stod-2-Sport-2-dagurinn'
78 | serve(url, res, next)
79 | })
80 |
81 | /* Stod 3 */
82 | app.get('/tv/stod3', (req, res, next) => {
83 | const url = 'http://www.stod2.is/XML--dagskrar-feed/XML-Stod-3-dagurinn'
84 | serve(url, res, next)
85 | })
86 |
87 | /* Stod 2 Bio */
88 | app.get('/tv/stod2bio', (req, res, next) => {
89 | const url = 'http://www.stod2.is/XML--dagskrar-feed/XML-Stod-2-Bio-dagurinn'
90 | serve(url, res, next)
91 | })
92 |
--------------------------------------------------------------------------------
/lib/test_helpers.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable prefer-destructuring */
2 | /* eslint-disable no-prototype-builtins */
3 | /* eslint-disable no-restricted-syntax */
4 | const assert = require('assert')
5 |
6 | function assertResults(json, canBeEmpty) {
7 | assert(json.results, 'Does not contain a \'results\' field')
8 | if (!canBeEmpty) {
9 | assert(json.results.length > 0, 'Results are empty')
10 | }
11 | }
12 |
13 | function assertPresenceOfFields(fields, arr) {
14 | arr.forEach((result, i) => {
15 | fields.forEach((field) => {
16 | const fieldExists = result[field] !== undefined
17 | assert(fieldExists, `Missing field ${field} in result #${i}`)
18 | })
19 | })
20 | }
21 |
22 | /*
23 | Asserts that all fields provided are of the expected type (Date is a bit
24 | messy, since it will be a string until we actually try to parse it as a Date).
25 | */
26 | function assertTypesOfFields(fields, arr) {
27 | arr.forEach((result) => {
28 | for (const key in fields) {
29 | if (fields.hasOwnProperty(key)) {
30 | const type = fields[key]
31 | const value = result[key]
32 | let constructor = value.constructor
33 | if (type === Date && !Number.isNaN(Date.parse(value))) {
34 | constructor = Date
35 | }
36 | assert(
37 | constructor === type,
38 | `Field ${key} should be ${type.name}, but is ${constructor.name}`
39 | )
40 | }
41 | }
42 | })
43 | }
44 |
45 | // always returns the same fields, so we'll just reuse this function for both cases
46 | // (I may be going a bit overboard on this)
47 | exports.testRequestHandlerForFields = (done, fieldsToCheckFor, customCallback, canBeEmpty) => {
48 | return (err, res, body) => {
49 | if (err) throw err
50 | let json
51 | try {
52 | json = JSON.parse(body)
53 | } catch (e) {
54 | throw e
55 | }
56 |
57 | // Check for the presence of the results property
58 | assertResults(json, canBeEmpty)
59 |
60 | const fieldsIsObject = fieldsToCheckFor.constructor === Object
61 | const fields = fieldsIsObject ? Object.keys(fieldsToCheckFor) : fieldsToCheckFor
62 |
63 | if (!canBeEmpty) {
64 | // Check for the presence of all expected fields
65 | assertPresenceOfFields(fields, json.results)
66 | }
67 |
68 | if (fieldsIsObject) {
69 | assertTypesOfFields(fieldsToCheckFor, json.results)
70 | }
71 |
72 | if (customCallback) {
73 | customCallback(json)
74 | }
75 |
76 | done()
77 | }
78 | }
79 | // Generate http request params for a particular endpoint
80 | exports.testRequestParams = (path, form) => {
81 | return {
82 | url: `http://localhost:3101${path}`,
83 | method: 'GET',
84 | qs: form,
85 | headers: ['Content-Type: application/json'],
86 | }
87 | }
88 | exports.assertPresenceOfFields = assertPresenceOfFields
89 | exports.assertTypesOfFields = assertTypesOfFields
90 |
--------------------------------------------------------------------------------
/endpoints/weather/documentation.md:
--------------------------------------------------------------------------------
1 | # Icelandic Weather
2 |
3 | Source: [Icelandic Meteorological Office](http://vedur.is)
4 |
5 | - GET [/weather/:source/:lang](https://apis.is/weather/:source/:lang)
6 |
7 | Get weather information for Iceland.
8 | Under 'Descriptions' you will find a list that will help you understand the output of the following endpoints.
9 |
10 | | Parameters | Description | Example |
11 | |------------------|--------------------------------------|------------------------------------------------|
12 | | :type (required) | Type of information to get | [forecasts](https://apis.is/weather/forecasts) |
13 | | :lang | Language of output, defaults to 'is' | [en](https://apis.is/weather/forecasts/en) |
14 |
15 | - GET [/weather/forecasts/:lang](https://apis.is/weather/forecasts/:lang)
16 |
17 | | Parameters | Description | Example |
18 | |---------------------|--------------------------------------|------------------------------------------------|
19 | | :lang | Language of output, defaults to 'is' | [en](https://apis.is/weather/forecasts/en) |
20 | | stations (required) | List of station numbers seperated by commas(,) or semicolons(;). See links below for more information. [Weather stations (icelandic)](http://www.vedur.is/vedur/stodvar) & [Weather stations (english)](http://en.vedur.is/weather/stations/) | |
21 |
22 | - GET [/weather/observations/:lang](https://apis.is/weather/observations/:lang)
23 |
24 | | Parameters | Description | Example |
25 | |---------------------|--------------------------------------|------------------------------------------------|
26 | | :lang | Language of output, defaults to 'is' | [en](https://apis.is/weather/forecasts/en) |
27 | | stations (required) | List of station numbers seperated by commas(,) or semicolons(;). See links below for more information. [Weather stations (icelandic)](http://www.vedur.is/vedur/stodvar) & [Weather stations (english)](http://en.vedur.is/weather/stations/) | |
28 | | time | 1h (default) = Fetch data from automatic weather stations that are updated on the hour. 3h = Only fetch mixed data from manned and automatic weather stations that is updated every 3 hours. | |
29 | | anytime | 0 (default) = an error will be returned if current data is not available. 1 = last available numbers will be displayed, regardless of date. | |
30 |
31 | - GET [/weather/texts](https://apis.is/weather/texts)
32 |
33 | | Parameters | Description |
34 | |------------------|--------------------------------------|
35 | | types (required) | List of types seperated by commas(,) or semicolons(;). See 'Valid types' below for full list of valid type numbers and what they stand for. |
36 |
37 | ---
38 |
--------------------------------------------------------------------------------
/endpoints/petrol/index.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const $ = require('cheerio')
3 | const h = require('apis-helpers')
4 | const app = require('../../server')
5 |
6 | function queryData(callback) {
7 | const url = 'https://raw.githubusercontent.com/gasvaktin/gasvaktin/master/vaktin/gas.min.json'
8 | const headers = {
9 | Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
10 | 'Accept-Language': 'en-US,en;q=0.8,is;q=0.6',
11 | 'Cache-Control': 'max-age=0',
12 | 'Content-Type': 'application/x-www-form-urlencoded',
13 | 'User-Agent': h.browser(),
14 | }
15 | request.get({
16 | headers,
17 | url,
18 | }, (error, response, body) => {
19 | if (error || response.statusCode !== 200) {
20 | callback(error, response, '')
21 | }
22 | callback(error, response, body)
23 | })
24 | }
25 |
26 | function queryTimestamps(callback) {
27 | const url = 'https://gist.github.com/gasvaktin'
28 | const headers = {
29 | Accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
30 | 'Accept-Language': 'en-US,en;q=0.8,is;q=0.6',
31 | 'Cache-Control': 'no-cache',
32 | Connection: 'keep-alive',
33 | Host: 'gist.github.com',
34 | Pragma: 'no-cache',
35 | 'Upgrade-Insecure-Requests': 1,
36 | 'User-Agent': h.browser(),
37 | }
38 | request.get({
39 | headers,
40 | url,
41 | }, (error, response, body) => {
42 | if (error || response.statusCode !== 200) {
43 | callback(error, response, '')
44 | }
45 | callback(error, response, body)
46 | })
47 | }
48 |
49 | function parseTimestamps(htmlText) {
50 | const data = $(htmlText)
51 | return {
52 | lastPriceChanges: data.find('#file-prices_changed_timestamp-LC1').text().slice(0, 23),
53 | lastPriceCheck: data.find('#file-prices_lookup_timestamp-LC1').text().slice(0, 23),
54 | }
55 | }
56 |
57 | app.get('/petrol', (req, res) => {
58 | const timestampApis = (new Date().toISOString().slice(0, 23))
59 | const nineMinutes = 900
60 | queryData((error1, response1, jsonText) => {
61 | if (error1 || response1.statusCode !== 200) {
62 | return res.status(500).json({
63 | error: 'raw.githubusercontent.com refuses to respond or give back data',
64 | })
65 | }
66 | const results = JSON.parse(jsonText).stations
67 | queryTimestamps((error2, response2, htmlText) => {
68 | if (error2 || response2.statusCode !== 200) {
69 | return res.cache(nineMinutes).json({
70 | results,
71 | timestampApis,
72 | timestampPriceChanges: null,
73 | timestampPriceCheck: null,
74 | })
75 | }
76 | const timestamps = parseTimestamps(htmlText)
77 | return res.cache(nineMinutes).json({
78 | results,
79 | timestampApis,
80 | timestampPriceChanges: timestamps.lastPriceChanges,
81 | timestampPriceCheck: timestamps.lastPriceCheck,
82 | })
83 | })
84 | })
85 | })
86 |
--------------------------------------------------------------------------------
/endpoints/sarschool/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/first */
2 | const request = require('request')
3 | const cheerio = require('cheerio')
4 | const app = require('../../server')
5 |
6 | function pad(n) {
7 | return n < 10 ? `0${n}` : n
8 | }
9 |
10 | const getRequest = (callback, providedUrl) => {
11 | const url = providedUrl || 'http://skoli.landsbjorg.is/Open/Seminars.aspx?'
12 |
13 | const params = { url }
14 |
15 | request(params, (error, res, body) => {
16 | if (error) throw new Error(error)
17 |
18 | if (res.statusCode !== 200) {
19 | throw new Error(`HTTP error from endpoint, status code ${res.statusCode}`)
20 | }
21 |
22 | return callback(body)
23 | })
24 | }
25 |
26 | const parseList = (body) => {
27 | let $
28 | try {
29 | $ = cheerio.load(body)
30 | } catch (error) {
31 | throw new Error('Could not parse body')
32 | }
33 |
34 | const results = []
35 |
36 | const tr = $('.rgMasterTable').find('tbody').find('tr')
37 |
38 | tr.each(() => {
39 | const td = $(this).find('td')
40 |
41 | // Change start time from d.m.YYYY to YYYY-mm-dd
42 | const startDate = td.eq(6).text().trim()
43 | let startDateFinal
44 | let sdSplit
45 | let sd
46 | if (startDate === '') {
47 | startDateFinal = 'n/a'
48 | } else {
49 | sdSplit = startDate.split('.')
50 | sd = new Date(sdSplit[2], sdSplit[1], sdSplit[0])
51 | startDateFinal = `${sd.getFullYear()}-${pad(sd.getMonth())}-${pad(sd.getDate())}`
52 | }
53 |
54 | // Change end time from d.m.YYYY to YYYY-mm-dd
55 | const endDate = td.eq(7).text().trim()
56 | let endDateFinal
57 | let edSplit
58 | let ed
59 | if (endDate === '') {
60 | endDateFinal = 'n/a'
61 | } else {
62 | edSplit = endDate.split('.')
63 | ed = new Date(edSplit[2], edSplit[1], edSplit[0])
64 | endDateFinal = `${ed.getFullYear()}-${pad(ed.getMonth())}-${pad(ed.getDate())}`
65 | }
66 |
67 | results.push({
68 | id: (td.eq(3).text().trim() === '' ? '' : parseFloat(td.eq(3).text().trim())),
69 | name: (td.eq(4).text().trim() === '' ? '' : td.eq(4).text().trim()),
70 | time_start: startDateFinal,
71 | time_end: endDateFinal,
72 | sar_members_only: (td.eq(0).find('img').length > 0 ? 1 : 0),
73 | host: (td.eq(5).find('input').attr('checked') === 'checked' ? 'Squad' : 'Other'),
74 | location: (td.eq(8).text().trim() === '' ? '' : td.eq(8).text().trim()),
75 | price_regular: (td.eq(9).text().trim() === '' ? '' : parseFloat(td.eq(9).text().trim().replace('.', ''))),
76 | price_members: (td.eq(10).text().trim() === '' ? '' : parseFloat(td.eq(10).text().trim().replace('.', ''))),
77 | link: `http://skoli.landsbjorg.is/Open/Course.aspx?Id=${td.eq(3).text().trim()}`,
78 | })
79 | })
80 |
81 | return results
82 | }
83 |
84 | app.get('/sarschool', (req, res) => {
85 | getRequest((body) => {
86 | return res.cache().json({
87 | results: parseList(body),
88 | })
89 | })
90 | })
91 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "apis.is",
3 | "version": "0.3.0",
4 | "main": "server.js",
5 | "author": {
6 | "name": "Kristján Ingi Mikaelsson",
7 | "email": "kristjanmik@gmail.com"
8 | },
9 | "engines": {
10 | "node": ">=8.6.0"
11 | },
12 | "xo": {
13 | "envs": [
14 | "node",
15 | "mocha"
16 | ],
17 | "semicolon": false,
18 | "space": true,
19 | "rules": {
20 | "object-curly-spacing": "off",
21 | "arrow-parens": "off",
22 | "comma-dangle": "off",
23 | "camelcase": "off",
24 | "curly": "off",
25 | "capitalized-comments": "off",
26 | "unicorn/catch-error-name": "off",
27 | "unicorn/filename-case": "off",
28 | "brace-style": "off",
29 | "max-params": "off",
30 | "no-implicit-coercion": "off",
31 | "max-nested-callbacks": "off",
32 | "handle-callback-err": "off",
33 | "no-warning-comments": "off",
34 | "no-negated-condition": "off",
35 | "unicorn/prefer-type-error": "off",
36 | "unicorn/no-abusive-eslint-disable": "off"
37 | }
38 | },
39 | "description": "A json wrapper around publicly open data in Iceland",
40 | "licence": "MIT",
41 | "repository": {
42 | "type": "git",
43 | "url": "https://github.com/kristjanmik/apis.git"
44 | },
45 | "contributors": [
46 | {
47 | "name": "Arnór Heiðar Sigurðsson",
48 | "email": "arnorhs@gmail.com"
49 | },
50 | {
51 | "name": "Ragnar Þór Valgeirsson (rthor)",
52 | "email": "ragnar.valgeirsson@gmail.com",
53 | "url": "http://rthor.is"
54 | },
55 | {
56 | "name": "Baldur Már Helgason",
57 | "email": "baldur.helgason@gmail.com"
58 | },
59 | {
60 | "name": "Benedikt D Valdez (benediktvaldez)",
61 | "email": "benediktvaldez@gmail.com",
62 | "url": "http://bdvs.info"
63 | },
64 | {
65 | "name": "Kristján Oddsson (koddsson)",
66 | "email": "koddsson@gmail.com",
67 | "url": "https://koddsson.com"
68 | },
69 | {
70 | "name": "Alexander Baldvin",
71 | "email": "alexanderbaldvin@gmail.com"
72 | }
73 | ],
74 | "dependencies": {
75 | "apis-helpers": "0.0.1",
76 | "cheerio": "0.22.0",
77 | "debug": "3.1.0",
78 | "expect": "22.4.3",
79 | "express": "^4.16.3",
80 | "file": "~0.2.2",
81 | "fridagar": "3.0.0",
82 | "globby": "8.0.1",
83 | "graphql": "0.13.2",
84 | "iconv-lite": "0.4.21",
85 | "isn2wgs": "0.0.2",
86 | "lodash": "^4.17.5",
87 | "marked": "github:koddsson/marked#escape-markdown-option",
88 | "mocha": "5.1.1",
89 | "moment": "2.22.1",
90 | "nock": "^9.2.3",
91 | "raven": "2.4.2",
92 | "redis": "^2.8.0",
93 | "request": "2.85.0",
94 | "scraper": "0.0.9",
95 | "statuses": "^1.4.0",
96 | "xml2js": "0.4.19",
97 | "xregexp": "4.1.1"
98 | },
99 | "scripts": {
100 | "dev": "nodemon server.js",
101 | "start": "DEBUG=server node server.js",
102 | "test": "NODE_ENV=test mocha --exit --timeout 10000 --reporter=dot ./lib/*.spec.js ./test.js ./endpoints/*/tests/integration_test.js",
103 | "coveralls": "exit 0",
104 | "lint": "xo"
105 | },
106 | "devDependencies": {
107 | "nodemon": "1.17.3",
108 | "sinon": "5.0.3",
109 | "xo": "^0.21.0"
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-restricted-globals */
2 | /* eslint-disable import/no-dynamic-require */
3 | /* eslint-disable global-require */
4 | /* eslint-disable prefer-destructuring */
5 | /* eslint-disable import/first */
6 | /* eslint-disable new-cap */
7 |
8 | const { EventEmitter: EE } = require('events')
9 | const express = require('express')
10 |
11 | const fileModule = require('file')
12 | const statuses = require('statuses')
13 | const debug = require('debug')('server')
14 | const Raven = require('raven')
15 | const metricsMiddleware = require('./lib/expressMetrics')
16 |
17 | const cache = require('./lib/cache')
18 | const cors = require('./lib/cors')
19 |
20 | const app = express()
21 |
22 | // Set up error tracking with Sentry
23 | const SENTRY_URL = process.env.SENTRY_URL
24 | const redis = require('./lib/redis')
25 |
26 | if (SENTRY_URL !== undefined && SENTRY_URL !== '') {
27 | Raven.config(SENTRY_URL).install()
28 |
29 | // The request handler must be the first middleware on the app
30 | app.use(Raven.requestHandler())
31 |
32 | // The error handler must be before any other error middleware
33 | app.use(Raven.errorHandler())
34 | }
35 |
36 | if (process.env.NODE_ENV !== 'test') {
37 | app.use(metricsMiddleware())
38 | app.get('/metrics', (req, res) => {
39 | redis.HGETALL('metrics', (error, metrics) => {
40 | res.json(metrics)
41 | })
42 | })
43 | }
44 |
45 | module.exports = app
46 |
47 | /**
48 | * Set the spacing to 0 for shorter output
49 | */
50 | app.set('json spaces', 0)
51 |
52 | /**
53 | * Create an event listener for app
54 | */
55 | EE.call(app)
56 |
57 | /**
58 | * Cross-origin resource sharing
59 | */
60 | app.use(cors())
61 |
62 | /**
63 | * Caching layer
64 | */
65 | app.use(cache())
66 |
67 | /**
68 | * Set up endpoints
69 | */
70 | fileModule.walkSync('./endpoints', (dirPath, dirs, endpoints) => {
71 | function requireEndpoint(endpoint) {
72 | if (endpoint.includes('.js') && endpoint !== 'graphql_schema.js') {
73 | try {
74 | require(`./${dirPath}/${endpoint}`)
75 | } catch (e) {
76 | console.error('Error loading file', e)
77 | }
78 | }
79 | }
80 |
81 | if (endpoints && dirPath.indexOf('test') < 0) {
82 | endpoints.forEach(requireEndpoint)
83 | }
84 | })
85 |
86 | app.use((error, req, res, next) => {
87 | let code = 500
88 | let message = 'Unknown error'
89 |
90 | if (res.headersSent) {
91 | console.error('Headers already sent')
92 | return next()
93 | }
94 |
95 | if (typeof error === 'number') {
96 | code = error
97 | message = statuses[code] || message
98 | } else if (
99 | typeof error === 'object' &&
100 | error.message &&
101 | error.message.length === 3 &&
102 | !isNaN(error.message)) {
103 | code = error.message
104 | message = statuses[code] || message
105 | } else {
106 | // Other errors that might have been swallowed
107 | debug(error.stack)
108 | message = error.message
109 | }
110 |
111 | code = parseInt(code, 10)
112 |
113 | res.status(code).json({ error: message })
114 | })
115 |
116 | /**
117 | * Start the server
118 | */
119 | const port =
120 | process.env.PORT || (process.env.NODE_ENV === 'test' ? 3101 : 3100)
121 | app.listen(port, () => {
122 | app.emit('ready')
123 | })
124 |
125 | app.on('ready', () => {
126 | debug('Server running at port:', port)
127 | })
128 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/endpoints/tv/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | const request = require('request')
2 | const helpers = require('../../../lib/test_helpers')
3 |
4 | describe('tv root', () => {
5 | it('should return info', (done) => {
6 | const fieldsToCheckFor = ['info']
7 | const params = helpers.testRequestParams('/tv/')
8 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
9 | request.get(params, resultHandler)
10 | })
11 | })
12 |
13 | describe.skip('tv - skjar1', () => {
14 | it('should return an array of objects containing correct fields', (done) => {
15 | const fieldsToCheckFor = ['series', 'title', 'originalTitle', 'description', 'live', 'premier']
16 | const params = helpers.testRequestParams('/tv/skjar1')
17 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
18 | request.get(params, resultHandler)
19 | })
20 | })
21 |
22 | describe('tv - stod2 bio', () => {
23 | it('should return an array of objects containing correct fields', (done) => {
24 | const fieldsToCheckFor = ['series', 'title', 'originalTitle', 'description', 'live', 'premier']
25 | const params = helpers.testRequestParams('/tv/stod2bio')
26 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
27 | request.get(params, resultHandler)
28 | })
29 | })
30 |
31 | describe('tv - stod3', () => {
32 | it('should return an array of objects containing correct fields', (done) => {
33 | const fieldsToCheckFor = ['series', 'title', 'originalTitle', 'description', 'live', 'premier']
34 | const params = helpers.testRequestParams('/tv/stod3')
35 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
36 | request.get(params, resultHandler)
37 | })
38 | })
39 |
40 | describe.skip('tv - stod2 sport2', () => {
41 | it('should return an array of objects containing correct fields', (done) => {
42 | const fieldsToCheckFor = ['series', 'title', 'originalTitle', 'description', 'live', 'premier']
43 | const params = helpers.testRequestParams('/tv/stod2sport2')
44 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
45 | request.get(params, resultHandler)
46 | })
47 | })
48 |
49 | describe('tv - stod2 sport', () => {
50 | it('should return an array of objects containing correct fields', (done) => {
51 | const fieldsToCheckFor = ['series', 'title', 'originalTitle', 'description', 'live', 'premier']
52 | const params = helpers.testRequestParams('/tv/stod2sport')
53 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
54 | request.get(params, resultHandler)
55 | })
56 | })
57 |
58 | describe('tv - stod2', () => {
59 | it('should return an array of objects containing correct fields', (done) => {
60 | const fieldsToCheckFor = ['series', 'title', 'originalTitle', 'description', 'live', 'premier']
61 | const params = helpers.testRequestParams('/tv/stod2')
62 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
63 | request.get(params, resultHandler)
64 | })
65 | })
66 |
67 | describe.skip('tv - ruv ithrottir', function () {
68 | it('should return an array of objects containing correct fields', (done) => {
69 | this.timeout(20000)
70 | const fieldsToCheckFor = ['series', 'title', 'originalTitle', 'description', 'live', 'premier']
71 | const params = helpers.testRequestParams('/tv/ruvithrottir')
72 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor, undefined, true)
73 | request.get(params, resultHandler)
74 | })
75 | })
76 |
77 | describe.skip('tv - ruv', function () {
78 | it('should return an array of objects containing correct fields', (done) => {
79 | this.timeout(20000)
80 | const fieldsToCheckFor = ['series', 'title', 'originalTitle', 'description', 'live', 'premier']
81 | const params = helpers.testRequestParams('/tv/ruv')
82 | const resultHandler = helpers.testRequestHandlerForFields(done, fieldsToCheckFor)
83 | request.get(params, resultHandler)
84 | })
85 | })
86 |
--------------------------------------------------------------------------------
/endpoints/names/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/first */
2 | /*
3 | About: An API for all names that are allowed in Iceland. Names are devided into
4 | male names, female names and middle names
5 | Author: Hjörtur Líndal Stefánsson
6 | Email: hjorturls@gmail.com
7 | Created: August 2014
8 | */
9 |
10 | const request = require('request')
11 | const h = require('apis-helpers')
12 | const cheerio = require('cheerio')
13 | const app = require('../../server')
14 |
15 | /* Handles the request for a specific request URL */
16 | function handleRequest(providedUrl, req, res) {
17 | let $
18 | // Check for the filter parameter
19 | const filter = req.params.filter || req.query.filter || req.query.search || ''
20 |
21 | // Add name filtering if it is requested
22 | let url = providedUrl || ''
23 | if (filter !== '') {
24 | url += `&Nafn=${filter}`
25 | }
26 |
27 | request.get({
28 | headers: { 'User-Agent': h.browser() },
29 | url,
30 | }, (error, response, body) => {
31 | if (error || response.statusCode !== 200) {
32 | return res.status(500).json({ error: 'www.island.is refuses to respond or give back data' })
33 | }
34 |
35 | try {
36 | $ = cheerio.load(body)
37 | } catch (exc) {
38 | return res.status(500).json({ error: 'Could not parse body' })
39 | }
40 |
41 | const obj = { results: [] }
42 |
43 | // Clear data regarding the acceptance date of the name (not needed)
44 | $('.dir li i').each(() => {
45 | $(this).remove()
46 | })
47 |
48 | // Loop through all the names in the list and add them to our array
49 | $('.dir li').each(() => {
50 | const name = $(this).text()
51 | obj.results.push(name.trim())
52 | })
53 |
54 | // Return the results as JSON and cache for 24 hours
55 | return res.cache(86400).json(obj)
56 | })
57 | }
58 |
59 | /* Root names handler - only returns a list of resources */
60 | app.get('/names', (req, res) => {
61 | return res.json({
62 | results: [
63 | {
64 | // eslint-disable-next-line max-len
65 | info: 'This is an api that lists all allowed Icelandic names. A search parameter can be used with each endpoint',
66 | endpoints: {
67 | males: '/names/males/',
68 | females: '/names/females/',
69 | middlenames: '/names/middlenames/',
70 | },
71 | },
72 | ],
73 | })
74 | })
75 |
76 | /* Get all legal names for males */
77 | app.get('/names/males/:filter?', (req, res) => {
78 | const url = 'https://www.island.is/mannanofn/leit-ad-nafni/?Stafrof=&Drengir=on&Samthykkt=yes'
79 | return handleRequest(url, req, res)
80 | })
81 |
82 | /* Get all legal names for females */
83 | app.get('/names/females/:filter?', (req, res) => {
84 | const url = 'https://www.island.is/mannanofn/leit-ad-nafni/?Stafrof=&Stulkur=on&Samthykkt=yes'
85 | return handleRequest(url, req, res)
86 | })
87 |
88 | /* Get all legal middle names */
89 | app.get('/names/middlenames/:filter?', (req, res) => {
90 | const url = 'https://www.island.is/mannanofn/leit-ad-nafni/?Stafrof=&Millinofn=on&Samthykkt=yes'
91 | return handleRequest(url, req, res)
92 | })
93 |
94 | /* Get all rejected names for males */
95 | app.get('/names/rejected/males/:filter?', (req, res) => {
96 | const url = 'https://www.island.is/mannanofn/leit-ad-nafni/?Stafrof=&Drengir=on&Samthykkt=no'
97 | return handleRequest(url, req, res)
98 | })
99 |
100 | /* Get all rejected names for females */
101 | app.get('/names/rejected/females/:filter?', (req, res) => {
102 | const url = 'https://www.island.is/mannanofn/leit-ad-nafni/?Stafrof=&Stulkur=on&Samthykkt=no'
103 | return handleRequest(url, req, res)
104 | })
105 |
106 | /* Get all rejected middle names */
107 | app.get('/names/rejected/middlenames/:filter?', (req, res) => {
108 | const url = 'https://www.island.is/mannanofn/leit-ad-nafni/?Stafrof=&Millinofn=on&Samthykkt=no'
109 | return handleRequest(url, req, res)
110 | })
111 |
112 | /* Get all rejected names */
113 | app.get('/names/rejected/:filter?', (req, res) => {
114 | const url = 'https://www.island.is/mannanofn/leit-ad-nafni/?Stafrof=&Stulkur=on&Drengir=on&Millinofn=on&Samthykkt=no'
115 | return handleRequest(url, req, res)
116 | })
117 |
--------------------------------------------------------------------------------
/endpoints/ship/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-undef */
2 | /* eslint-disable no-restricted-globals */
3 | /* eslint-disable prefer-promise-reject-errors */
4 | const request = require('request')
5 | const $ = require('cheerio')
6 | const h = require('apis-helpers')
7 | const app = require('../../server')
8 |
9 | const parseIsFloat = str => parseFloat(str.replace('.', '').replace(',', '.'))
10 |
11 | const lookupShip = searchStr => new Promise((resolve, reject) => {
12 | // Encode searchString so that Icelandic characters will work
13 | const searchString = encodeURIComponent(searchStr)
14 | const url = `http://www.samgongustofa.is/siglingar/skrar-og-utgafa/skipaskra/uppfletting?sq=${searchString}`
15 |
16 | request.get({
17 | headers: { 'User-Agent': h.browser() },
18 | url,
19 | }, (error, response, body) => {
20 | if (error || response.statusCode !== 200) {
21 | reject('www.samgongustofa.is refuses to respond or give back data')
22 | }
23 |
24 | // Translations from: https:// www.samgongustofa.is/media/siglingar/skip/Vefskipaskra-2012.pdf
25 | const typeDict = {
26 | BJÖRGUNARSKIP: 'LIFEBOAT',
27 | DRÁTTARSKIP: 'TUGBOAT',
28 | 'DÝPK. OG SANDSKIP': 'DREDGERSET',
29 | DÝPKUNARSKIP: 'DREDGER',
30 | 'EFTIRLITS‐ OG BJÖRGUNARSKIP': 'PATROL AND LIFEBOAT',
31 | FARÞEGASKIP: 'PASSENGERSHIP',
32 | 'FISKI,FARÞEGASKIP': 'FISHING,PASSENGERSHIP',
33 | FISKISKIP: 'FISHING VESSEL',
34 | FLOTBRYGGJA: 'PONTOON BRIDGE',
35 | FLOTKVÍ: 'FLOATING DOCK',
36 | 'FLUTNINGA/BRUNNSKIP': 'CARGO VESSEL LIVE FISH CARRIER',
37 | FRÍSTUNDAFISKISKIP: '',
38 | 'HAFNSÖGU/DRÁTTARSKIP': 'PILOT‐ AND TUGBOAT',
39 | HVALVEIÐISKIP: 'FISH.V.WHALE CATCHER',
40 | LÓÐSSKIP: 'PILOT BOAT',
41 | 'NÓTAVEIÐI/SKUTTOGARI': 'FISH.V.PURSE STEINERS/ST',
42 | OLÍUSKIP: 'OIL TANKER',
43 | PRAMMI: 'BARGE',
44 | RANNSÓKNARSKIP: 'RESEARCH VESSEL',
45 | SAFNSKIP: 'MUSEUM SHIP',
46 | SEGLSKIP: 'SAILBOAT',
47 | SJÓMÆLINGASKIP: '',
48 | SKEMMTISKIP: 'PLEASURE CRAFT',
49 | SKÓLASKIP: 'TRAINING VESSEL',
50 | SKUTTOGARI: 'FISH.V.STERN TRAWLER',
51 | VARÐSKIP: 'INSPECTION SHIP',
52 | VINNUSKIP: 'WORKBOAT',
53 | VÍKINGASKIP: 'VIKING SHIP',
54 | VÖRUFLUTNINGASKIP: 'DRY CARGO SHIP',
55 | ÞANGSKURÐARPRAMMI: 'BARGE',
56 | }
57 |
58 | const data = $(body)
59 | const fieldList = []
60 | data.find('.vehicleinfo ul').each((index, element) => {
61 | const fields = []
62 | $(element).find('li').each((i, el) => {
63 | // i === 10 is info about ship owners
64 | if (i !== 10) {
65 | const val = $(el).find('span').text()
66 | fields.push(val)
67 | } else {
68 | // We'll parse the owners' field separately
69 | const owners = []
70 | $(el).children('span').each(function () {
71 | const info = $(this).text().split(/\s{2,}/g)
72 | const owner = {
73 | name: info[1],
74 | socialnumber: info[2].replace('(kt. ', '').replace('-', '').replace(')', ''),
75 | share: parseIsFloat(info[3].replace(' eign', '')) / 100,
76 | }
77 | owners.push(owner)
78 | })
79 | fields.push(owners)
80 | }
81 | })
82 | if (fields.length > 0) {
83 | fieldList.push(fields)
84 | }
85 | })
86 |
87 | if (fieldList.length > 0 && fieldList[0].length > 0) {
88 | resolve(fieldList.map((fields) => {
89 | const type = typeDict[fields[1]] ? typeDict[fields[1]] : fields[1]
90 | const registrationStatus = fields[5] === 'Skráð' ? 'Listed' : 'Unlisted'
91 | return {
92 | name: fields[0],
93 | type,
94 | registrationNumber: fields[2],
95 | regionalCode: fields[3].replace(/(\(.*\))/g, '').match(/(\S.*)/g).join(' '),
96 | homePort: fields[4],
97 | registrationStatus,
98 | grossRegisterTonnage: parseIsFloat(fields[6]),
99 | grossTonnage: parseIsFloat(fields[7]),
100 | length: parseIsFloat(fields[8]),
101 | buildYear: parseInt(fields[9].split('af')[0], 10),
102 | buildYard: fields[9].split('af')[1].match(/(\S.*)/g).join(' '),
103 | owners: fields[10],
104 | }
105 | }))
106 | } else {
107 | reject(`No ship found with the query ${searchStr}`)
108 | }
109 | })
110 | })
111 |
112 | app.get('/ship', async (req, res) => {
113 | const search = req.query.search || ''
114 |
115 | if (!search) {
116 | return res.status(431).json({ error: 'Please provide a valid search string to lookup' })
117 | }
118 |
119 | try {
120 | const ships = await lookupShip(search)
121 | res.cache().json({ results: ships })
122 | } catch (error) {
123 | res.status(500).json({ error })
124 | }
125 | })
126 |
127 | module.exports = lookupShip
128 |
--------------------------------------------------------------------------------
/endpoints/names/tests/integration_test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/extensions */
2 | /*
3 | About: Integration tests for the Names API
4 | Author: Hjörtur Líndal Stefánsson
5 | Email: hjorturls@gmail.com
6 | Created: August 2014
7 | */
8 |
9 | const assert = require('assert')
10 | const request = require('request')
11 | const helpers = require('../../../lib/test_helpers.js')
12 |
13 | /* Asserts the results */
14 | function assertResults(params, beEmpty) {
15 | const shouldBeEmpty = beEmpty || false
16 | request.get(params, (err, res, body) => {
17 | const json = JSON.parse(body)
18 |
19 | assert(json.results && typeof json.results.length !== 'undefined', 'Does not contain a \'results\' field')
20 | if (!shouldBeEmpty) {
21 | assert(json.results.length > 0, 'Results are empty')
22 | } else {
23 | assert(json.results.length <= 0, 'Results are NOT empty')
24 | }
25 | })
26 | }
27 |
28 | /* Test the unfiltered list of names */
29 | function testUnfiltered(url) {
30 | it('should return an array of strings', (done) => {
31 | const params = helpers.testRequestParams(url, {})
32 |
33 | assertResults(params)
34 | done()
35 | })
36 | }
37 |
38 | /* Test filtering with an invalid string */
39 | function testFilteredEmpty(url) {
40 | it('should return an array of strings', (done) => {
41 | const params = helpers.testRequestParams(url, { search: '234asdf' })
42 |
43 | assertResults(params, true)
44 | done()
45 | })
46 | }
47 |
48 | /* Test filtering the results with a valid filter */
49 | function testFiltered(url) {
50 | it('should return an array of strings', (done) => {
51 | const params = helpers.testRequestParams(url, { search: 'an' })
52 |
53 | assertResults(params)
54 | done()
55 | })
56 | }
57 |
58 | describe('names', () => {
59 | /* Test the /names/males endpoint with filtering */
60 | describe('males-filtered', () => {
61 | testFiltered('/names/males')
62 | })
63 |
64 | /* Test the /names/males endpoint with invalid filtering */
65 | describe('males-filtered-invalid', () => {
66 | testFilteredEmpty('/names/males')
67 | })
68 |
69 | /* Test the /names/males endpoint without filtering */
70 | describe('males', () => {
71 | testUnfiltered('/names/males')
72 | })
73 |
74 | /* Test the /names/females endpoint with filtering */
75 | describe('females-filtered', () => {
76 | testFiltered('/names/females')
77 | })
78 |
79 | /* Test the /names/females endpoint with invalid filtering */
80 | describe('females-filtered-invalid', () => {
81 | testFilteredEmpty('/names/males')
82 | })
83 |
84 | /* Test the /names/females endpoint without filtering */
85 | describe('females', () => {
86 | testUnfiltered('/names/females')
87 | })
88 |
89 | /* Test the /names/middlenames endpoint with filtering */
90 | describe('middlenames-filtered', () => {
91 | testFiltered('/names/middlenames')
92 | })
93 |
94 | /* Test the /names/middlenames endpoint with invalid filtering */
95 | describe('middlenames-filtered-invalid', () => {
96 | testFilteredEmpty('/names/middlenames')
97 | })
98 |
99 | /* Test the /names/middlenames endpoint without filtering */
100 | describe('middlenames', () => {
101 | testUnfiltered('/names/middlenames')
102 | })
103 |
104 | /* Test the /names/rejected/males endpoint with filtering */
105 | describe('rejected-males-filtered', () => {
106 | testFiltered('/names/rejected/males')
107 | })
108 |
109 | /* Test the /names/rejected/males endpoint with invalid filtering */
110 | describe('rejected-males-filtered-invalid', () => {
111 | testFilteredEmpty('/names/rejected/males')
112 | })
113 |
114 | /* Test the /names/rejected/males endpoint without filtering */
115 | describe('rejected-males', () => {
116 | testUnfiltered('/names/rejected/males')
117 | })
118 |
119 | /* Test the /names/rejected/females endpoint with filtering */
120 | describe('rejected-females-filtered', () => {
121 | testFiltered('/names/rejected/females')
122 | })
123 |
124 | /* Test the /names/rejected/females endpoint with invalid filtering */
125 | describe('rejected-females-filtered-invalid', () => {
126 | testFilteredEmpty('/names/rejected/females')
127 | })
128 |
129 | /* Test the /names/rejected/females endpoint without filtering */
130 | describe('rejected-females', () => {
131 | testUnfiltered('/names/rejected/females')
132 | })
133 |
134 | /* Test the /names/rejected/middlenames endpoint with filtering */
135 | describe('rejected-middlenames-filtered', () => {
136 | testFiltered('/names/rejected/middlenames')
137 | })
138 |
139 | /* Test the /names/rejected/middlenames endpoint with invalid filtering */
140 | describe('rejected-middlenames-filtered-invalid', () => {
141 | testFilteredEmpty('/names/rejected/middlenames')
142 | })
143 |
144 | /* Test the /names/rejected/middlenames endpoint without filtering */
145 | describe('rejected-middlenames', () => {
146 | testUnfiltered('/names/rejected/middlenames')
147 | })
148 | })
149 |
--------------------------------------------------------------------------------
/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/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/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