Initial commit

This commit is contained in:
Blaise de Carné 2018-04-24 15:25:06 +02:00
commit d38f28bd07
18 changed files with 41921 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
.idea
dataset/kb/data/*
!dataset/kb/data/.gitkeep

16
CHANGELOG.md Normal file
View File

@ -0,0 +1,16 @@
# Changelog
Tous les changements notables apportés à ce projet seront documentés dans ce fichier.
Le format est basé sur [Keep a Changelog](http://keepachangelog.com/fr/1.0.0/)
et ce projet adhère à [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [1.0.0] - 2018-04-24
### Ajouts
- Code source initial
- Documentation

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 DATAtourisme
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

106
README.md Normal file
View File

@ -0,0 +1,106 @@
<p align="center">
<a href="https://www.datatourisme.gouv.fr" target="_blank">
<img alt="DATAtourisme" src="https://framagit.org/datatourisme/api/raw/master/docs/_media/logo.png">
</a>
</p>
<p align="center">
La plateforme <strong>OPEN DATA</strong> de l'information touristique
</p>
<p align="center"><a href="https://www.datatourisme.gouv.fr/">https://www.datatourisme.gouv.fr</a></p>
# Environnement full-stack Docker
Ce projet contient un environnement [docker-compose](https://docs.docker.com/compose/) contenant les
services suivants :
* La base de données [Blazegraph](http://www.blazegraph.com)
* L'[API DATAtourisme](https://framagit.org/datatourisme/api) associée à :
* Un point d'accès HTTP GraphQL
* [GraphiQL](https://github.com/graphql/graphiql), une interface visuelle de conception de requête
* [Voyager](https://github.com/APIs-guru/graphql-voyager), un visualisateur de schéma sous forme de graphe.
Ce stack vous permet de mettre rapidement en place un environnement de restitution des données touristiques
téléchargées à partir de la [plateforme diffuseur](https://diffuseur.datatourisme.gouv.fr).
## Utilisation
### Pré-requis
Pour utiliser l'environnement, doivent être installés et configurés :
* [Docker-ce](https://docs.docker.com/engine/installation/)
* [docker-compose](https://docs.docker.com/compose/install/)
Par défaut, vous devrez rendre disponible les ports 8080 et 9999.
### Récupération d'un fichier de données
Pour vous servir de l'environnement, vous devez d'abord récupérer des données touristiques :
1. Connectez-vous à la [plateforme diffuseur](https://diffuseur.datatourisme.gouv.fr). Il sera nécessaire de
créer un compte s'il s'agit de votre première connexion.
2. Créez et configurez un flux de données à l'aide de l'éditeur visuel de requête.
3. Configurez le flux pour utiliser un format **compatible avec l'API** :
* RDF-XML
* Turtle
* NT
4. Téléchargez le fichier du flux une fois celui-ci disponible.
Le fichier ainsi obtenu doit être copié dans le répertoire `dataset/kb/data` pour être chargé dans la base de
données lors de la création du conteneur Docker.
À titre d'information, voici comment se décompose le dossier `dataset` :
| Répertoire | description |
| -------------------------------- |-------------------------------------------------|
| dataset/ | Répertoire décrivant la configuration et les données à charger dans le namespace |
| &nbsp;&nbsp;&nbsp;/kb/ | Namespace pour les données (kb étant le namespace par défaut) |
| &nbsp;&nbsp;&nbsp;/data/ | Répertoire où copier les données sémantiques (ttl, nt, n3, xml, gz...) |
| &nbsp;&nbsp;&nbsp;/RWSTore.properties | Configuration de la base de données pour ce namespace namespace |
### Lancement de l'environnement
Vous pouvez maintenant lancer l'environnement :
```
$ docker-compose up
```
Ces commandes lanceront deux serveurs :
- un serveur [Blazegraph](https://www.blazegraph.com/) sur le port 9999 chargé de vos données.
- un serveur incluant l'[API DATAtourisme](https://framagit.org/datatourisme/api) sur le port 8080.
Vous pouvez accèder aux interfaces suivantes :
* Interface d'administration Blazegraph : http://localhost:9999
* Point d'accès HTTP GraphQL : http://localhost:8080
* GraphiQL : http://localhost:8080/graphiql
* Voyager : http://localhost:8080/voyager
### Utilisation de l'API
Pour utiliser l'API, reportez vous à la [documentation officielle](https://datatourisme.frama.io/api/#/).
### Mise à jour des données
Lorsque vous souhaitez mettre à jour les données de votre environnement, vous devez procéder ainsi :
1. Stoppez l'environnement s'il est actif : `docker-compose stop`
1. Téléchargez le nouveau fichier de donnée et remplacez le dans **dataset/kb/data**
2. Supprimez le conteneur Blazegraph : `docker-compose rm blazegraph`
3. Relancez l'environnement : `docker-compose up`
## Liens
* [API DATAtourisme](https://framagit.org/datatourisme/api)
* [Documentation de l'API](https://datatourisme.frama.io/api)
* [Plateforme diffuseur](https://diffuseur.datatourisme.gouv.fr)
* Centre de support
## License
MIT License

View File

@ -0,0 +1,53 @@
#
# Note: These options are applied when the journal and the triple store are
# first created.
##
## Journal options.
##
# The backing file. This contains all your data. You want to put this someplace
# safe. The default locator will wind up in the directory from which you start
# your servlet container.
com.bigdata.journal.AbstractJournal.file=/var/lib/blazegraph/blazegraph.jnl
# The persistence engine. Use 'Disk' for the WORM or 'DiskRW' for the RWStore.
com.bigdata.journal.AbstractJournal.bufferMode=DiskRW
# Setup for the RWStore recycler rather than session protection.
com.bigdata.service.AbstractTransactionService.minReleaseAge=1
# Enable group commit. See http://wiki.blazegraph.com/wiki/index.php/GroupCommit
# Note: Group commit is a beta feature in BlazeGraph release 1.5.1.
#com.bigdata.journal.Journal.groupCommit=true
com.bigdata.btree.writeRetentionQueue.capacity=4000
com.bigdata.btree.BTree.branchingFactor=128
# 200M initial extent
com.bigdata.journal.AbstractJournal.initialExtent=209715200
com.bigdata.journal.AbstractJournal.maximumExtent=209715200
##
## Setup for QUADS mode without the full text index.
##
com.bigdata.rdf.sail.truthMaintenance=false
com.bigdata.rdf.store.AbstractTripleStore.quads=false
com.bigdata.rdf.store.AbstractTripleStore.statementIdentifiers=false
com.bigdata.rdf.store.AbstractTripleStore.textIndex=true
com.bigdata.rdf.store.AbstractTripleStore.axiomsClass=com.bigdata.rdf.axioms.NoAxioms
# Bump up the branching factor for the lexicon indices on the default kb.
com.bigdata.namespace.kb.lex.com.bigdata.btree.BTree.branchingFactor=400
# Bump up the branching factor for the statement indices on the default kb.
com.bigdata.namespace.kb.spo.com.bigdata.btree.BTree.branchingFactor=1024
# Uncomment to enable collection of OS level performance counters. When
# collected they will be self-reported through the /counters servlet and
# the workbench "Performance" tab.
#
# com.bigdata.journal.Journal.collectPlatformStatistics=true
# geospatial
com.bigdata.rdf.store.AbstractTripleStore.geoSpatial=true

0
dataset/kb/data/.gitkeep Normal file
View File

23
docker-compose.yml Normal file
View File

@ -0,0 +1,23 @@
version: '3.1'
services:
blazegraph:
image: conjecto/blazegraph
ports:
- 9999:9999
environment:
JAVA_XMS: 512m
JAVA_XMX: 1g
volumes:
- ./dataset:/docker-entrypoint-initdb.d
ports:
- "9999:9999"
datatourisme:
build: docker
ports:
- "8080:80"
restart: always
depends_on:
- blazegraph

22
docker/Dockerfile Normal file
View File

@ -0,0 +1,22 @@
FROM php
MAINTAINER Conjecto <contact@conjecto.com>
RUN apt-get update && apt-get install -y \
--no-install-recommends git zip zlib1g-dev
RUN docker-php-ext-install -j$(nproc) zip
COPY app/ /var/app/
WORKDIR /var/app/
# install composer
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
RUN php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
RUN php composer-setup.php
RUN php -r "unlink('composer-setup.php');"
# install vendors
RUN php composer.phar install
# run server
CMD ["php", "-S", "0.0.0.0:80", "-t", "/var/app/www"]

13
docker/app/composer.json Normal file
View File

@ -0,0 +1,13 @@
{
"type": "project",
"authors": [
{
"name": "Conjecto",
"email": "contact@conjecto.com"
}
],
"require": {
"php": "^7.0",
"datatourisme/api": "^1.0"
}
}

File diff suppressed because one or more lines are too long

1
docker/app/www/graphiql/fetch.min.js vendored Normal file
View File

@ -0,0 +1 @@
!function(){"use strict";function t(t){if("string"!=typeof t&&(t=t.toString()),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(t))throw new TypeError("Invalid character in header field name");return t.toLowerCase()}function e(t){return"string"!=typeof t&&(t=t.toString()),t}function r(t){this.map={},t instanceof r?t.forEach(function(t,e){this.append(e,t)},this):t&&Object.getOwnPropertyNames(t).forEach(function(e){this.append(e,t[e])},this)}function o(t){return t.bodyUsed?Promise.reject(new TypeError("Already read")):void(t.bodyUsed=!0)}function n(t){return new Promise(function(e,r){t.onload=function(){e(t.result)},t.onerror=function(){r(t.error)}})}function s(t){var e=new FileReader;return e.readAsArrayBuffer(t),n(e)}function i(t){var e=new FileReader;return e.readAsText(t),n(e)}function a(){return this.bodyUsed=!1,this._initBody=function(t){if(this._bodyInit=t,"string"==typeof t)this._bodyText=t;else if(p.blob&&Blob.prototype.isPrototypeOf(t))this._bodyBlob=t;else if(p.formData&&FormData.prototype.isPrototypeOf(t))this._bodyFormData=t;else{if(t)throw new Error("unsupported BodyInit type");this._bodyText=""}},p.blob?(this.blob=function(){var t=o(this);if(t)return t;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){return this.blob().then(s)},this.text=function(){var t=o(this);if(t)return t;if(this._bodyBlob)return i(this._bodyBlob);if(this._bodyFormData)throw new Error("could not read FormData body as text");return Promise.resolve(this._bodyText)}):this.text=function(){var t=o(this);return t?t:Promise.resolve(this._bodyText)},p.formData&&(this.formData=function(){return this.text().then(h)}),this.json=function(){return this.text().then(JSON.parse)},this}function u(t){var e=t.toUpperCase();return c.indexOf(e)>-1?e:t}function f(t,e){if(e=e||{},this.url=t,this.credentials=e.credentials||"omit",this.headers=new r(e.headers),this.method=u(e.method||"GET"),this.mode=e.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&e.body)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(e.body)}function h(t){var e=new FormData;return t.trim().split("&").forEach(function(t){if(t){var r=t.split("="),o=r.shift().replace(/\+/g," "),n=r.join("=").replace(/\+/g," ");e.append(decodeURIComponent(o),decodeURIComponent(n))}}),e}function d(t){var e=new r,o=t.getAllResponseHeaders().trim().split("\n");return o.forEach(function(t){var r=t.trim().split(":"),o=r.shift().trim(),n=r.join(":").trim();e.append(o,n)}),e}function l(t,e){e||(e={}),this._initBody(t),this.type="default",this.url=null,this.status=e.status,this.ok=this.status>=200&&this.status<300,this.statusText=e.statusText,this.headers=e.headers instanceof r?e.headers:new r(e.headers),this.url=e.url||""}if(!self.fetch){r.prototype.append=function(r,o){r=t(r),o=e(o);var n=this.map[r];n||(n=[],this.map[r]=n),n.push(o)},r.prototype["delete"]=function(e){delete this.map[t(e)]},r.prototype.get=function(e){var r=this.map[t(e)];return r?r[0]:null},r.prototype.getAll=function(e){return this.map[t(e)]||[]},r.prototype.has=function(e){return this.map.hasOwnProperty(t(e))},r.prototype.set=function(r,o){this.map[t(r)]=[e(o)]},r.prototype.forEach=function(t,e){Object.getOwnPropertyNames(this.map).forEach(function(r){this.map[r].forEach(function(o){t.call(e,o,r,this)},this)},this)};var p={blob:"FileReader"in self&&"Blob"in self&&function(){try{return new Blob,!0}catch(t){return!1}}(),formData:"FormData"in self},c=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];a.call(f.prototype),a.call(l.prototype),self.Headers=r,self.Request=f,self.Response=l,self.fetch=function(t,e){var r;return r=f.prototype.isPrototypeOf(t)&&!e?t:new f(t,e),new Promise(function(t,e){function o(){return"responseURL"in n?n.responseURL:/^X-Request-URL:/m.test(n.getAllResponseHeaders())?n.getResponseHeader("X-Request-URL"):void 0}var n=new XMLHttpRequest;n.onload=function(){var r=1223===n.status?204:n.status;if(100>r||r>599)return void e(new TypeError("Network request failed"));var s={status:r,statusText:n.statusText,headers:d(n),url:o()},i="response"in n?n.response:n.responseText;t(new l(i,s))},n.onerror=function(){e(new TypeError("Network request failed"))},n.open(r.method,r.url,!0),"include"===r.credentials&&(n.withCredentials=!0),"responseType"in n&&p.blob&&(n.responseType="blob"),r.headers.forEach(function(t,e){n.setRequestHeader(e,t)}),n.send("undefined"==typeof r._bodyInit?null:r._bodyInit)})},self.fetch.polyfill=!0}}();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,149 @@
<!--
* Copyright (c) Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
-->
<!DOCTYPE html>
<html>
<head>
<style>
body {
height: 100%;
margin: 0;
width: 100%;
overflow: hidden;
}
#graphiql {
height: 100vh;
}
</style>
<!--
This GraphiQL example depends on Promise and fetch, which are available in
modern browsers, but can be "polyfilled" for older browsers.
GraphiQL itself depends on React DOM.
If you do not want to rely on a CDN, you can host these files locally or
include them directly in your favored resource bunder.
-->
<script src="/graphiql/es6-promise.auto.min.js"></script>
<script src="/graphiql/fetch.min.js"></script>
<script src="/graphiql/react.min.js"></script>
<script src="/graphiql/react-dom.min.js"></script>
<!--
These two files can be found in the npm module, however you may wish to
copy them directly into your environment, or perhaps include them in your
favored resource bundler.
-->
<link rel="stylesheet" href="/graphiql/graphiql.css" />
<script src="/graphiql/graphiql.js"></script>
</head>
<body>
<div id="graphiql">Loading...</div>
<script>
var serverUri = '/';
/**
* This GraphiQL example illustrates how to use some of GraphiQL's props
* in order to enable reading and updating the URL parameters, making
* link sharing of queries a little bit easier.
*
* This is only one example of this kind of feature, GraphiQL exposes
* various React params to enable interesting integrations.
*/
// Parse the search string to get url parameters.
var search = window.location.search;
var parameters = {};
search.substr(1).split('&').forEach(function (entry) {
var eq = entry.indexOf('=');
if (eq >= 0) {
parameters[decodeURIComponent(entry.slice(0, eq))] =
decodeURIComponent(entry.slice(eq + 1));
}
});
// if variables was provided, try to format it.
if (parameters.variables) {
try {
parameters.variables =
JSON.stringify(JSON.parse(parameters.variables), null, 2);
} catch (e) {
// Do nothing, we want to display the invalid JSON as a string, rather
// than present an error.
}
}
// When the query and variables string is edited, update the URL bar so
// that it can be easily shared
function onEditQuery(newQuery) {
parameters.query = newQuery;
updateURL();
}
function onEditVariables(newVariables) {
parameters.variables = newVariables;
updateURL();
}
function onEditOperationName(newOperationName) {
parameters.operationName = newOperationName;
updateURL();
}
function updateURL() {
var newSearch = '?' + Object.keys(parameters).filter(function (key) {
return Boolean(parameters[key]);
}).map(function (key) {
return encodeURIComponent(key) + '=' +
encodeURIComponent(parameters[key]);
}).join('&');
history.replaceState(null, null, newSearch);
}
// Defines a GraphQL fetcher using the fetch API. You're not required to
// use fetch, and could instead implement graphQLFetcher however you like,
// as long as it returns a Promise or Observable.
function graphQLFetcher(graphQLParams) {
// This example expects a GraphQL server at the path /graphql.
// Change this to point wherever you host your GraphQL server.
return fetch(serverUri, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(graphQLParams),
credentials: 'include',
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
// Render <GraphiQL /> into the body.
// See the README in the top level of this module to learn more about
// how you can customize GraphiQL by providing different values or
// additional child elements.
ReactDOM.render(
React.createElement(GraphiQL, {
fetcher: graphQLFetcher,
query: parameters.query,
variables: parameters.variables,
operationName: parameters.operationName,
onEditQuery: onEditQuery,
onEditVariables: onEditVariables,
onEditOperationName: onEditOperationName
}),
document.getElementById('graphiql')
);
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

12
docker/app/www/graphiql/react.min.js vendored Normal file

File diff suppressed because one or more lines are too long

26
docker/app/www/index.php Normal file
View File

@ -0,0 +1,26 @@
<?php
//define('SERVER', 'sparql'); # switch resolver to pure sparql
header('Access-Control-Allow-Credentials: true', true);
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {
return;
}
if (isset($_SERVER['CONTENT_TYPE']) && $_SERVER['CONTENT_TYPE'] === 'application/json') {
$rawBody = file_get_contents('php://input');
$requestData = json_decode($rawBody ?: '', true);
} else {
$requestData = $_POST;
}
$payload = isset($requestData['query']) ? $requestData['query'] : null;
require_once __DIR__.'/../vendor/autoload.php';
$processor = \Datatourisme\Api\DatatourismeApi::create('http://blazegraph:9999/blazegraph/namespace/kb/sparql');
$response = $processor->process($payload);
header('Content-Type: application/json');
echo json_encode($response);
exit;

View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
height: 100%;
margin: 0;
width: 100%;
overflow: hidden;
}
#voyager {
height: 100vh;
}
</style>
<!--
This GraphQL Voyager example depends on Promise and fetch, which are available in
modern browsers, but can be "polyfilled" for older browsers.
GraphQL Voyager itself depends on React DOM.
If you do not want to rely on a CDN, you can host these files locally or
include them directly in your favored resource bunder.
-->
<script src="//cdn.jsdelivr.net/es6-promise/4.0.5/es6-promise.auto.min.js"></script>
<script src="//cdn.jsdelivr.net/fetch/0.9.0/fetch.min.js"></script>
<script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
<script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
<link rel="stylesheet" href="https://apis.guru/graphql-voyager/releases/v1.x/voyager.css" />
<script src="https://apis.guru/graphql-voyager/releases/v1.x/voyager.min.js"></script>
</head>
<body>
<div id="voyager">Loading...</div>
<script>
// Defines a GraphQL introspection fetcher using the fetch API. You're not required to
// use fetch, and could instead implement introspectionProvider however you like,
// as long as it returns a Promise
// Voyager passes introspectionQuery as an agrument for this function
function introspectionProvider(introspectionQuery) {
// This example expects a GraphQL server at the path /graphql.
// Change this to point wherever you host your GraphQL server.
return fetch('/', {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({query: introspectionQuery}),
credentials: 'include',
}).then(function (response) {
return response.text();
}).then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
// Render <Voyager /> into the body.
GraphQLVoyager.init(document.getElementById('voyager'), {
introspection: introspectionProvider
});
</script>
</body>
</html>