diff --git a/.bowerrc b/.bowerrc
new file mode 100644
index 0000000..5e0e574
--- /dev/null
+++ b/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "public/bower"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a88195f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules
+bower
+build
+
+*~
+*.log
+*.env
+*.migrate
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..740279e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Quentin PANISSIER
+
+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.
diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..ae7366d
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: node web.js
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/VERSION b/VERSION
new file mode 100644
index 0000000..fc65695
--- /dev/null
+++ b/VERSION
@@ -0,0 +1 @@
+1.0.0 ( https://github.com/hardware/ovh-availability/releases/tag/v1.0.0 )
diff --git a/bin/cron b/bin/cron
new file mode 100755
index 0000000..0ad7f02
--- /dev/null
+++ b/bin/cron
@@ -0,0 +1,15 @@
+#!/usr/bin/env node
+
+var argv = require('optimist').argv;
+var request = require('request');
+
+var options = {
+ method: 'GET',
+ uri: process.env.APP_URL + 'cron/' + argv.task + '/' + process.env.CRON_KEY
+};
+
+request(options, function( error, response, body ) {
+
+ if( ! error && response.statusCode == 200 ) console.log( body );
+
+});
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..cd094d6
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,23 @@
+{
+ "name": "ovh-availability",
+ "version": "1.0.0",
+ "description": "Application permettant de connaitre la disponibilité des serveurs d'OVH (SoYourStart, Kimsufi)",
+ "main": "web.js",
+ "moduleType": [
+ "node"
+ ],
+ "license": "MIT",
+ "homepage": "https://github.com/hardware/ovh-availability",
+ "private": true,
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components"
+ ],
+ "dependencies": {
+ "jquery": "1.11.1",
+ "bootstrap": "3.2.0",
+ "html5shiv": "3.7.2",
+ "respond": "1.4.2"
+ }
+}
diff --git a/database/schema.dbm b/database/schema.dbm
new file mode 100644
index 0000000..f1b9d59
--- /dev/null
+++ b/database/schema.dbm
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/migrations/001-init.js b/migrations/001-init.js
new file mode 100644
index 0000000..c98328c
--- /dev/null
+++ b/migrations/001-init.js
@@ -0,0 +1,309 @@
+var pg = require('pg');
+var S = require('string');
+var async = require('async');
+
+var DB_URL = process.env.DATABASE_URL || 'tcp://localhost:5432/ovh-availability';
+
+exports.up = function( next ) {
+
+ pg.connect(DB_URL, function( dbErr, client, done ) {
+
+ async.series([
+
+ // Création de l'énumération "SERVERS_TYPE"
+ function( callback ) {
+
+ var q = client.query("CREATE TYPE public.servers_type AS ENUM ('sys','kimsufi')");
+
+ q.on('end', function() {
+ callback();
+ });
+
+ },
+
+ // Création de l'énumération "REQUEST_STATE"
+ function( callback ) {
+
+ var q = client.query("CREATE TYPE public.request_state AS ENUM ('pending','done')");
+
+ q.on('end', function() {
+ callback();
+ });
+
+ },
+
+ // Création de la table "SERVERS"
+ function( callback ) {
+
+ var q = client.query(" \
+ CREATE TABLE public.servers( \
+ id serial NOT NULL, \
+ type public.servers_type NOT NULL, \
+ name character varying(100) NOT NULL, \
+ reference character varying(10) NOT NULL, \
+ CONSTRAINT id_servers_pm PRIMARY KEY (id), \
+ CONSTRAINT unique_ref UNIQUE (reference) \
+ )");
+
+ q.on('end', function() {
+ callback();
+ });
+
+ },
+
+ // Création de la table "REQUESTS"
+ function( callback ) {
+
+ var q = client.query(" \
+ CREATE TABLE public.requests( \
+ id serial NOT NULL, \
+ reference character varying(10) NOT NULL, \
+ mail character varying(100) NOT NULL, \
+ date timestamp with time zone NOT NULL DEFAULT NOW(), \
+ state public.request_state NOT NULL DEFAULT 'pending', \
+ CONSTRAINT id_requests_pm PRIMARY KEY (id) \
+ )");
+
+ q.on('end', function() {
+ callback();
+ });
+
+ },
+
+ // Création de la relation entre la table 'servers' et 'requests'
+ function( callback ) {
+
+ var q = client.query("ALTER TABLE public.requests ADD CONSTRAINT ref_servers_fk FOREIGN KEY (reference) \
+ REFERENCES public.servers (reference) MATCH FULL \
+ ON DELETE NO ACTION ON UPDATE NO ACTION;");
+
+ q.on('end', function() {
+ callback();
+ });
+
+ },
+
+ // Ajout des serveurs d'OVH dans la table 'servers'
+ function( callback ) {
+
+ async.parallel([
+
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'E3-SAT-1', '143sys4')", function( err, result ) {
+ callback();
+ });
+ },
+
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'E3-SAT-1', '143sys4')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'E3-SSD-1', '143sys13')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'E3-SAT-2', '143sys1')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'E3-SSD-2', '143sys10')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'E3-SAT-3', '143sys2')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'E3-SSD-3', '143sys11')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'E3-SAT-4', '143sys3')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'E3-SSD-4', '143sys12')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'SYS-IP-1', '142sys4')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'SYS-IP-2', '142sys5')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'SYS-IP-4', '142sys8')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'SYS-IP-5', '142sys6')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'SYS-IP-5S', '142sys10')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'SYS-IP-6', '142sys7')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'SYS-IP-6S', '142sys9')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'BK-8T', '141bk1')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'BK-24T', '141bk2')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'GAME-1', '141game1')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'GAME-2', '141game2')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('sys', 'GAME-3', '141game3')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('kimsufi', 'KS-1', '150sk10')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('kimsufi', 'KS-2a', '150sk20')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('kimsufi', 'KS-2b', '150sk21')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('kimsufi', 'KS-2c', '150sk22')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('kimsufi', 'KS-3', '150sk30')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('kimsufi', 'KS-4', '150sk40')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('kimsufi', 'KS-5', '150sk50')", function( err, result ) {
+ callback();
+ });
+ },
+ function( callback ) {
+ client.query("INSERT INTO servers (type, name, reference) VALUES ('kimsufi', 'KS-6', '150sk60')", function( err, result ) {
+ callback();
+ });
+ }
+ ], function( err, results ) {
+
+ callback();
+
+ });
+
+ }
+
+ ], function( err, results ) {
+
+ if( ! err ) next();
+
+ });
+
+ });
+
+};
+
+exports.down = function( next ) {
+
+ pg.connect(DB_URL, function( dbErr, client, done ) {
+
+ async.series([
+
+ function( callback ) {
+
+ var q = client.query("DROP TABLE servers CASCADE");
+
+ q.on('end', function() {
+ callback();
+ });
+
+ },
+
+ function( callback ) {
+
+ var q = client.query("DROP TABLE requests CASCADE");
+
+ q.on('end', function() {
+ callback();
+ });
+
+ },
+
+ function( callback ) {
+
+ var q = client.query("DROP TYPE servers_type");
+
+ q.on('end', function() {
+ callback();
+ });
+
+ },
+
+ function( callback ) {
+
+ var q = client.query("DROP TYPE request_state");
+
+ q.on('end', function() {
+ callback();
+ });
+
+ }
+
+ ], function( err, results ) {
+
+ if( ! err ) next();
+
+ });
+
+ });
+
+};
diff --git a/models/requests.js b/models/requests.js
new file mode 100644
index 0000000..19b0361
--- /dev/null
+++ b/models/requests.js
@@ -0,0 +1,65 @@
+var pg = require('pg');
+var error = require('../routes/errorHandler');
+
+/*
+ * Ajout d'une nouvelle demande dans la base de données
+ */
+exports.add = function( data, next, callback ) {
+
+ pg.connect(process.env.DATABASE_URL, function( err, client, done ) {
+
+ client.query('INSERT INTO public.requests( reference, mail, date, state ) \
+ VALUES( $1, $2, DEFAULT, DEFAULT)',
+
+ [ data.reference, data.mail ], function( err, result ) {
+
+ if( error.handler( err, client, done, next ) ) return;
+ done();
+
+ if( result.rowCount == 1 )
+ callback( true );
+ else
+ callback( false );
+
+ });
+ });
+
+};
+
+/*
+ * Permet de récupérer l'ensemble des demandes en attentes ( pending )
+ */
+exports.getPendingRequests = function( next, callback ) {
+
+ pg.connect(process.env.DATABASE_URL, function( err, client, done ) {
+ client.query("SELECT r.id, r.reference, r.mail, s.type, s.name \
+ FROM public.requests r \
+ LEFT JOIN public.servers s ON s.reference = r.reference \
+ WHERE r.state = $1",
+ [ 'pending' ], function( err, result ) {
+
+ if( error.handler( err, client, done, next ) ) return;
+ done();
+
+ callback( result.rows );
+
+ });
+ });
+
+};
+
+/*
+ * Mise à jour de l'état de la demande ( pending -> done )
+ */
+exports.updateState = function( requestId, next ) {
+
+ pg.connect(process.env.DATABASE_URL, function( err, client, done ) {
+ client.query('UPDATE public.requests SET state = $1 WHERE id = $2', [ 'done', requestId ], function( err, result ) {
+
+ if( error.handler( err, client, done, next ) ) return;
+ done();
+
+ });
+ });
+
+};
diff --git a/models/servers.js b/models/servers.js
new file mode 100644
index 0000000..13cd03f
--- /dev/null
+++ b/models/servers.js
@@ -0,0 +1,56 @@
+var pg = require('pg');
+var async = require('async');
+var error = require('../routes/errorHandler');
+
+/*
+ * Permet de récupérer l'ensemble des offres d'OVH par gamme ( sys/kimsufi )
+ */
+exports.getServers = function( type, next, callback ) {
+
+ pg.connect(process.env.DATABASE_URL, function( err, client, done ) {
+ client.query("SELECT * FROM public.servers WHERE type = $1", [ type ], function( err, result ) {
+
+ if( error.handler( err, client, done, next ) ) return;
+ done();
+
+ callback( result.rows );
+
+ });
+ });
+
+};
+
+/*
+ * Permet de récupérer l'ensemble des références des serveurs d'OVH
+ */
+exports.getAllRefs = function( next, callback ) {
+
+ pg.connect(process.env.DATABASE_URL, function( err, client, done ) {
+ client.query("SELECT reference FROM public.servers", function( err, result ) {
+
+ if( error.handler( err, client, done, next ) ) return;
+ done();
+
+ var refsList = [];
+
+ async.each(result.rows, function( row, nextRow ) {
+
+ refsList.push( row.reference );
+ nextRow();
+
+ }, function( err ) {
+
+ if( err ) {
+ next( err );
+ return;
+ }
+
+ callback( refsList );
+
+ });
+
+ });
+ });
+
+};
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..7f7c6ca
--- /dev/null
+++ b/package.json
@@ -0,0 +1,47 @@
+{
+ "name": "ovh-availability",
+ "version": "1.0.0",
+ "description": "Application permettant de connaitre la disponibilité des serveurs d'OVH (SoYourStart, Kimsufi)",
+ "main": "web.js",
+ "scripts": {
+ "start": "node web.js",
+ "postinstall": "./node_modules/bower/bin/bower -s install"
+ },
+ "dependencies": {
+ "async": "0.x",
+ "body-parser": "1.9.x",
+ "bower": "1.3.x",
+ "compression": "1.1.x",
+ "cookie-parser": "1.3.x",
+ "csurf": "1.6.x",
+ "db-migrate": "0.8.x",
+ "debug": "2.x",
+ "errorhandler": "1.2.x",
+ "express": "4.9.x",
+ "express-session": "1.8.x",
+ "express-validator": "2.6.x",
+ "jade": "1.x",
+ "migrate": "^0.1.6",
+ "morgan": "1.3.x",
+ "optimist": "0.6.x",
+ "pg": "3.4.x",
+ "request": "2.x",
+ "sendgrid": "1.2.x",
+ "string": "2.1.x"
+ },
+ "devDependencies": {
+ "express-debug": "1.1.x"
+ },
+ "engines": {
+ "node": "0.10.x"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/hardware/ovh-availability.git"
+ },
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/hardware/ovh-availability/issues"
+ },
+ "homepage": "https://github.com/hardware/ovh-availability"
+}
diff --git a/public/css/template.css b/public/css/template.css
new file mode 100644
index 0000000..30a7f15
--- /dev/null
+++ b/public/css/template.css
@@ -0,0 +1,103 @@
+body {
+ padding-top: 20px;
+ padding-bottom: 20px;
+}
+
+pre {
+ font-size:10px;
+ white-space:pre;
+ color:firebrick;
+ margin-bottom:20px;
+}
+
+input {
+ margin-right: 5px;
+}
+
+.header,
+.footer {
+ padding-right: 15px;
+ padding-left: 15px;
+}
+
+.footer {
+ padding-top: 19px;
+ color: #777;
+ border-top: 1px solid #e5e5e5;
+}
+
+.btn-group.space-right {
+ margin-right:10px;
+}
+
+.bs-callout {
+ padding: 20px;
+ margin: 20px 0px;
+ border-width: 1px 1px 1px 5px;
+ border-style: solid;
+ border-color: #EEE;
+ -moz-border-top-colors: none;
+ -moz-border-right-colors: none;
+ -moz-border-bottom-colors: none;
+ -moz-border-left-colors: none;
+ border-image: none;
+ border-radius: 3px;
+}
+
+.bs-callout-info {
+ border-left-color: #5BC0DE;
+}
+
+.bs-callout-danger {
+ border-left-color: #D9534F;
+}
+
+.bs-callout-warning {
+ border-left-color: #F0AD4E;
+}
+
+.bs-callout h4 {
+ margin-top: 0;
+ margin-bottom: 10px;
+}
+
+.bs-callout-info h4 {
+ color: #5bc0de;
+}
+
+.align-right {
+ text-align: right;
+}
+
+.align-center {
+ text-align: center;
+}
+
+span.help-block {
+ color: #A94442;
+}
+
+
+a.nostyle {
+ text-decoration:none;
+}
+
+@media (min-width: 768px) {
+ .container {
+ max-width: 820px;
+ }
+}
+
+@media screen and (min-width: 768px) {
+
+ .header,
+ .footer {
+ padding-right: 0;
+ padding-left: 0;
+ }
+
+ .header {
+ margin-bottom: 30px;
+ }
+
+}
diff --git a/public/img/.gitkeep b/public/img/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/public/js/.gitkeep b/public/js/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/routes/cron.js b/routes/cron.js
new file mode 100644
index 0000000..fde6b7c
--- /dev/null
+++ b/routes/cron.js
@@ -0,0 +1,132 @@
+var async = require('async');
+var request = require('request');
+var mailer = require('./mailer');
+var requestModel = require('../models/requests');
+
+/*
+ * Traitement de l'ensemble des demandes en attentes
+ * Route : /cron/handleRequests/:secureKey
+ * Methode : GET
+ */
+exports.handleRequests = function( req, res, next ) {
+
+ checkSecureKey(req, res, req.params.secureKey, function() {
+ requestModel.getPendingRequests(next, function( pendingRequests ) {
+ request(process.env.OVH_API_URL, function( error, response, body ) {
+
+ if( ! error && response.statusCode == 200 ) {
+
+ var data = JSON.parse( body );
+
+ async.each(pendingRequests, function( request, nextRequest ) {
+ async.each(data.answer.availability, function( offer, nextOffer ) {
+
+ if( offer.reference == request.reference ) {
+
+ var availableZones = 0
+
+ async.each(offer.zones, function( zone, nextZone ) {
+
+ if( zone.availability != 'unknown' && zone.availability != 'unavailable' )
+ availableZones++;
+
+ nextZone();
+
+ }, function( err ) {
+
+ if( err ) { next( err ); return; }
+
+ if( availableZones > 0 )
+ inform( request, next );
+
+ });
+
+ }
+
+ nextOffer();
+
+ }, function( err ) {
+
+ if( err ) { next( err ); return; }
+
+ nextRequest();
+
+ });
+
+ }, function( err ) {
+
+ if( err ) { next( err ); return; }
+
+ res.send('PROCESSING REQUESTS COMPLETED !');
+
+ });
+
+ }
+
+ });
+ });
+ });
+
+};
+
+/*
+ * Permet d'informer l'utilisateur par mail de la disponibilité d'un offre d'OVH
+ */
+var inform = function( request, next ) {
+
+ var orderUrl = ''
+
+ switch( request.type ) {
+ case 'sys':
+ orderUrl = 'https://eu.soyoustart.com/fr/commande/soYouStart.xml?reference=' + request.reference
+ break;
+ case 'kimsufi':
+ orderUrl = 'https://www.kimsufi.com/fr/commande/kimsufi.xml?reference=' + request.reference
+ break;
+ }
+
+ var payload = {
+ to:request.mail,
+ from:'nepasrepondre@ovh-availability',
+ subject:'[ovh-availability] Votre serveur est disponible ( ' + request.name + ' )',
+ html:"
Bonjour,
\
+ Le serveur " + request.name + " est disponible.
\
+ Pour le réserver, cliquez sur le lien ci-dessous :
\
+ Commander \
+ Si vous avez raté l'offre, vous pouvez toujours refaire une demande via :
\
+ " + process.env.APP_URL + " \
+ A très bientôt sur ovh-availability
"
+ };
+
+ mailer.send( payload, next );
+
+ // Mise à jour de l'état de la demande ( pending -> done )
+ requestModel.updateState( request.id, next );
+
+}
+
+/*
+ * Vérification de la clé sécurisée
+ */
+var checkSecureKey = function( req, res, secureKey, callback ) {
+
+ if( secureKey === process.env.CRON_KEY )
+ callback();
+ else
+ res.send('invalid secure key ! :(');
+
+}
+
+/*
+ ####### Heroku scheduler #######
+
+ COMMAND FREQUENCY
+--------------------------------------------
+| cron --task handleRequests | every 10min |
+--------------------------------------------
+
+Note :
+ - pour la commande cron voir le fichier bin/cron
+ - pour paramétrer le scheduler : heroku addons:open scheduler
+
+*/
diff --git a/routes/data.js b/routes/data.js
new file mode 100644
index 0000000..58d5e97
--- /dev/null
+++ b/routes/data.js
@@ -0,0 +1,43 @@
+exports.settings = function( req, res, options, callback ) {
+
+ var isLogged = ( req.session.user ) ? true : false;
+
+ // Contrôle de la session dans les zones restreintes
+ if( !! options.shouldBeLogged && ! isLogged ) {
+ res.redirect('/user/login?url=' + req.url);
+ return;
+ }
+
+ if( ! options.shouldBeLogged && isLogged ) {
+
+ if( ! options.mayBeLogged ) {
+ res.redirect('/account');
+ return;
+ }
+
+ }
+
+ var settings = {
+ path:req.path,
+ title:"OVH Disponibilité",
+ isLogged:isLogged
+ };
+
+ if( isLogged ) {
+
+ var isAdmin = ( req.session.user.type === 'admin' ) ? true : false;
+
+ settings.user = req.session.user;
+ settings.isAdmin = isAdmin;
+
+ // Contrôle de la session dans les zones restreintes
+ if( !! options.shouldBeAdmin && ! isAdmin ) {
+ res.redirect('/account');
+ return;
+ }
+
+ }
+
+ callback( settings );
+
+};
diff --git a/routes/errorHandler.js b/routes/errorHandler.js
new file mode 100644
index 0000000..e5b6185
--- /dev/null
+++ b/routes/errorHandler.js
@@ -0,0 +1,11 @@
+exports.handler = function( err, client, done, next ) {
+
+ if( ! err ) return false;
+
+ err.status = 500;
+ done( client );
+ next( err );
+
+ return true;
+
+};
diff --git a/routes/index.js b/routes/index.js
new file mode 100644
index 0000000..d7b5de5
--- /dev/null
+++ b/routes/index.js
@@ -0,0 +1,132 @@
+var async = require('async');
+var data = require('./data');
+var recaptcha = require('./recaptcha');
+var serversModel = require('../models/servers');
+var requestModel = require('../models/requests');
+
+/*
+ * INDEX
+ * Route : /
+ * Methode : GET
+ */
+exports.index = function( req, res, next ) {
+
+ data.settings(req, res, { shouldBeLogged:false, mayBeLogged:true }, function( settings ) {
+
+ serversModel.getServers('sys', next, function( sysServersList ) {
+ serversModel.getServers('kimsufi', next, function( kimServersList ) {
+
+ settings.formErrors = {};
+ settings.sysServersList = sysServersList;
+ settings.kimServersList = kimServersList;
+
+ res.render('index', settings);
+
+ });
+ });
+
+ });
+
+};
+
+/*
+ * INDEX
+ * Route : /
+ * Methode : POST
+ */
+exports.run = function( req, res, next ) {
+
+ data.settings(req, res, { shouldBeLogged:false, mayBeLogged:true }, function( settings ) {
+
+ // Récupération des ressources
+ serversModel.getServers('sys', next, function( sysServersList ) {
+ serversModel.getServers('kimsufi', next, function( kimServersList ) {
+ serversModel.getAllRefs(next, function( refsList ) {
+
+ // Validation des valeurs contenues dans req.body
+ req.checkBody('mail', 'La valeur de ce champ est invalide.').isEmail().len(5, 100);
+ req.checkBody('mail', 'Ce champ est requis.').notEmpty();
+ req.checkBody('server', 'La valeur de ce champ est invalide.').isIn( refsList );
+ req.checkBody('server', 'Ce champ est requis.').notEmpty();
+
+ var errors = req.validationErrors( true );
+
+ async.waterfall([
+
+ // Vérification du formulaire
+ function( callback ) {
+
+ if( errors )
+ callback("Une erreur est survenue lors de la validation du formulaire, veuillez vérifier les données saisies.");
+ else
+ callback();
+
+ },
+
+ // Vérification du captcha
+ function( callback ) {
+
+ recaptcha.verify(req, req.body["g-recaptcha-response"], next, function( result ) {
+
+ if( ! result )
+ callback("Veuillez cocher la case située à la fin du formulaire afin de prouver que vous êtes bien humain.");
+ else
+ callback();
+
+ });
+
+ },
+
+ // Ajout de la demande au sein de la base de données
+ function( callback ) {
+
+ var data = {
+ reference:req.body.server,
+ mail:req.body.mail
+ };
+
+ requestModel.add(data, next, function( result ) {
+
+ if( result ) {
+
+ callback();
+
+ } else {
+
+ callback('Une erreur est survenue lors de l\'enregistrement de votre demande dans la base de données.');
+
+ }
+
+ });
+
+ }
+
+ ], function( err, result ) {
+
+ if( err ) {
+
+ settings.formError = true;
+ settings.formMessage = err;
+
+ } else {
+
+ settings.formSuccess = true;
+ settings.formMessage = 'Votre demande a bien été prise en compte.';
+
+ }
+
+ settings.formErrors = ( errors ) ? errors : {};
+ settings.sysServersList = sysServersList;
+ settings.kimServersList = kimServersList;
+
+ res.render('index', settings);
+
+ });
+
+ });
+ });
+ });
+
+ });
+
+};
diff --git a/routes/mailer.js b/routes/mailer.js
new file mode 100644
index 0000000..2854cb9
--- /dev/null
+++ b/routes/mailer.js
@@ -0,0 +1,106 @@
+var async = require('async');
+var sendgrid = require('sendgrid')(
+ process.env.SENDGRID_USERNAME,
+ process.env.SENDGRID_PASSWORD
+);
+
+exports.send = function( email, next ) {
+
+ async.waterfall([
+ function( callback ) {
+
+ var fullBody = '';
+
+ var prefix = getPrefix();
+ var suffix = getSuffix();
+ var body = email.html;
+
+ fullBody += prefix;
+ fullBody += body;
+ fullBody += suffix;
+
+ callback( null, fullBody );
+ },
+ function( fullBody, callback ) {
+
+ email.html = fullBody;
+
+ callback( null, email );
+ }
+ ],
+
+ function( err, email ) {
+
+ if( err ) {
+
+ next( err );
+ return;
+
+ } else {
+
+ sendgrid.send(email, function( err, json ) {
+
+ if( err )
+ next(new Error("Une erreur est survenue pendant l'exécution du module de mail [ " + err + " ]"));
+
+ });
+
+ }
+
+ });
+
+};
+
+var getPrefix = function() {
+
+ var html = ' \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ \
+ ';
+
+ return html;
+};
+
+var getSuffix = function( callback ) {
+
+ var html = ' | | |
\
+ ';
+
+ return html;
+
+};
diff --git a/routes/recaptcha.js b/routes/recaptcha.js
new file mode 100644
index 0000000..f79f5e9
--- /dev/null
+++ b/routes/recaptcha.js
@@ -0,0 +1,32 @@
+var request = require('request')
+
+exports.verify = function( req, response, next, callback ) {
+
+ var ip = req.headers['x-forwarded-for'] ||
+ req.connection.remoteAddress ||
+ req.socket.remoteAddress ||
+ req.connection.socket.remoteAddress;
+
+ var url = "https://www.google.com/recaptcha/api/siteverify?secret=" + process.env.RECAPTCHA_PRIVATE_KEY + "&response=" + response + "&remoteip=" + ip
+
+ request(url, function( error, response, body ) {
+
+ if ( error || response.statusCode != 200 ) {
+
+ next( error );
+ return;
+
+ } else {
+
+ var result = JSON.parse( body );
+
+ if( result.success )
+ callback( true );
+ else
+ callback( false );
+
+ }
+
+ });
+
+};
diff --git a/views/error.jade b/views/error.jade
new file mode 100644
index 0000000..95fddc0
--- /dev/null
+++ b/views/error.jade
@@ -0,0 +1,8 @@
+extends layout
+
+block content
+ hr
+ .alert.alert-danger!= 'ERREUR ' + ( ( error.status ) ? error.status : '' ) + ' : ' + error.message
+
+ if error.stack
+ pre= '[ STACK TRACE ] ' + error.stack
diff --git a/views/includes/footer.jade b/views/includes/footer.jade
new file mode 100644
index 0000000..a20b551
--- /dev/null
+++ b/views/includes/footer.jade
@@ -0,0 +1,4 @@
+.footer
+ p
+ | © OVH-AVAILABILITY 2014
+ | v1.0.0
diff --git a/views/includes/header.jade b/views/includes/header.jade
new file mode 100644
index 0000000..bae291f
--- /dev/null
+++ b/views/includes/header.jade
@@ -0,0 +1 @@
+.header
diff --git a/views/includes/javascript.jade b/views/includes/javascript.jade
new file mode 100644
index 0000000..922f758
--- /dev/null
+++ b/views/includes/javascript.jade
@@ -0,0 +1 @@
+script(src='/bower/bootstrap/dist/js/bootstrap.min.js')
diff --git a/views/index.jade b/views/index.jade
new file mode 100644
index 0000000..49f10ba
--- /dev/null
+++ b/views/index.jade
@@ -0,0 +1,50 @@
+extends layout
+
+block content
+
+ div(class='bs-callout bs-callout-info' id='info-top')
+ h4.align-center Disponibilité des offres OVH
+ hr
+ p
+ | Pour connaitre la disponibilité des offres d'OVH (SoYourStart / Kimsufi), choisissez un serveur parmis la liste ci-dessous et ajoutez votre adresse mail.
+ | Vous serez prévenu par email lorsque le serveur souhaité sera disponible.
+
+ if formSuccess
+ .alert.alert-success !{formMessage}
+ else
+
+ if formError
+ .alert.alert-danger #{formMessage}
+
+ form(action='/' method='post' class='form-horizontal' role='form')
+
+ input(type='hidden' name='_csrf' value=token)
+
+ .form-group
+ label(for='server' class='col-sm-4 control-label') Liste des serveurs :
+ .col-sm-5
+ select(class='form-control' name='server')
+ option(value='default') Choisir...
+ optgroup(label='Gamme SoYouStart :')
+ for server in sysServersList
+ option(value=server.reference) #{server.name}
+ optgroup(label='Gamme Kimsufi :')
+ for server in kimServersList
+ option(value=server.reference) #{server.name}
+ if formErrors["server"]
+ span.help-block #{formErrors["server"]["msg"]}
+
+ .form-group
+ label(for='mail' class='col-sm-4 control-label') Email :
+ .col-sm-5
+ input(type='mail' name='mail' class='form-control' id='mail' placeholder='Votre adresse email')
+ if formErrors["mail"]
+ span.help-block #{formErrors["mail"]["msg"]}
+
+ div(class='form-group' id='recaptcha')
+ .col-sm-offset-4.col-sm-10
+ div(class='g-recaptcha' data-sitekey=recaptchaKey)
+
+ .form-group
+ .col-sm-offset-4.col-sm-10
+ button(type='submit' class='btn btn-success') Valider
diff --git a/views/layout.jade b/views/layout.jade
new file mode 100644
index 0000000..3937a75
--- /dev/null
+++ b/views/layout.jade
@@ -0,0 +1,33 @@
+mixin ie(condition)
+ |
+
+doctype html
+html
+ head
+ meta(charset='utf-8')
+ meta(http-equiv='X-UA-Compatible' content='IE=edge')
+ meta(name='viewport' content='width=device-width, initial-scale=1')
+ title=title
+
+ link(href='/bower/bootstrap/dist/css/bootstrap.min.css', rel='stylesheet')
+ link(href='/css/template.css', rel='stylesheet')
+
+ +ie('if lt IE 9')
+ script(src='/bower/html5shiv/dist/html5shiv.js')
+ script(src='/bower/respond/dest/respond.min.js')
+
+ script(src='/bower/jquery/dist/jquery.min.js')
+ script(src='https://www.google.com/recaptcha/api.js' async defer)
+
+ body
+ .container
+ include includes/header
+
+ .content
+ block content
+
+ include includes/footer
+
+ include includes/javascript
diff --git a/web.js b/web.js
new file mode 100644
index 0000000..7ace371
--- /dev/null
+++ b/web.js
@@ -0,0 +1,83 @@
+var express = require('express');
+var http = require('http');
+var path = require('path');
+var logger = require('morgan');
+var compression = require('compression');
+var cookieParser = require('cookie-parser');
+var bodyParser = require('body-parser');
+var session = require('express-session');
+var validator = require('express-validator');
+var csrf = require('csurf');
+var errorHandler = require('errorhandler');
+
+var routes = require('./routes');
+var cron = require('./routes/cron');
+
+var app = express();
+
+// URL de la base de donnée ( prod / dev )
+process.env.DATABASE_URL = process.env.DATABASE_URL || 'tcp://localhost:5432/ovh-availability';
+
+app.set('env', process.env.ENV || 'development');
+app.set('port', process.env.PORT);
+app.set('views', path.join(__dirname, 'views'));
+app.set('view engine', 'jade');
+
+if(app.get('env') == 'development') {
+ app.use(logger('dev'));
+ var edt = require('express-debug');
+ edt(app);
+}
+
+app.use(compression());
+app.use(bodyParser.json());
+app.use(bodyParser.urlencoded({ extended: false }));
+app.use(validator());
+app.use(cookieParser(process.env.COOKIES_SECRET));
+app.use(session({ secret: process.env.SESSION_SECRET, key: 'SID', resave:true, saveUninitialized:true }));
+app.use(csrf());
+
+app.use(function( req, res, next ) {
+ res.locals.token = req.csrfToken();
+ res.locals.recaptchaKey = process.env.RECAPTCHA_PUBLIC_KEY;
+ next();
+});
+
+app.use(express.static(path.join(__dirname, 'public')));
+
+
+/*
+ * ROUTES
+ */
+
+
+// INDEX
+app.get('/', routes.index);
+app.post('/', routes.run);
+
+// CRON
+app.get('/cron/handleRequests/:secureKey', cron.handleRequests);
+
+if(app.get('env') == 'development') {
+ app.use(errorHandler());
+}
+
+/*
+ * ERREUR 404
+ */
+app.use(function( req, res, next ) {
+ var err = new Error('Page introuvable');
+ err.status = 404;
+ next( err );
+});
+
+/*
+ * TOUTES LES AUTRES ERREURS
+ */
+app.use(function( err, req, res, next ) {
+ res.render('error', { title:'Erreur', error:err });
+});
+
+var server = app.listen(app.get('port'), function() {
+ console.log('Express server listening on port %d', app.get('port'));
+});