├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── model └── index.js ├── package.json ├── test ├── fixtures │ ├── api.json │ ├── contents.json │ └── raw.json ├── model-test.js └── utils-test.js └── utils └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | config/runtime.json 3 | package-lock.json 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | - "10" 6 | sudo: false 7 | cache: 8 | directories: 9 | - node_modules 10 | 11 | script: 12 | - npm test 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [3.0.0] - 2018-11-20 6 | ### Changed 7 | * upgrade to support Koop 3.X 8 | 9 | ## [2.0.0] - 2015-10-23 10 | 11 | Stable Release! 12 | 13 | ### Changed 14 | * bumped `koop-provider` to `^1.0.0` 15 | 16 | ### Added 17 | * **model**: debug information for github api and koop cache 18 | * **example**: updates based on koop-gist example server 19 | 20 | ## [2.0.0-alpha] - 2015-10-15 21 | 22 | ### Fixed 23 | * **controller**: preview route dependencies work on non-root mountpaths 24 | * **model**: local cache works (resolved issue from koop upstream) 25 | * **model**: github access tokens now work as expected 26 | 27 | ### Changed 28 | * **provider**: name is now `github` instead of `Github` 29 | * **provider**: `status.version` moved to `version` 30 | * **controller**: removed `ctrl.Error`, using `koop-provider`'s `ctrl.errorResponse` method 31 | * **model**: simplify `find` and `drop` methods (use options object) 32 | 33 | ### Added 34 | * **model**: looks for `KOOP_GITHUB_TOKEN` environmental variable if `config.ghtoken` isn't specified 35 | * **controller/routes**: added `rate_limit` route for checking github rate limit status 36 | * **controller**: support for JSONP callbacks ([`res.jsonp`](http://expressjs.com/api.html#res.jsonp)) 37 | 38 | ### Removed 39 | * **controller/routes**: thumbnail (Thumbnail generation no longer supported) 40 | 41 | ## [1.0.1] - 2015-08-13 42 | 43 | ### Changed 44 | * Bump Esri Leaflet version used by preview to 1.0.0 45 | * Switch from `mocha` to `tape` for tests 46 | 47 | ### Added 48 | * Added `status.version` to provider exports 49 | 50 | ## [1.0.0] - 2015-07-28 51 | 52 | ### Added 53 | * Now checking build status with Travis-CI 54 | * Using [JavaScript Standard Style](https://github.com/feross/standard) 55 | 56 | ### Changed 57 | * Improved documentation 58 | * Moved from Esri to KoopJS github organization 59 | 60 | ### Removed 61 | * Deleted unused fixtures and configuration files 62 | * Removed broken TopoJSON support 63 | 64 | ### Fixed 65 | * Reverted to leaflet CDN to fix broken `leaflet.css` link 66 | 67 | ## v0.1.12 - 2015-03-16 68 | 69 | [3.0.0]: https://github.com/koopjs/koop-github/compare/v2.0.0...v3.0.0 70 | [2.0.0]: https://github.com/koopjs/koop-github/compare/v2.0.0-alpha...v2.0.0 71 | [2.0.0-alpha]: https://github.com/koopjs/koop-github/compare/v1.0.1...v2.0.0-alpha 72 | [1.0.1]: https://github.com/koopjs/koop-github/compare/v1.0.0...v1.0.1 73 | [1.0.0]: https://github.com/koopjs/koop-github/compare/v0.1.12...v1.0.0 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Esri 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # koop-github 2 | 3 | [![Greenkeeper badge](https://badges.greenkeeper.io/koopjs/koop-provider-github.svg)](https://greenkeeper.io/) 4 | 5 | > Github provider for [Koop](https://github.com/koopjs/koop) 6 | 7 | [![npm][npm-image]][npm-url] 8 | [![travis][travis-image]][travis-url] 9 | 10 | [npm-image]: https://img.shields.io/npm/v/koop-github.svg?style=flat-square 11 | [npm-url]: https://www.npmjs.com/package/koop-github 12 | [travis-image]: https://travis-ci.org/koopjs/koop-provider-github.svg?branch=master 13 | [travis-url]: https://travis-ci.org/koopjs/koop-provider-github 14 | 15 | Take GeoJSON from a Github repository and serve it as an ArcGIS Feature Service, CSV, KML, or Shapefile. 16 | 17 | ## Install 18 | 19 | Koop providers require that you first install Koop. For information on using Koop, see https://github.com/koopjs/koop. 20 | 21 | You can add `koop-github` to your Koop server's dependencies by installing it with npm and adding it to your package.json like so: 22 | 23 | ``` 24 | npm install @koopjs/provider-github --save 25 | ``` 26 | 27 | ## Usage 28 | 29 | Make sure your koop configuration includes a github access token (`ghtoken` in the config object passed to koop or `KOOP_GITHUB_TOKEN` as an environmental variable). Your Github API requests will be rate limited and you will not have access private gists if you don't include a token. 30 | 31 | ```js 32 | var koop = require('koop')({ 33 | 'ghtoken': 'XXXXXX' // defaults to `process.env.KOOP_GITHUB_TOKEN` 34 | }) 35 | var koopGithub = require('@koopjs/provider-github') 36 | 37 | koop.register(koopGithub) 38 | 39 | koop.server.listen(1338, function () { 40 | console.log('Listening at http://%s:%d/', this.address().address, this.address().port) 41 | }) 42 | ``` 43 | 44 | Once `koop-github` is registered as provider and you've started your Koop server, you can request GeoJSON files in Github repositories using this pattern. Note that the path within the repo uses `::` as a directory separator: 45 | 46 | ``` 47 | /github/{organization name}::{repository name}::{branch}::{folder::path::to::geojson}/FeatureServer/0/query 48 | ``` 49 | 50 | so for example: 51 | 52 | ``` 53 | http://localhost:1338/github/koopjs::geodata::master::countries::mexico/FeatureServer/0/query 54 | ``` 55 | 56 | ## Test 57 | 58 | `koop-github` uses [tape](https://github.com/substack/tape) for testing. 59 | 60 | ``` 61 | npm test 62 | ``` 63 | 64 | ## Contributing 65 | 66 | Esri welcomes contributions from anyone and everyone. Please see our [guidelines for contributing](https://github.com/Esri/contributing). 67 | 68 | ## License 69 | 70 | [Apache 2.0](LICENSE) -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const github = { 2 | type: 'provider', 3 | name: 'github', 4 | Model: require('./model'), 5 | version: require('./package.json').version 6 | } 7 | 8 | module.exports = github 9 | -------------------------------------------------------------------------------- /model/index.js: -------------------------------------------------------------------------------- 1 | const config = require('config') 2 | const _ = require('lodash') 3 | const geohub = require('geohub') 4 | const { translate } = require('../utils') 5 | const ghtoken = config.ghtoken || process.env.KOOP_GITHUB_TOKEN || null 6 | 7 | if (!ghtoken) console.log('[github provider] No github access token configured. Github API requests may be rate limited.') 8 | 9 | /** 10 | * Model constructor 11 | */ 12 | function Model () {} 13 | 14 | Model.prototype.getData = function (req, callback) { 15 | // Parse the id param to get user, repo, and path 16 | const githubPath = req.params.id.split('::') 17 | const user = githubPath[0] 18 | const repo = githubPath[1] 19 | const branch = githubPath[2] // Updated branch parameter for main vs master 20 | const path = githubPath.slice(3).join('/') 21 | 22 | if (!repo || !path) callback(new Error('The "id" parameter must be of form "user::repo::path::to::file"')) 23 | 24 | // Handle request to github with geohub 25 | geohub.repo({ 26 | user, 27 | repo, 28 | branch, 29 | path, 30 | token: ghtoken 31 | }, (err, data) => { 32 | if (err) return callback(err) 33 | 34 | // Translate to uniform GeoJSON Feature Collection 35 | const geojson = translate(data) 36 | 37 | // Add metadata 38 | geojson.metadata = geojson.metadata || {} 39 | geojson.metadata.title = geojson.metadata.name = githubPath[githubPath.length - 1] 40 | geojson.metadata.description = `GeoJSON from https://raw.github.com/${user}/${repo}/${branch}/${path}.geojson` 41 | geojson.metadata.geometryType = _.get(geojson, 'features[0].geometry.type') 42 | callback(null, geojson) 43 | }) 44 | } 45 | 46 | module.exports = Model 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@koopjs/provider-github", 3 | "description": "Github provider for Koop", 4 | "version": "3.0.0", 5 | "author": "Chris Helm", 6 | "bugs": { 7 | "url": "https://github.com/koopjs/koop-provider-github/issues" 8 | }, 9 | "dependencies": { 10 | "config": "^3.0.1", 11 | "geohub": "^1.0.4" 12 | }, 13 | "devDependencies": { 14 | "nock": "^10.0.2", 15 | "standard": "^12.0.1", 16 | "tap-spec": "^5.0.0", 17 | "tape": "^4.9.1" 18 | }, 19 | "homepage": "https://github.com/koopjs/koop-provider-github", 20 | "keywords": [ 21 | "etl", 22 | "feature", 23 | "feature-service", 24 | "geojson", 25 | "github", 26 | "koop", 27 | "provider", 28 | "service" 29 | ], 30 | "license": "Apache-2.0", 31 | "main": "index.js", 32 | "repository": { 33 | "type": "git", 34 | "url": "git@github.com:koopjs/koop-provider-github.git" 35 | }, 36 | "scripts": { 37 | "test": "standard && tape test/model-test.js | tap-spec" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/fixtures/api.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"map.geojson", 3 | "path":"map.geojson", 4 | "sha":"6978b3c6e6b5b5b065c02b2e371962dfa9410118", 5 | "size":128, 6 | "url":"https://api.github.com/repos/test-org/geodata/contents/map.geojson?ref=master", 7 | "html_url":"https://github.com/test-org/geodata/blob/master/map.geojson", 8 | "git_url":"https://api.github.com/repos/test-org/geodata/git/blobs/6978b3c6e6b5b5b065c02b2e371962dfa9410118", 9 | "download_url":"https://raw.githubusercontent.com/test-org/geodata/master/map.geojson", 10 | "type":"file", 11 | "content":"eyJ0eXBlIjoiRmVhdHVyZUNvbGxlY3Rpb24iLCJmZWF0dXJlcyI6W3sidHlw\nZSI6IkZlYXR1cmUiLCJwcm9wZXJ0aWVzIjp7fSwiZ2VvbWV0cnkiOnsidHlw\nZSI6IlBvaW50IiwiY29vcmRpbmF0ZXMiOlstMTQ0LDYyXX19XX0=\n", 12 | "encoding":"base64", 13 | "_links":{ 14 | "self":"https://api.github.com/repos/test-org/geodata/contents/map.geojson?ref=master", 15 | "git":"https://api.github.com/repos/test-org/geodata/git/blobs/6978b3c6e6b5b5b065c02b2e371962dfa9410118", 16 | "html":"https://github.com/test-org/geodata/blob/master/map.geojson" 17 | } 18 | } -------------------------------------------------------------------------------- /test/fixtures/contents.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name":"map.geojson", 4 | "path":"map.geojson", 5 | "sha":"6978b3c6e6b5b5b065c02b2e371962dfa9410118", 6 | "size":128, 7 | "url":"https://api.github.com/repos/test-org/geodata/contents/map.geojson?ref=master", 8 | "html_url":"https://github.com/test-org/geodata/blob/master/map.geojson", 9 | "git_url":"https://api.github.com/repos/test-org/geodata/git/blobs/6978b3c6e6b5b5b065c02b2e371962dfa9410118", 10 | "download_url":"https://raw.githubusercontent.com/test-org/geodata/master/map.geojson", 11 | "type":"file", 12 | "_links":{ 13 | "self":"https://api.github.com/repos/test-org/geodata/contents/map.geojson?ref=master", 14 | "git":"https://api.github.com/repos/test-org/geodata/git/blobs/6978b3c6e6b5b5b065c02b2e371962dfa9410118", 15 | "html":"https://github.com/test-org/geodata/blob/master/map.geojson" 16 | } 17 | } 18 | ] -------------------------------------------------------------------------------- /test/fixtures/raw.json: -------------------------------------------------------------------------------- 1 | { 2 | "type":"FeatureCollection", 3 | "features":[ 4 | { 5 | "type":"Feature", 6 | "properties":{ 7 | 8 | }, 9 | "geometry":{ 10 | "type":"Point", 11 | "coordinates":[ 12 | -144, 13 | 62 14 | ] 15 | } 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /test/model-test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape') 2 | const Model = require('../model') 3 | const model = new Model() 4 | const nock = require('nock') 5 | 6 | test('should properly fetch from the API and translate features', t => { 7 | t.plan(7) 8 | 9 | const featureCollection = require('./fixtures/raw.json') 10 | nock('https://api.github.com') 11 | .get('/repos/test-org/geodata/contents/?ref=master') 12 | .reply(200, JSON.stringify(require('./fixtures/contents.json'))) 13 | 14 | nock('https://api.github.com') 15 | .get('/repos/test-org/geodata/contents/map.geojson?ref=master') 16 | .reply(200, JSON.stringify(require('./fixtures/api.json'))) 17 | 18 | nock('https://raw.github.com') 19 | .get('/test-org/geodata/master/map.geojson?ref=master') 20 | .reply(200, JSON.stringify(featureCollection)) 21 | 22 | model.getData({ params: { id: 'test-org::geodata::map' } }, (err, geojson) => { 23 | t.notOk(err, 'no error') 24 | t.deepEquals(geojson.features, featureCollection.features, 'features found') 25 | t.ok(geojson.metadata, 'metdata added') 26 | t.equals(geojson.metadata.title, 'map', 'set metadata title') 27 | t.equals(geojson.metadata.name, 'map', 'set metadata name') 28 | t.equals(geojson.metadata.description, `GeoJSON from https://raw.github.com/test-org/geodata/master/map.geojson`, 'set metadata description') 29 | t.equals(geojson.metadata.geometryType, 'Point', 'set metadata geometry') 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /test/utils-test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape') 2 | var { featureToFeatureCollection, geometryToFeatureCollection, translate } = require('../utils') 3 | 4 | test('utils: featureToFeatureCollection', function (t) { 5 | t.plan(1) 6 | const result = featureToFeatureCollection('test') 7 | 8 | t.deepEquals(result, { 9 | type: 'FeatureCollection', 10 | features: ['test'] 11 | }, 'returns expected result') 12 | }) 13 | 14 | test('utils: geometryToFeatureCollection', function (t) { 15 | t.plan(1) 16 | const result = geometryToFeatureCollection('test') 17 | 18 | t.deepEquals(result, { 19 | type: 'FeatureCollection', 20 | features: [{ 21 | type: 'Feature', 22 | geometry: 'test', 23 | properties: {} 24 | }] 25 | }, 'returns expected result') 26 | }) 27 | 28 | test('utils: translate feature', function (t) { 29 | t.plan(1) 30 | const feature = { 31 | 'type': 'Feature', 32 | 'properties': {}, 33 | 'geometry': { 34 | 'type': 'Point', 35 | 'coordinates': [ 36 | -144, 37 | 62 38 | ] 39 | } 40 | } 41 | const result = translate(feature) 42 | 43 | t.deepEquals(result, { 44 | type: 'FeatureCollection', 45 | features: [feature] 46 | }, 'returns expected result') 47 | }) 48 | 49 | test('utils: translate geometry', function (t) { 50 | t.plan(1) 51 | const geometry = { 52 | 'type': 'Point', 53 | 'coordinates': [ 54 | -144, 55 | 62 56 | ] 57 | } 58 | const result = translate(geometry) 59 | 60 | t.deepEquals(result, { 61 | type: 'FeatureCollection', 62 | features: [{ 63 | type: 'Feature', 64 | geometry, 65 | properties: {} 66 | }] 67 | }, 'returns expected result') 68 | }) 69 | 70 | test('utils: translate feature collection', function (t) { 71 | t.plan(1) 72 | const featureCollection = { 73 | 'type': 'FeatureCollection', 74 | 'features': [ 75 | { 76 | 'type': 'Feature', 77 | 'properties': {}, 78 | 'geometry': { 79 | 'type': 'Point', 80 | 'coordinates': [ 81 | -144, 82 | 62 83 | ] 84 | } 85 | } 86 | ] 87 | } 88 | const result = translate(featureCollection) 89 | 90 | t.deepEquals(result, featureCollection, 'returns expected result') 91 | }) 92 | -------------------------------------------------------------------------------- /utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * GeoJSON to convert into required Koop format 3 | * @param {object} input GeoJSON 4 | * @returns {object} standardized feature collection 5 | */ 6 | function translate (input) { 7 | // If input type is Feature, wrap in Feature Collection 8 | if (!input || input.type === 'Feature') return featureToFeatureCollection(input) 9 | 10 | // If it's neither a Feature or a FeatureCollection its a geometry. Wrap in a Feature Collection 11 | if (input.type !== 'FeatureCollection') return geometryToFeatureCollection(input) 12 | 13 | // Or it is already a feature collection, so just return 14 | return input 15 | } 16 | 17 | /** 18 | * Wrap a GeoJSON feature in a feature collection 19 | * @param {object} feature 20 | */ 21 | function featureToFeatureCollection (feature) { 22 | return { 23 | type: 'FeatureCollection', 24 | features: [feature] 25 | } 26 | } 27 | 28 | /** 29 | * Convert a GeoJSON to geometry to a single-feature feature collection 30 | * @param {*} geometry 31 | */ 32 | function geometryToFeatureCollection (geometry) { 33 | return { 34 | type: 'FeatureCollection', 35 | features: [{ 36 | type: 'Feature', 37 | geometry, 38 | properties: {} 39 | }] 40 | } 41 | } 42 | 43 | module.exports = { translate, featureToFeatureCollection, geometryToFeatureCollection } 44 | --------------------------------------------------------------------------------