diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..8012ebb --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +4.2 \ No newline at end of file diff --git a/PRIVATE.json.template b/PRIVATE.json.template index 6003423..40872f8 100644 --- a/PRIVATE.json.template +++ b/PRIVATE.json.template @@ -1,3 +1,6 @@ { - "secret": "fake" + "secret": "fake", + "pg_user": "postgres", + "pg_pwd": "toto", + "db_name": "element" } \ No newline at end of file diff --git a/README.md b/README.md index 8f0bda8..dd4284b 100644 --- a/README.md +++ b/README.md @@ -8,56 +8,41 @@ ### Dependencies Install: -* Docker * Node.js +* Postgresql the use `npm install` to install all the dependencies. -Create a `Tokens.json` following the [example](Tokens.example.json) +Create a `PRIVATE.json` following the [example](PRIVATE.example.json) ### Initialize the database -There are two way of loading the places in the db. - -#### Load with files - -Put your datafiles in `data`, then do: +In the psql console (just type `psql` to access it), you can init your db with: ``` -npm run serve-dev - -# then in another window -docker exec 6elementdev_api_1 tools/init-database.js -docker exec 6elementdev_api_1 tools/loadFiles.js +alter user postgres password 'toto'; +CREATE DATABASE element OWNER postgres; ``` -in production you can use `npm run prod` and change the names of the containers in the exec commands. - -#### Load from a backup - -In dev, `./backups` is linked to `/backups` and in prod, `/data/6element/backups` is linked to `/backups` where automatic backups (at 3AM) are persisted. -At anytime you can backup the db using +In your regular console: ``` -docker exec 6elementdev_api_1 tools/backup.js > backups/test.sql +node tools/init-database.js ``` -to load it back **you must put it in your backups folder and give the path inside the container**: +you can always use `psql` separately to load and dump data: ``` -docker exec 6elementdev_api_1 tools/restore.js /backups/test.sql +psql -p5432 -U postgres -d element < Desktop/latest.sql ``` -you can also use a gziped file (comming from the automated backup for example). - - ### Running the app #### Daily routine in dev ``` -npm run start-dev +npm run dev ``` diff --git a/client/css/main.css b/client/css/main.css index c6478ca..a5dab22 100644 --- a/client/css/main.css +++ b/client/css/main.css @@ -283,6 +283,7 @@ article{ .border-open { border: solid #7fdc2b 2px; font-size: 15px; } .border-closed { border: solid #ff3b6c 2px; font-size: 15px; text-decoration : line-through;} +.border-grey { border: solid #616161 2px; font-size: 15px;} .chart { width: 100%; diff --git a/client/decheteries.html b/client/decheteries.html index 6a6f55d..91bb234 100644 --- a/client/decheteries.html +++ b/client/decheteries.html @@ -13,9 +13,14 @@ - - +
@@ -31,13 +36,5 @@
- \ No newline at end of file diff --git a/client/index.html b/client/index.html index 79d61dc..14bbe48 100644 --- a/client/index.html +++ b/client/index.html @@ -10,6 +10,14 @@ +
diff --git a/client/js/charts.js b/client/js/charts.js index 1c995e4..21771ea 100644 --- a/client/js/charts.js +++ b/client/js/charts.js @@ -26,11 +26,11 @@ function formatDate (date) { showlegend: false, x: xSignals, y: ySignals, - marker: { - symbol: 'x', - color: '#E400B9' + line: { + shape: 'spline', + color: '#E400B9', + connectgaps: false }, - line: {shape: 'spline'}, mode: 'lines', hoverinfo: 'none' }, @@ -134,9 +134,9 @@ function draw(node, data){ ticksX.push(strDate); } - if(value >= 0 && isAffluence){ + if(isAffluence){ xSignals.push(strDate); - ySignals.push(value); + ySignals.push(value >= 0 ? value : {undefined}); } // Color diff --git a/client/js/favorite.js b/client/js/favorite.js index c940b38..f98ae03 100644 --- a/client/js/favorite.js +++ b/client/js/favorite.js @@ -17,14 +17,21 @@ function getCookie(sName) { function changeFavorite(e){ - var button = e.srcElement.parentNode; + var button = e.currentTarget; var isFavorite = button.class === 'place-favorite'; button.class = isFavorite ? 'place-no-favorite' : 'place-favorite'; - e.srcElement.src = isFavorite ? '../img/no-favorite.svg' : '../img/favorite.svg'; + e.currentTarget.firstChild.src = isFavorite ? '../img/no-favorite.svg' : '../img/favorite.svg'; var id = button.id.replace('register-',''); var cookie_places = getCookie('6element-places') || ''; if(isFavorite) setCookie('6element-places', cookie_places.replace(id + ';',''));// remove place in cookie else setCookie('6element-places', cookie_places + id + ';');// add place in cookie + + ga('send', { + hitType: 'event', + eventCategory: 'Favorites', + eventAction: 'changeFavorite', + eventLabel: id + ':' + (isFavorite ? 'false' : 'true') + }); } function initializeAllFavorites(){ diff --git a/client/views/placeView.jsx b/client/views/placeView.jsx index eb1ec26..cb294a2 100644 --- a/client/views/placeView.jsx +++ b/client/views/placeView.jsx @@ -5,6 +5,8 @@ var React = require('react'); var getColor = require('../js/getColor'); var Calendar = require('./calendar'); var opening_hours = require('opening_hours'); +var moment = require('moment'); +var momentTZ = require('moment-timezone'); var NotEmpty = function(field){ if(field === undefined) return false; @@ -13,6 +15,22 @@ var NotEmpty = function(field){ return true; } +function fromUTC(str){ + + var tmp = str.split('T'); + var vDate = tmp[0].split('-'); + var vTime = tmp[1].split(':'); + + var yyyy = parseInt(vDate[0]); + var MM = parseInt(vDate[1]); + var dd = parseInt(vDate[2]); + var hh = parseInt(vTime[0]); + var mm = parseInt(vTime[1]); + var ss = parseInt(vTime[2]); + + return new Date(Date.UTC(yyyy,MM-1,dd,hh,mm,ss)); +} + module.exports = React.createClass({ render: function() { @@ -22,7 +40,11 @@ module.exports = React.createClass({ // OPENING HOURS var oh = place.opening_hours === null ? undefined : new opening_hours(place.opening_hours); - var isOpen = oh ? oh.getState() : true; + var now = momentTZ().tz('Europe/Paris').toDate(); + //console.log(now); + //var now = fromUTC(momentTZ().tz('Europe/Paris').format()); + + var isOpen = oh ? oh.getState(now) : true; var calendarJSX = NotEmpty(place.opening_hours) ? () : ""; @@ -78,7 +100,7 @@ module.exports = React.createClass({ { binsJSX = place.bins .map(function(bin, num){ - return (
  • {bin.t}
  • ); + return (
  • {bin.t}
  • ); }); } diff --git a/client/views/placesView.jsx b/client/views/placesView.jsx index 48191f4..9efa461 100644 --- a/client/views/placesView.jsx +++ b/client/views/placesView.jsx @@ -14,9 +14,6 @@ module.exports = React.createClass({ return (); }); - /* -
    */ - return (
    {placesJSX} diff --git a/compose-alpha.yml b/compose-alpha.yml deleted file mode 100644 index fd939f4..0000000 --- a/compose-alpha.yml +++ /dev/null @@ -1,27 +0,0 @@ -api: - build: ./dockerfiles - dockerfile: node - command: node server/index.js - links: - - db - ports: - - "8001:8001" - environment: - - VIRTUAL_HOST=6element-alpha.ants.builders - - VIRTUAL_PORT=8001 - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - - PGPASSWORD=postgres - - NODE_ENV=alpha - volumes: - - ./:/6element - - ./backups:/backups/ - tty: true - -db: - build: ./dockerfiles - dockerfile: postgis - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - tty: true diff --git a/compose-dev.yml b/compose-dev.yml deleted file mode 100644 index c21dfa8..0000000 --- a/compose-dev.yml +++ /dev/null @@ -1,28 +0,0 @@ -api: - build: ./dockerfiles - dockerfile: node - command: nodemon -L --watch server --watch client/* server/index.js - links: - - db - ports: - - "8001:8001" - environment: - - VIRTUAL_PORT=8001 - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - - PGPASSWORD=postgres - - NODE_ENV=development - volumes: - - ./:/6element - - ./backups:/backups/ - tty: true - dns: - - 8.8.8.8 - -db: - build: ./dockerfiles - dockerfile: postgis - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - tty: true diff --git a/compose-prod.yml b/compose-prod.yml deleted file mode 100644 index 347dc69..0000000 --- a/compose-prod.yml +++ /dev/null @@ -1,27 +0,0 @@ -api: - build: ./dockerfiles - dockerfile: node - command: node server/index.js - links: - - db - ports: - - "8000:8000" - environment: - - VIRTUAL_HOST=6element.fr - - VIRTUAL_PORT=8000 - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - - PGPASSWORD=postgres - - NODE_ENV=production - volumes: - - ./:/6element - - ./backups:/backups/ - tty: true - -db: - build: ./dockerfiles - dockerfile: postgis - environment: - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - tty: true diff --git a/package.json b/package.json index 3643651..53b9190 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,10 @@ "lint": "eslint .", "watch": "babel client/views/*.jsx --out-dir . --watch", "build": "babel client/views/*.jsx --out-dir .", - "stop-dev": "docker-compose -p dev6element -f compose-dev.yml stop", - "stop-alpha": "docker-compose -p alpha6element -f compose-alpha.yml stop", - "stop-prod": "docker-compose -f compose-prod.yml stop", - "prod": "docker-compose -f compose-prod.yml up -d --force-recreate", + "prod": "node server/index.js", "preprod": "npm run build", - "alpha": "docker-compose -p alpha6element -f compose-alpha.yml up -d --force-recreate", - "prealpha": "npm run build", - "serve-dev": "docker-compose -p dev6element -f compose-dev.yml up --force-recreate", - "dev": "npm-run-all --parallel serve-dev watch", + "monitor": "nodemon -L --watch server --watch client/*.js server/index.js", + "dev": "npm-run-all --parallel serve-dev watch monitor", "validate": "npm ls" }, "babel": { @@ -49,12 +44,11 @@ "es6-shim": "^0.31.3", "express": "^4.12.4", "jsdom": "^7.0.2", - "node-schedule": "^0.6.0", + "moment": "^2.11.1", + "moment-timezone": "^0.5.0", "opening_hours": "^3.3.0", "pg": "^4.4.2", "pg-hstore": "^2.3.2", - "geolib": "^2.0.18", - "proj4": "^2.3.10", "query-string": "^3.0.0", "react": "0.14.5", "react-dom": "0.14.5", @@ -70,6 +64,7 @@ "babel-preset-react": "^6.1.18", "eslint": "^1.3.1", "eslint-plugin-react": "^3.15.0", + "nodemon": "^1.8.1", "npm-run-all": "^1.3.4", "reactify": "^1.1.1", "watchify": "^3.2.1" diff --git a/server/database/management/connectToDB.js b/server/database/management/connectToDB.js index f9acd87..0741ebf 100644 --- a/server/database/management/connectToDB.js +++ b/server/database/management/connectToDB.js @@ -1,16 +1,9 @@ 'use strict'; var pg = require('pg'); +var PRIVATE = require('../../../PRIVATE.json'); -var conString = [ - 'postgres://', - process.env.POSTGRES_USER, - ':', - process.env.POSTGRES_PASSWORD, - '@db/postgres' -].join(''); - -console.log('conString', conString); +var conString = 'postgres://'+ PRIVATE.pg_user + ':' + PRIVATE.pg_pwd + '@localhost:5432/' + PRIVATE.db_name; var MAX_ATTEMPTS = 10; var INITIAL_TIMEOUT_TIME = 100; @@ -27,11 +20,14 @@ module.exports = function(){ client.connect(function(err) { if(err){ + console.log("Couldn't connect to db"); if(attempts >= MAX_ATTEMPTS) reject(err); - else + else { // wait twice more to give time and not overwhelm the database with useless attempts to connect + console.log("Retrying in ", 2*time); tryConnect(2*time); + } } else{ resolve(client); diff --git a/server/getMeasures.js b/server/getMeasures.js index 5cb97b7..1a3aa71 100644 --- a/server/getMeasures.js +++ b/server/getMeasures.js @@ -2,6 +2,8 @@ var request = require('request'); var opening_hours = require('opening_hours'); +var moment = require('moment'); +var momentTZ = require('moment-timezone'); function makeSearchString(obj){ @@ -70,22 +72,6 @@ function sendRequest(url, place, attribute){ ); } -var fromUTC = function(str){ - - var tmp = str.split('T'); - var vDate = tmp[0].split('-'); - var vTime = tmp[1].split(':'); - - var yyyy = parseInt(vDate[0]); - var MM = parseInt(vDate[1]); - var dd = parseInt(vDate[2]); - var hh = parseInt(vTime[0]); - var mm = parseInt(vTime[1]); - var ss = parseInt(vTime[2]); - - return new Date(Date.UTC(yyyy,MM-1,dd,hh,mm,ss)); -} - function processMeasures(place, start, end, mode){ var oh = place.opening_hours === null ? undefined : @@ -99,7 +85,7 @@ function processMeasures(place, start, end, mode){ place.measures.today !== undefined){ measures = place.measures.today.map(function(measure){ - var date = fromUTC(measure.date); // UTC -> Local + var date = new Date(measure.date); return { date: date, signals: measure.value.length } }); } @@ -108,17 +94,18 @@ function processMeasures(place, start, end, mode){ place.measures.latest !== undefined) ? place.measures.latest.max: 0; var nbTicksX = (20-8)*4; // every 15 minutes from 8am to 8pm - var now = new Date(); + var now = momentTZ().tz('Europe/Paris').toDate(); // For each tick of 15 minutes - for (var i = 0; i<=nbTicksX; ++i) { + for (var i = 0; i Local + var date = new Date(measure.date); // UTC -> Local return { date: date, bin: measure.value } }) .sort(function(m1, m2){ @@ -186,10 +172,6 @@ function processMeasures(place, start, end, mode){ }) .forEach(function(measure){ - //console.log(measure.date, measure.bin.a); - //console.log(measure.date); - //console.log('iTickXStart', iTickXStart); - // 1) we go Backward: // for each tick unfilled before a measure // we fill with the inverse of status @@ -197,12 +179,10 @@ function processMeasures(place, start, end, mode){ // For each tick of 15 minutes included var iTickXEnd = iTickXStart; - for (var i = iTickXStart; i<=nbTicksX; ++i) { - - var beginTick = new Date(start); - beginTick.setHours(8+Math.floor(i/4),i*15%60, 0); - var endTick = new Date(end); - endTick.setHours(8+Math.floor((i+1)/4),(i+1)*15%60, 0); + for (var i = 0; i now ) break; @@ -226,12 +206,8 @@ function processMeasures(place, start, end, mode){ // 2) For the rest of the day, we expand the last status of avilability for (var i = iTickXStart; i<=nbTicksX; ++i) { - var beginTick = new Date(start); - beginTick.setHours(8+Math.floor(i/4),i*15%60, 0); - var endTick = new Date(end); - endTick.setHours(8+Math.floor((i+1)/4),(i+1)*15%60, 0); - - //if(endTick > now ) break; + var beginTick = moment(start).add(15*i,'minutes').toDate(); + var endTick = moment(start).add(15*(i+1),'minutes').toDate(); // This time, we go forward so do not invert values: var isOpen = oh ? oh.getState(beginTick) : true; @@ -260,21 +236,20 @@ module.exports = function(selection){ return new Promise(function(resolve, reject){ - var start = new Date(selection.date); - start.setHours(8,0,0,0); - var end = new Date(selection.date); - end.setHours(20,0,0,0); + var start = momentTZ.tz(selection.date, 'Europe/Paris').add(8,'hours').toDate(); + var end = momentTZ.tz(selection.date, 'Europe/Paris').add(20,'hours').toDate(); + var parameters = { id: place.pheromon_id, type: undefined, start: start, end: end } - + if( place.pheromon_id === null || place.pheromon_id === undefined){ - place['results'] = processMeasures(place, start, end); + place['results'] = processMeasures(place, parameters.start, parameters.end); return resolve(place); } @@ -291,7 +266,7 @@ module.exports = function(selection){ if(selection.mode === 'citizen'){ // Process measures - placeWithToday['results'] = processMeasures(placeWithToday, start, end, selection.mode); + placeWithToday['results'] = processMeasures(placeWithToday, parameters.start, parameters.end, selection.mode); return resolve(placeWithToday); } else { @@ -302,7 +277,7 @@ module.exports = function(selection){ sendRequest(origin + '/measurements/place/raw' + makeSearchString(parameters), placeWithToday, 'bins') .then(function(placeWithBins){ - placeWithBins['results'] = processMeasures(placeWithBins, start, end, selection.mode); + placeWithBins['results'] = processMeasures(placeWithBins, parameters.start, parameters.end, selection.mode); return resolve(placeWithBins); }) .catch(function(error){ diff --git a/server/index.js b/server/index.js index c46b641..71abfa6 100644 --- a/server/index.js +++ b/server/index.js @@ -7,11 +7,6 @@ var path = require('path'); var PRIVATE = require('../PRIVATE.json'); -// Dumps -var spawn = require('child_process').spawn; -var zlib = require('zlib'); -var schedule = require('node-schedule'); - // Express var express = require('express'); var bodyParser = require('body-parser'); @@ -41,25 +36,6 @@ app.use(bodyParser.urlencoded({extended: true})); app.use(compression()); -// ---------- BACKUPS ---------- -// Backup database everyday at 3AM -if (process.env.NODE_ENV === "production") { - schedule.scheduleJob('0 3 * * *', function(){ - console.log('Backup database'); - var gzip = zlib.createGzip(); - var today = new Date(); - var wstream = fs.createWriteStream('/backups/' + today.getDay() + '.sql.gz'); - var proc = spawn('pg_dump', ['-p', process.env.DB_PORT_5432_TCP_PORT, '-h', process.env.DB_PORT_5432_TCP_ADDR, '-U', process.env.POSTGRES_USER, '-w']); - proc.stdout - .pipe(gzip) - .pipe(wstream); - proc.stderr.on('data', function(buffer) { - console.log(buffer.toString().replace('\n', '')); - }); - }); -} - - // ---------- SOCKETS ---------- // Frontside sockets to activate //var io6element = require('socket.io')(server); @@ -136,7 +112,7 @@ app.get('/decheteries.html', function(req,res){ } if(getPlaces === undefined) - return redirectError(res, "Erreur de traitement, veuillez renouveller votre recherche"); + return redirectError(res, "Erreur de traitement, veuillez renouveler votre recherche"); // DB places getPlaces @@ -162,17 +138,17 @@ app.get('/decheteries.html', function(req,res){ }) .catch(function(err){ console.error('/', err, err.stack); - redirectError(res, "Erreur de traitement, veuillez renouveller votre recherche"); + redirectError(res, "Erreur de traitement, veuillez renouveler votre recherche"); }); }) .catch(function(err){ console.error('/', err, err.stack); - redirectError(res, "Erreur de traitement, veuillez renouveller votre recherche"); + redirectError(res, "Erreur de traitement, veuillez renouveler votre recherche"); }); }) .catch(function(err){ console.error('/', err, err.stack); - redirectError(res, "Erreur de traitement, veuillez renouveller votre recherche"); + redirectError(res, "Erreur de traitement, veuillez renouveler votre recherche"); }); }); diff --git a/todo.md b/todo.md index 00a9b99..56f76ca 100644 --- a/todo.md +++ b/todo.md @@ -1,4 +1,3 @@ -- [ ] le watch sur JSX fontionne mais ne modifie par le serveur - [ ] Sockets - [ ] debugger css sur tel Romain - [ ] Photo d'accueil @@ -29,6 +28,11 @@ - [x] Mettre le owner dans les infos ? -> (Les noms sont bizarres Agglo_Pau) - [x] redirect operator:name - [x] Il n'y a aucune décheterie à proximité +- [X] le watch sur JSX fontionne mais ne modifie par le serveur +- [X] Corriger faute d'orthographe sur le message d'erreur +- [X] Google Analytics +- [X] Découper les courbes sur les périodes discontinues +- [X] Bug de décalage de dates delete from places where opening_hours is null; \ No newline at end of file diff --git a/tools/backup.js b/tools/backup.js deleted file mode 100755 index 0522059..0000000 --- a/tools/backup.js +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env node - -"use strict"; - -var spawn = require('child_process').spawn; - -spawn('pg_dump', ['-p', process.env.DB_PORT_5432_TCP_PORT, '-h', process.env.DB_PORT_5432_TCP_ADDR, '-U', process.env.POSTGRES_USER, '-w'], {stdio: 'inherit'}); diff --git a/tools/restore.js b/tools/restore.js deleted file mode 100755 index 322179b..0000000 --- a/tools/restore.js +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env node - -"use strict"; - -require('es6-shim'); - -var child_process = require('child_process'); -var fs = require('fs') -var spawn = child_process.spawn; -var zlib = require('zlib'); - -var connectToDB = require('../server/database/management/connectToDB.js'); -var dropAllTables = require('../server/database/management/dropAllTables.js'); - -var inputFile = process.argv[2]; - -connectToDB() -.then(function(){ - return dropAllTables() - .catch(function(err){ - console.error("Couldn't drop tables", err); - process.exit(); - }) - .then(function(){ - console.log("Loading the data"); - if (inputFile.includes("gz")) { - console.log("Gz format") - var gzip = zlib.createGunzip(); - var readStream = fs.createReadStream(inputFile); - var proc = spawn('psql', ['-p', process.env.DB_PORT_5432_TCP_PORT, '-h', process.env.DB_PORT_5432_TCP_ADDR, '-U', process.env.POSTGRES_USER, '-d', process.env.POSTGRES_USER]); - - return new Promise(function(resolve, reject){ - readStream - .pipe(gzip) - .pipe(proc.stdin) - .on('finish', function() { - resolve(); - }) - .on('error', function(error) { - reject(error); - }) - }); - } - else - spawn('psql', ['-p', process.env.DB_PORT_5432_TCP_PORT, '-h', process.env.DB_PORT_5432_TCP_ADDR, '-U', process.env.POSTGRES_USER, '-w', '-f', inputFile]); - }) - .catch(function(err){ - console.error("Couldn't load the data", err); - process.exit(); - }) - .then(function(){ - console.log("Success!"); - process.exit(); - }) -}) -.catch(function(err){ - console.error("Couldn't connect to database", err); -});