test feature implemented comletly
authorebelcrom <ebelcrom@gmail.com>
Thu, 26 Dec 2019 21:45:51 +0000 (22:45 +0100)
committerebelcrom <ebelcrom@gmail.com>
Thu, 26 Dec 2019 21:45:51 +0000 (22:45 +0100)
26 files changed:
.gitignore
app.js
init/initcloud.js [new file with mode: 0644]
init/initdb.js
init/vapid.json [new file with mode: 0644]
lib/dblib.js
lib/logger.js
package-lock.json
package.json
public/images/closed.jpg
public/images/moving.jpg [deleted file]
public/images/open.jpg
public/index.html [deleted file]
public/stylesheets/style.css [deleted file]
routes/index.js
routes/v1/control.js
routes/v1/events.js
routes/v1/notification.js [new file with mode: 0644]
routes/v1/status.js
routes/v1/test/control.js
routes/v1/test/events.js
routes/v1/test/notification.js [new file with mode: 0644]
routes/v1/test/settings.js
routes/v1/test/status.js
spec/garnod.json
spec/garnod.yaml

index d1bed128fa83b3ed094d8387f4b8f19c019e2ae8..89dae9c49b2aca13cea3f593eee51382f84ecb98 100644 (file)
@@ -1,3 +1,6 @@
+# User defined
+public/mVk7Yr3k/
+
 # Logs
 logs
 *.log
diff --git a/app.js b/app.js
index 23ac4a9ef949a10debba89a1e5b47dff26c6cdb3..986a6b037a51f9ebe88d86dd430b5658fab4a44c 100644 (file)
--- a/app.js
+++ b/app.js
@@ -12,9 +12,11 @@ const indexRouter = require('./routes/index');
 const prodStatus = require('./routes/' + ver + '/status');
 const prodEvents = require('./routes/' + ver + '/events');
 const prodControl = require('./routes/' + ver + '/control');
+const prodNotification = require('./routes/' + ver + '/notification');
 const testStatus = require('./routes/' + ver + '/test/status');
 const testEvents = require('./routes/' + ver + '/test/events').router;
 const testControl = require('./routes/' + ver + '/test/control');
+const testNotification = require('./routes/' + ver + '/test/notification');
 const testSettings = require('./routes/' + ver + '/test/settings');
 
 // api-docs
@@ -51,9 +53,11 @@ app.use('/' + ver + '/spec', express.static('spec'));
 app.use('/' + ver + '/status', prodStatus);
 app.use('/' + ver + '/events', prodEvents);
 app.use('/' + ver + '/control', prodControl);
+app.use('/' + ver + '/notification', prodNotification);
 app.use('/' + ver + '/test/status', testStatus);
 app.use('/' + ver + '/test/events', testEvents);
 app.use('/' + ver + '/test/control', testControl);
+app.use('/' + ver + '/test/notification', testNotification);
 app.use('/' + ver + '/test/settings', testSettings);
 
 // api-docs
diff --git a/init/initcloud.js b/init/initcloud.js
new file mode 100644 (file)
index 0000000..a986f70
--- /dev/null
@@ -0,0 +1,12 @@
+
+
+function generateVAPIDKeys() {
+  var curve = crypto.createECDH('prime256v1');
+  curve.generateKeys();
+
+  return {
+    publicKey: curve.getPublicKey(),
+    privateKey: curve.getPrivateKey(),
+  };
+}
+
index d95b06c127b0ea23c365a16d0be3c32752e25395..fd9e4e1c00c85a410d450bb0053529a05a0c16ae 100644 (file)
@@ -5,57 +5,68 @@ const name = 'garnod';
 // create db
 mongo.connect(url + '/' + name, function(err, db) {
   if (err) {
-               throw err;
-                       return;
-       }
+    throw err;
+      return;
+  }
   console.log('garnod created!');
 
-       // create collections
+  // create collections
   var dbo = db.db(name);
   dbo.createCollection('keys', function(err, res) {
     if (err) {
-                       throw err;
-                       return;
-               }
+      throw err;
+      return;
+    }
     console.log('keys created!');
 
-               // populate collection
-               dbo.collection('keys').insertOne({
-                               _id: 1,
-                               area: 'test',
-                               key: '2TTqCD4mNNny'
-                       }, function(err, res) {
-           if (err) {
-                               throw err;
-                               return;
-                       }
-         console.log('test key set!');
-               });
+    // populate collection
+    dbo.collection('keys').insertOne({
+        _id: 1,
+        area: 'test',
+        key: '2TTqCD4mNNny'
+      }, function(err, res) {
+      if (err) {
+        throw err;
+        return;
+      }
+      console.log('test key set!');
+    });
+  });
+
+  dbo.createCollection('notifications', function(err, res) {
+    if (err) {
+      throw err;
+      return;
+    }
+    console.log('notifications created!');
   });
 
   dbo.createCollection('settings', function(err, res) {
     if (err) {
-                       throw err;
-                       return;
-               }
+      throw err;
+      return;
+    }
     console.log('settings created!');
 
-               // populate collection
-               dbo.collection('settings').insertOne({
-                               _id: 1,
-                               status: {
-                                       state: 'closed'
-                               },
-                               events: {
-                                       state: 'closed',
-                                       delay: 0
-                               }
-                       }, function(err, res) {
-           if (err) {
-                               throw err;
-                               return;
-                       }
-         console.log('settings set!');
-               });
+    // populate collection
+    dbo.collection('settings').insertOne({
+        _id: 1,
+        status: {
+          state: 'closed'
+        },
+        events: {
+          state: 'closed',
+          delay: 0
+        },
+        notification: {
+          delay: 0
+        }
+      }, function(err, res) {
+      if (err) {
+        throw err;
+        return;
+      }
+      console.log('settings set!');
+    });
   });
 });
diff --git a/init/vapid.json b/init/vapid.json
new file mode 100644 (file)
index 0000000..530596e
--- /dev/null
@@ -0,0 +1 @@
+{"publicKey":"BLDSdGasI5sLks30brbIWvlLMFqzoxxkOs7aW_E9PDBzIO_mDs6-tvtb2U0-BVFDafNd58DJgoXxdK5711FF29c","privateKey":"_AFTIegzYV_l_5RYwzOCc22cpYcMUmpkA8bLbrlNq9I"}
index a0cc9bd3917882d78699785c7b34d24f2b8a5262..8d35a619c17b80d336bb7e67921bff1db09a387a 100644 (file)
@@ -5,9 +5,9 @@ const name = 'garnod';
 var db = null;
 
 const msg = {
-       ok: 'ok',
-       dbError: 'dbError',
-       keyMismatch: 'keyMismatch'
+  ok: 'ok',
+  dbError: 'dbError',
+  keyMismatch: 'keyMismatch'
 };
 
 function connect() {
@@ -33,113 +33,221 @@ function connect() {
 connect();
 
 function setSettings(data, callback) {
-       // resolve API key
-       new Promise((resolve, reject) => {
-               var keys = db.db(name).collection('keys');
-               log.debug('Search for API key, area', data.area);
-               keys.findOne({ area: data.area }, {}, (err, res) => {
-                       if (err) {
-                               log.error('Error on searching for API key', JSON.stringify(err));
-                               return reject(msg.dbError);
-                       }
-                       if (res === null) {
-                               log.warn('No such key area found in DB');
-                               return reject(msg.dbError);
-                       }
-                       if (res.key !== data.key) {
-                               log.info('API key doesn\'t match');
-                               return reject(msg.keyMismatch);
-                       }
-                       log.debug('API key is valid');
-                       resolve(msg.ok);
-               })
-       })
-       .catch (err => {
-               log.error('Error on searching for API key', err);
-               return callback(err, null);
-       })
-
-       // save settings
-       .then(res => {
-               return new Promise((resolve, reject) => {
-                       var settings = db.db(name).collection('settings');
-                       log.debug('Update settings:', JSON.stringify(data.settings));
-                       settings.updateOne({ _id: 1 }, { $set: data.settings }, { upsert: true }, (err, res) => {
-                               if (err) {
-                                       log.error('Error on updating settings', JSON.stringify(err));
-                                       return reject(msg.dbError);
-                               }
-                               log.debug('Update settings done');
-                               resolve(msg.ok);
-                       });
-               });
-       })
-       .catch (err => {
-               log.error('Error on updating settings', err);
-               return callback(err, null);
-       })
-
-       // return result
-       .then(res => {
-               return callback(null, res);
-       });
+  // resolve API key
+  new Promise((resolve, reject) => {
+    var keys = db.db(name).collection('keys');
+    log.debug('Search for API key, area', data.area);
+    keys.findOne({ area: data.area }, {}, (err, res) => {
+      if (err) {
+        log.error('Error on searching for API key', JSON.stringify(err));
+        return reject(msg.dbError);
+      }
+      if (res === null) {
+        log.warn('No such key area found in DB');
+        return reject(msg.dbError);
+      }
+      if (res.key !== data.key) {
+        log.info('API key doesn\'t match');
+        return reject(msg.keyMismatch);
+      }
+      log.debug('API key is valid');
+      resolve(msg.ok);
+    })
+  })
+  .catch (err => {
+    log.error('Error on searching for API key', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // save settings
+  .then(res => {
+    return new Promise((resolve, reject) => {
+      var settings = db.db(name).collection('settings');
+      log.debug('Update settings:', JSON.stringify(data.settings));
+      settings.updateOne({ _id: 1 }, { $set: data.settings }, { upsert: true }, (err, res) => {
+        if (err) {
+          log.error('Error on updating settings', JSON.stringify(err));
+          return reject(msg.dbError);
+        }
+        log.debug('Update settings done');
+        resolve(msg.ok);
+      });
+    });
+  })
+  .catch (err => {
+    log.error('Error on updating settings', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // return result
+  .then(res => {
+    return callback(null, res);
+  });
+}
+
+function setNotification(data, callback) {
+  // resolve API key
+  new Promise((resolve, reject) => {
+    var keys = db.db(name).collection('keys');
+    log.debug('Search for API key, area', data.area);
+    keys.findOne({ area: data.area }, {}, (err, res) => {
+      if (err) {
+        log.error('Error on searching for API key', JSON.stringify(err));
+        return reject(msg.dbError);
+      }
+      if (res === null) {
+        log.warn('No such key area found in DB');
+        return reject(msg.dbError);
+      }
+      if (res.key !== data.key) {
+        log.info('API key doesn\'t match');
+        return reject(msg.keyMismatch);
+      }
+      log.debug('API key is valid');
+      resolve(msg.ok);
+    })
+  })
+  .catch (err => {
+    log.error('Error on searching for API key', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // save notification data
+  .then(res => {
+    return new Promise((resolve, reject) => {
+      var settings = db.db(name).collection('notifications');
+      log.debug('Update notifications:', JSON.stringify(data.notification));
+      settings.updateOne({ _id: 1 }, { $set: data.notification }, { upsert: true }, (err, res) => {
+        if (err) {
+          log.error('Error on updating notification', JSON.stringify(err));
+          return reject(msg.dbError);
+        }
+        log.debug('Update notification done');
+        resolve(msg.ok);
+      });
+    });
+  })
+  .catch (err => {
+    log.error('Error on updating notification', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // return result
+  .then(res => {
+    return callback(null, res);
+  });
 }
 
 function getSettings(data, callback) {
-       // resolve API key
-       new Promise((resolve, reject) => {
-               var keys = db.db(name).collection('keys');
-               log.debug('Search for API key, area', data.area);
-               keys.findOne({ area: data.area }, {}, (err, res) => {
-                       if (err) {
-                               log.error('Error on searching for API key', JSON.stringify(err));
-                               return reject(msg.dbError);
-                       }
-                       if (res === null) {
-                               log.warn('No such key area found in DB');
-                               return reject(msg.dbError);
-                       }
-                       if (res.key !== data.key) {
-                               log.info('API key doesn\'t match');
-                               return reject(msg.keyMismatch);
-                       }
-                       log.debug('API key is valid');
-                       resolve(msg.ok);
-               })
-       })
-       .catch (err => {
-               log.error('Error on searching for API key', err);
-               return callback(err, null);
-       })
-
-       // get settings
-       .then(res => {
-               return new Promise((resolve, reject) => {
-                       var settings = db.db(name).collection('settings');
-                       log.debug('Search for settings object');
-                       settings.findOne({ _id: 1 }, {}, (err, res) => {
-                               if (err) {
-                                       log.error('Error on getting settings', JSON.stringify(err));
-                                       return reject(msg.dbError);
-                               }
-                               log.debug('Get settings done:', JSON.stringify(res));
-                               resolve(res);
-                       });
-               });
-       })
-       .catch (err => {
-               log.error('Error on getting settings', err);
-               return callback(err, null);
-       })
-
-       // return result
-       .then(res => {
-               return callback(null, res);
-       });
+  // resolve API key
+  new Promise((resolve, reject) => {
+    var keys = db.db(name).collection('keys');
+    log.debug('Search for API key, area', data.area);
+    keys.findOne({ area: data.area }, {}, (err, res) => {
+      if (err) {
+        log.error('Error on searching for API key', JSON.stringify(err));
+        return reject(msg.dbError);
+      }
+      if (res === null) {
+        log.warn('No such key area found in DB');
+        return reject(msg.dbError);
+      }
+      if (res.key !== data.key) {
+        log.info('API key doesn\'t match');
+        return reject(msg.keyMismatch);
+      }
+      log.debug('API key is valid');
+      resolve(msg.ok);
+    })
+  })
+  .catch (err => {
+    log.error('Error on searching for API key', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // get settings
+  .then(res => {
+    return new Promise((resolve, reject) => {
+      var settings = db.db(name).collection('settings');
+      log.debug('Search for settings object');
+      settings.findOne({ _id: 1 }, {}, (err, res) => {
+        if (err) {
+          log.error('Error on getting settings', JSON.stringify(err));
+          return reject(msg.dbError);
+        }
+        log.debug('Get settings done:', JSON.stringify(res));
+        resolve(res);
+      });
+    });
+  })
+  .catch (err => {
+    log.error('Error on getting settings', err);
+    return callback(err, null);
+  })
+
+  // return result
+  .then(res => {
+    return callback(null, res);
+  });
+}
+
+function getNotification(data, callback) {
+  // resolve API key
+  new Promise((resolve, reject) => {
+    var keys = db.db(name).collection('keys');
+    log.debug('Search for API key, area', data.area);
+    keys.findOne({ area: data.area }, {}, (err, res) => {
+      if (err) {
+        log.error('Error on searching for API key', JSON.stringify(err));
+        return reject(msg.dbError);
+      }
+      if (res === null) {
+        log.warn('No such key area found in DB');
+        return reject(msg.dbError);
+      }
+      if (res.key !== data.key) {
+        log.info('API key doesn\'t match');
+        return reject(msg.keyMismatch);
+      }
+      log.debug('API key is valid');
+      resolve(msg.ok);
+    })
+  })
+  .catch (err => {
+    log.error('Error on searching for API key', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // get notification
+  .then(res => {
+    return new Promise((resolve, reject) => {
+      var settings = db.db(name).collection('notifications');
+      log.debug('Search for notification object');
+      settings.findOne({ _id: 1 }, {}, (err, res) => {
+        if (err) {
+          log.error('Error on getting notification', JSON.stringify(err));
+          return reject(msg.dbError);
+        }
+        log.debug('Get notification done:', JSON.stringify(res));
+        resolve(res);
+      });
+    });
+  })
+  .catch (err => {
+    log.error('Error on getting notification', JSON.stringify(err));
+    return callback(err, null);
+  })
+
+  // return result
+  .then(res => {
+    return callback(null, res);
+  });
 }
 
 module.exports = {
-       msg,
-       setSettings,
-       getSettings
+  msg,
+  setSettings,
+  getSettings,
+  setNotification,
+  getNotification
 }
index c8b145bbaa6ac8c2c1e62506ae5bfdae0e68a69e..c0f145e4d238050e9f6cac8c844d725a89e2be2e 100644 (file)
@@ -42,26 +42,26 @@ const logger = new (winston.Logger) ({
 });
 
 module.exports = function(fileName) {
-       var myLogger = {
-               error: function(...args) {
-                       logger.error('[' + fileName + ']', ...args);
-               },
-               warn: function(...args) {
-                       logger.warn('[' + fileName + ']', ...args);
-               },
-               info: function(...args) {
-                       logger.info('[' + fileName + ']', ...args);
-               },
-               todo: function(...args) {
-                       logger.todo('[' + fileName + ']', ...args);
-               },
-               verbose: function(...args) {
-                       logger.verbose('[' + fileName + ']', ...args);
-               },
-               debug: function(...args) {
-                       logger.debug('[' + fileName + ']', ...args);
-               },
-       }
+  var myLogger = {
+    error: function(...args) {
+      logger.error('[' + fileName + ']', ...args);
+    },
+    warn: function(...args) {
+      logger.warn('[' + fileName + ']', ...args);
+    },
+    info: function(...args) {
+      logger.info('[' + fileName + ']', ...args);
+    },
+    todo: function(...args) {
+      logger.todo('[' + fileName + ']', ...args);
+    },
+    verbose: function(...args) {
+      logger.verbose('[' + fileName + ']', ...args);
+    },
+    debug: function(...args) {
+      logger.debug('[' + fileName + ']', ...args);
+    },
+  }
 
-       return myLogger
+  return myLogger
 }
index 394f367aaf5a61158af3d9facb4c1d189ad66b0e..78be1d567996c66fd6d0ce35028f37cbfa7e1e51 100644 (file)
         "negotiator": "0.6.2"
       }
     },
+    "agent-base": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
+      "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
+      "requires": {
+        "es6-promisify": "^5.0.0"
+      }
+    },
+    "ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "requires": {
+        "color-convert": "^1.9.0"
+      }
+    },
     "array-find-index": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
       "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
       "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0="
     },
+    "asn1.js": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.2.0.tgz",
+      "integrity": "sha512-Q7hnYGGNYbcmGrCPulXfkEw7oW7qjWeM4ZTALmgpuIcZLxyqqKYWxCZg2UBm8bklrnB4m2mGyJPWfoktdORD8A==",
+      "requires": {
+        "bn.js": "^4.0.0",
+        "inherits": "^2.0.1",
+        "minimalistic-assert": "^1.0.0"
+      }
+    },
     "async": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz",
         "safe-buffer": "5.1.2"
       }
     },
+    "bn.js": {
+      "version": "4.11.8",
+      "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+      "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
+    },
     "body-parser": {
       "version": "1.18.3",
       "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
       "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.9.tgz",
       "integrity": "sha512-IQX9/h7WdMBIW/q/++tGd+emQr0XMdeZ6icnT/74Xk9fnabWn+gZgpE+9V+gujL3hhJOoNrnDVY7tWdzc7NUTg=="
     },
+    "buffer-equal-constant-time": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+      "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
+    },
     "buffer-shims": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz",
         "quick-lru": "^1.0.0"
       }
     },
+    "chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "requires": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      }
+    },
+    "cliff": {
+      "version": "0.1.10",
+      "resolved": "https://registry.npmjs.org/cliff/-/cliff-0.1.10.tgz",
+      "integrity": "sha1-U74z6p9ZvshWCe4wCsQgdgPlIBM=",
+      "requires": {
+        "colors": "~1.0.3",
+        "eyes": "~0.1.8",
+        "winston": "0.8.x"
+      },
+      "dependencies": {
+        "async": {
+          "version": "0.2.10",
+          "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
+          "integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E="
+        },
+        "winston": {
+          "version": "0.8.3",
+          "resolved": "https://registry.npmjs.org/winston/-/winston-0.8.3.tgz",
+          "integrity": "sha1-ZLar9M0Brcrv1QCTk7HY6L7BnbA=",
+          "requires": {
+            "async": "0.2.x",
+            "colors": "0.6.x",
+            "cycle": "1.0.x",
+            "eyes": "0.1.x",
+            "isstream": "0.1.x",
+            "pkginfo": "0.3.x",
+            "stack-trace": "0.0.x"
+          },
+          "dependencies": {
+            "colors": {
+              "version": "0.6.2",
+              "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz",
+              "integrity": "sha1-JCP+ZnisDF2uiFLl0OW+CMmXq8w="
+            }
+          }
+        }
+      }
+    },
+    "color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "requires": {
+        "color-name": "1.1.3"
+      }
+    },
+    "color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+    },
     "colors": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
       "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
       "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
     },
+    "ecdsa-sig-formatter": {
+      "version": "1.0.11",
+      "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+      "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "ee-first": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
       "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
       "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q="
     },
+    "es6-promisify": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+      "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
+      "requires": {
+        "es6-promise": "^4.0.3"
+      },
+      "dependencies": {
+        "es6-promise": {
+          "version": "4.2.8",
+          "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+          "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
+        }
+      }
+    },
     "escape-html": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
       "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
     },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
+    },
     "etag": {
       "version": "1.8.1",
       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
       "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q=="
     },
+    "has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
+    },
+    "homedir": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/homedir/-/homedir-0.6.0.tgz",
+      "integrity": "sha1-KyHbZr8Ipts4JJo+/1LX0YcGrx4="
+    },
     "hosted-git-info": {
       "version": "2.8.4",
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz",
       "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ=="
     },
+    "html-entities": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
+      "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8="
+    },
     "http-errors": {
       "version": "1.6.3",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
         "statuses": ">= 1.4.0 < 2"
       }
     },
+    "http_ece": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.1.0.tgz",
+      "integrity": "sha512-bptAfCDdPJxOs5zYSe7Y3lpr772s1G346R4Td5LgRUeCwIGpCGDUTJxRrhTNcAXbx37spge0kWEIH7QAYWNTlA==",
+      "requires": {
+        "urlsafe-base64": "~1.0.0"
+      }
+    },
+    "https-proxy-agent": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-3.0.1.tgz",
+      "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==",
+      "requires": {
+        "agent-base": "^4.3.0",
+        "debug": "^3.1.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "3.2.6",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+          "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+          "requires": {
+            "ms": "^2.1.1"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        }
+      }
+    },
     "iconv-lite": {
       "version": "0.4.23",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
       "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
       "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw=="
     },
+    "jwa": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+      "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+      "requires": {
+        "buffer-equal-constant-time": "1.0.1",
+        "ecdsa-sig-formatter": "1.0.11",
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "jws": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+      "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+      "requires": {
+        "jwa": "^1.4.1",
+        "safe-buffer": "^5.0.1"
+      }
+    },
     "load-json-file": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
         "path-exists": "^3.0.0"
       }
     },
+    "lodash.throttle": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+      "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
+    },
     "loud-rejection": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
         "signal-exit": "^3.0.0"
       }
     },
+    "m3u8stream": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.6.2.tgz",
+      "integrity": "sha512-WsuM2bd5pPN80xvfrB+1DZqr4M7+kJl8byi6+ZCy6cmVjEiHhmr/desN53Ngsa6Hs13kYumeVgT4wL0oIJ+v6g==",
+      "requires": {
+        "miniget": "^1.4.0",
+        "sax": "^1.2.4"
+      }
+    },
     "map-obj": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz",
         "mime-db": "1.40.0"
       }
     },
+    "miniget": {
+      "version": "1.6.1",
+      "resolved": "https://registry.npmjs.org/miniget/-/miniget-1.6.1.tgz",
+      "integrity": "sha512-I5oBwZmcaOuJrjQn7lpS29HM+aAZDbzKbX5ouxVyhFYdg6fA6YKOTwOCgzZQwlHuMek3FlCxz6eNrd4pOXbwOA=="
+    },
+    "minimalistic-assert": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+      "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
+    },
     "minimatch": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
       "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
     },
+    "progress-bar": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/progress-bar/-/progress-bar-0.1.1.tgz",
+      "integrity": "sha1-wyg0+I8PjqxGxjjg5jjxShwXvR8="
+    },
     "proxy-addr": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
+    "sanitize-filename": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz",
+      "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==",
+      "requires": {
+        "truncate-utf8-bytes": "^1.0.0"
+      }
+    },
+    "sax": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+    },
     "semver": {
       "version": "5.7.1",
       "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
       "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz",
       "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g="
     },
+    "supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "requires": {
+        "has-flag": "^3.0.0"
+      }
+    },
     "swagger-ui-dist": {
       "version": "3.23.11",
       "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.23.11.tgz",
       "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz",
       "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA="
     },
+    "truncate-utf8-bytes": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz",
+      "integrity": "sha1-QFkjkJWS1W94pYGENLC3hInKXys=",
+      "requires": {
+        "utf8-byte-length": "^1.0.1"
+      }
+    },
     "type-is": {
       "version": "1.6.18",
       "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
       "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
       "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
     },
+    "urlsafe-base64": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz",
+      "integrity": "sha1-I/iQaabGL0bPOh07ABac77kL4MY="
+    },
+    "utf8-byte-length": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz",
+      "integrity": "sha1-9F8VDExm7uloGGUFq5P8u4rWv2E="
+    },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
       "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
       "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
     },
+    "web-push": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/web-push/-/web-push-3.4.1.tgz",
+      "integrity": "sha512-wtx18llPtWWW+x8hv+Gxvz+2VjO+vZuyihInsjySNpNGNVswH1Bb2KkbbCtE96yi52VUmbFMdidxM8kJAPaSWQ==",
+      "requires": {
+        "asn1.js": "^5.0.0",
+        "http_ece": "1.1.0",
+        "https-proxy-agent": "^3.0.0",
+        "jws": "^3.1.3",
+        "minimist": "^1.2.0",
+        "urlsafe-base64": "^1.0.0"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+        }
+      }
+    },
     "winston": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/winston/-/winston-1.1.2.tgz",
       "requires": {
         "camelcase": "^4.1.0"
       }
+    },
+    "ytdl": {
+      "version": "0.13.1",
+      "resolved": "https://registry.npmjs.org/ytdl/-/ytdl-0.13.1.tgz",
+      "integrity": "sha512-+CJ4xjmMhCcL3vNLmg30TiFjeem7EZ5IbMzp1Ciaauyf5Lb+oe0S7JJotJ/FRRQCBlcsV8R8sv9s+KGNW4Nvng==",
+      "requires": {
+        "chalk": "^2.0.0",
+        "cliff": "~0.1.8",
+        "commander": "^3.0.0",
+        "homedir": "^0.6.0",
+        "lodash.throttle": "^4.1.1",
+        "progress-bar": "~0.1.1",
+        "sanitize-filename": "^1.6.1",
+        "ytdl-core": "^1.0.0"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "3.0.2",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz",
+          "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow=="
+        }
+      }
+    },
+    "ytdl-core": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-1.0.0.tgz",
+      "integrity": "sha512-aP/UBWbZtBSYlqHRYOx1oZ4tMRCAk2I5Y+OJy20/hrrr5XOhgxZ6+vJ20397h1FxYRzOTRSwb3VpBs3/CLe3fA==",
+      "requires": {
+        "html-entities": "^1.1.3",
+        "m3u8stream": "^0.6.2",
+        "miniget": "^1.6.0",
+        "sax": "^1.1.3"
+      }
     }
   }
 }
index 0d2aab9e790a5f6402cdb0f5fcaefcfb9f2a55c2..56ab0b70483ba48c801683a1520955f1c7427b83 100644 (file)
@@ -1,9 +1,9 @@
 {
   "name": "garnod",
-  "version": "0.0.0",
-  "description": "A garage door monitoring application.",
+  "version": "1.0.0",
+  "description": "A garage door monitoring application (server).",
   "scripts": {
-    "start": "node ./bin/www"
+    "start": "node ./bin/garnod"
   },
   "author": "ebelcrom",
   "license": "GPL-2.0-or-later",
@@ -16,7 +16,9 @@
     "morgan": "~1.9.1",
     "query-string": "^6.8.3",
     "swagger-ui-express": "^4.1.2",
-    "winston": "1.1.2"
+    "web-push": "^3.4.1",
+    "winston": "1.1.2",
+    "ytdl": "^0.13.1"
   },
   "devDependencies": {
     "express-generator": "^4.16.1"
index 9de6f5fc9bf2e7011241afccc75c930c5c533a6a..fb57f7d6c6a5c301581b7c4490178265e13d15ad 100644 (file)
Binary files a/public/images/closed.jpg and b/public/images/closed.jpg differ
diff --git a/public/images/moving.jpg b/public/images/moving.jpg
deleted file mode 100644 (file)
index 279f614..0000000
Binary files a/public/images/moving.jpg and /dev/null differ
index be1632b81d21593285dd68f9173052d1c667b79a..fb57f7d6c6a5c301581b7c4490178265e13d15ad 100644 (file)
Binary files a/public/images/open.jpg and b/public/images/open.jpg differ
diff --git a/public/index.html b/public/index.html
deleted file mode 100644 (file)
index ab1ad8a..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-<html>
-
-<head>
-  <title>Express</title>
-  <link rel="stylesheet" href="/stylesheets/style.css">
-</head>
-
-<body>
-  <h1>Express</h1>
-  <p>Welcome to Express</p>
-</body>
-
-</html>
diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css
deleted file mode 100644 (file)
index 9453385..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-body {
-  padding: 50px;
-  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
-}
-
-a {
-  color: #00B7FF;
-}
index ecca96a56b309a315ddf6399155fd2f953031d3b..1e2de67545150f19b1042f037516db0a9f09927f 100644 (file)
@@ -1,9 +1,9 @@
 var express = require('express');
 var router = express.Router();
 
-/* GET home page. */
+/* GET / */
 router.get('/', function(req, res, next) {
-  res.render('index', { title: 'Express' });
+  res.render('index');
 });
 
 module.exports = router;
index ecca96a56b309a315ddf6399155fd2f953031d3b..8cc3399de46a737a53cb1aab2fc5fb64203406ae 100644 (file)
@@ -1,9 +1,9 @@
 var express = require('express');
 var router = express.Router();
 
-/* GET home page. */
+/* GET  */
 router.get('/', function(req, res, next) {
-  res.render('index', { title: 'Express' });
+  res.status(500).send;
 });
 
 module.exports = router;
index ecca96a56b309a315ddf6399155fd2f953031d3b..8cc3399de46a737a53cb1aab2fc5fb64203406ae 100644 (file)
@@ -1,9 +1,9 @@
 var express = require('express');
 var router = express.Router();
 
-/* GET home page. */
+/* GET  */
 router.get('/', function(req, res, next) {
-  res.render('index', { title: 'Express' });
+  res.status(500).send;
 });
 
 module.exports = router;
diff --git a/routes/v1/notification.js b/routes/v1/notification.js
new file mode 100644 (file)
index 0000000..8cc3399
--- /dev/null
@@ -0,0 +1,9 @@
+var express = require('express');
+var router = express.Router();
+
+/* GET  */
+router.get('/', function(req, res, next) {
+  res.status(500).send;
+});
+
+module.exports = router;
index 23e5332cfeed9c889bacaa3ae3d24ee38195314a..8cc3399de46a737a53cb1aab2fc5fb64203406ae 100644 (file)
@@ -1,30 +1,9 @@
 var express = require('express');
-const log = require('./../../lib/logger')(__filename.slice(__dirname.length + 1));
-const qStr = require('query-string');
 var router = express.Router();
 
-/* GET /v1/status?image=true */
+/* GET  */
 router.get('/', function(req, res, next) {
-       if (typeof req.query.image == 'string') {
-               switch (req.query.image) {
-                       case 'true':
-                         res.json({
-                                       'image': 'image',
-                                       'state': 'open'
-                               });
-                               break;
-                       case 'false':
-                         res.json({
-                                       'state': 'open'
-                               });
-                               break;
-                       default:
-                               res.status(400).json([ 'image' ]);
-                               break;
-               }
-       } else {
-               res.status(400).json([ 'image' ]);
-       }
+  res.status(500).send;
 });
 
 module.exports = router;
index 21821ccd3180a15349ff8c2633614314e62caab3..d546a383495614325c347724a850478b44e2dd6e 100644 (file)
@@ -10,78 +10,78 @@ var timer = null;
 
 /* POST /vN/test/control */
 router.post('/', function(req, res, next) {
-       // check header
-       if (typeof req.header('X-API-Key-Test') === 'undefined') {
-               log.info('API key not set in request header');
-               res.status(401).send();
-               return;
-       } else {
-               log.debug('API key set in request header');
-       }
+  // check header
+  if (typeof req.header('X-API-Key-Test') === 'undefined') {
+    log.info('API key not set in request header');
+    res.status(401).send();
+    return;
+  } else {
+    log.debug('API key set in request header');
+  }
 
-       // check query parameter
-       var command = 'move';
-       if (typeof req.query.command != 'undefined') {
-               if (typeof req.query.command == 'string') {
-                       switch (req.query.command) {
-                               case 'move':
-                                       move(req, res, next);
-                                       break;
-                               default:
-                                       log.info('Value of command query parameter unknown');
-                                       res.status(400).send();
-                                       return;
-                       }
-               }
-       } else {
-               // default action
-               move(req, res, next);
-       }
+  // check query parameter
+  var command = 'move';
+  if (typeof req.query.command != 'undefined') {
+    if (typeof req.query.command == 'string') {
+      switch (req.query.command) {
+        case 'move':
+          move(req, res, next);
+          break;
+        default:
+          log.info('Value of command query parameter unknown');
+          res.status(400).send();
+          return;
+      }
+    }
+  } else {
+    // default action
+    move(req, res, next);
+  }
 });
 
 function move(req, res, next) {
-       // get settings
-       dblib.getSettings({
-                       area: 'test',
-                       key: req.header('X-API-Key-Test')
-               }, (err, data) => {
-               if (err) {
-                       switch (err) {
-                               case dblib.msg.dbError:
-                                       log.info('Server error response');
-                                       res.status(500).send();
-                                       break;
-                               case dblib.msg.keyMismatch:
-                                       log.info('Unauthorized access');
-                                       res.status(401).send();
-                                       break;
-                               default:
-                                       log.error('Error result unexpected');
-                                       res.status(500).send();
-                                       break;
-                       }
-               } else {
-                       // schedule event
-                       scheduleEvent(data.events.delay, data);
-                       // send response
-                       res.status(200).send();
-               }
-       });
+  // get settings
+  dblib.getSettings({
+      area: 'test',
+      key: req.header('X-API-Key-Test')
+    }, (err, data) => {
+    if (err) {
+      switch (err) {
+        case dblib.msg.dbError:
+          log.info('Server error response');
+          res.status(500).send();
+          break;
+        case dblib.msg.keyMismatch:
+          log.info('Unauthorized access');
+          res.status(401).send();
+          break;
+        default:
+          log.error('Error result unexpected');
+          res.status(500).send();
+          break;
+      }
+    } else {
+      // schedule event
+      scheduleEvent(data.events.delay, data);
+      // send response
+      res.status(200).send();
+    }
+  });
 }
 
 function scheduleEvent(delay, settings) {
-       if (timer !== null) {
-               clearTimeout(timer);
-               timer = null;
-       }
-       timer = setTimeout(executeEvent, delay * 1000, settings);
-       log.debug('Event set to execute in ' + delay + ' seconds');
+  if (timer !== null) {
+    clearTimeout(timer);
+    timer = null;
+  }
+  timer = setTimeout(executeEvent, delay * 1000, settings);
+  log.debug('Event set to execute in ' + delay + ' seconds');
 }
 
 function executeEvent(settings) {
-       clearTimeout(timer);
-       timer = null;
-       events.emit('event', settings);
+  clearTimeout(timer);
+  timer = null;
+  events.emit('event', settings);
 }
 
 module.exports = router;
index f39d2ff68a5593b4bc09de4dd8313630083f20dd..95830e93cd44fa5759a4a690ac5887eb419434ac 100644 (file)
@@ -10,109 +10,117 @@ const events = new EventEmitter();
 var response = null;
 var request = null;
 var timer = null;
-const delayMin = 1;
+const delayMin = -1;
 const delayMax = 300;
 
+function nocache(req, res, next) {
+  res.header('Cache-Control', 'private, no-cache, no-store, must-revalidate');
+  res.header('Expires', '-1');
+  res.header('Pragma', 'no-cache');
+  next();
+}
+
 /* GET /vN/test/events */
-router.get('/', function(req, res, next) {
-       // check header
-       if (typeof req.header('X-API-Key-Test') === 'undefined') {
-               log.info('API key not set in request header');
-               res.status(401).send();
-               return;
-       } else {
-               log.debug('API key set in request header');
-       }
+router.get('/', nocache, function(req, res, next) {
+  // check header
+  if (typeof req.header('X-API-Key-Test') === 'undefined') {
+    log.info('API key not set in request header');
+    res.status(401).send();
+    return;
+  } else {
+    log.debug('API key set in request header');
+  }
 
-       // TODO: dequeue?
+  // TODO: dequeue?
 
-       // check query parameter
-       var timeout = 30;
-       if (typeof req.query.timeout != 'undefined') {
-               var delay = parseInt(req.query.timeout);
-               if (!isNaN(delay)) {
-                       if (delay < delayMin || delay > delayMax) {
-                               log.info('Value of timeout query parameter not in range');
-                               res.status(400).send();
-                               return;
-                       } else {
-                               timeout = delay;
-                       }
-               }
-       }
+  // check query parameter
+  var timeout = 30;
+  if (typeof req.query.timeout != 'undefined') {
+    var delay = parseInt(req.query.timeout);
+    if (!isNaN(delay)) {
+      if (delay < delayMin || delay > delayMax) {
+        log.info('Value of timeout query parameter not in range');
+        res.status(400).send();
+        return;
+      } else {
+        timeout = delay;
+      }
+    }
+  }
 
-       // schedule resonse on timeout
-       response = res;
-       request = req;
-       if (timer === null) {
-               timer = setTimeout(processTimeout, timeout * 1000);
-               log.debug('Response timeout scheduled');
-       } else {
-               log.todo('not implemented');
-       }
+  // schedule resonse on timeout
+  response = res;
+  request = req;
+  if (timer === null && timeout !== delayMin) {
+    timer = setTimeout(processTimeout, timeout * 1000);
+    log.debug('Response timeout scheduled');
+  } else {
+    log.todo('not implemented');
+  }
 });
 
 events.on('event', (settings) => {
-       log.debug('Event ready for sending');
-       if (timer !== null) {
-               clearTimeout(timer);
-               timer = null;
-       }
+  log.debug('Event ready for sending');
+  if (timer !== null) {
+    clearTimeout(timer);
+    timer = null;
+  }
 
-       // read settings
-       log.debug('Got settings:', JSON.stringify(settings));
-       var image = true;
-       if (request !== null) {
-               if (typeof request.query.image != 'undefined') {
-                       if (request.query.image === 'false') {
-                               image = false;
-                       }
-               }
-               request = null;
-       }
+  // read settings
+  log.debug('Got settings:', JSON.stringify(settings));
+  var image = true;
+  if (request !== null) {
+    if (typeof request.query.image != 'undefined') {
+      if (request.query.image === 'false') {
+        image = false;
+      }
+    }
+    request = null;
+  }
 
-       // TODO: enqueue?
+  // TODO: enqueue?
 
-       // response
-       if (response !== null) {
-               var content = null;
-               if (image) {
-                       var file = null;
-                       switch (settings.events.state) {
-                               case 'open':
-                                       file = fs.readFileSync(__dirname + '/../../../public/images/open.jpg', 'base64');
-                                       break;
-                               case 'closed':
-                                       file = fs.readFileSync(__dirname + '/../../../public/images/closed.jpg', 'base64');
-                                       break;
-                               default:
-                                       log.error('Unexpected status from settings');
-                                       response.status(500).send();
-                                       return;
-                       }
-                       content = {
-                               'image': file,
-                               'state': settings.events.state
-                       };
-               } else {
-                       content = {
-                               'state': settings.events.state
-                       };
-               }
-               response.json(content);
-               response = null;
-       }
+  // response
+  if (response !== null) {
+    var content = null;
+    if (image) {
+      var file = null;
+      switch (settings.events.state) {
+        case 'open':
+          file = fs.readFileSync(__dirname + '/../../../public/images/open.jpg', 'base64');
+          break;
+        case 'closed':
+          file = fs.readFileSync(__dirname + '/../../../public/images/closed.jpg', 'base64');
+          break;
+        default:
+          log.error('Unexpected status from settings');
+          response.status(500).send();
+          return;
+      }
+      content = {
+        'image': file,
+        'state': settings.events.state
+      };
+    } else {
+      content = {
+        'state': settings.events.state
+      };
+    }
+    response.json(content);
+    response = null;
+  }
 });
 
 function processTimeout() {
-       log.debug('Timeout, no events');
-       clearTimeout(timer);
-       timer = null;
-       response.status(304).send();
-       response = null;
+  log.debug('Timeout, no events');
+  clearTimeout(timer);
+  timer = null;
+//console.log('response:', response);
+  response.status(304).end('');
+  response = null;
 }
 
 module.exports = {
-       router,
-       events
+  router,
+  events
 }
diff --git a/routes/v1/test/notification.js b/routes/v1/test/notification.js
new file mode 100644 (file)
index 0000000..dc866db
--- /dev/null
@@ -0,0 +1,82 @@
+const express = require('express');
+const log = require('./../../../lib/logger')(__filename.slice(__dirname.length + 1));
+const qStr = require('query-string');
+const router = express.Router();
+const dblib = require('./../../../lib/dblib');
+
+/* POST /vN/test/notification */
+router.post('/', function(req, res, next) {
+  // check header
+  if (typeof req.header('X-API-Key-Test') === 'undefined') {
+    log.info('API key not set in request header');
+    res.status(401).send();
+    return;
+  } else {
+    log.debug('API key set in request header');
+  }
+
+  // check body
+  if (typeof req.body != 'object') {
+    log.info('Request body is not JSON');
+    res.status(400).send();
+    return;
+  } else{
+    // check endpoint
+    if (typeof req.body.endpoint != 'string' ) {
+      log.info('Type of endpoint in content invalid');
+      res.status(400).send();
+      return;
+    }
+
+    // check expirationTime
+    if (req.body.expirationTime !== null) {
+      if (typeof req.body.expirationTime != 'string' ) {
+        log.info('Type of expirationTime in content invalid');
+        res.status(400).send();
+        return;
+      }
+    }
+
+    // check keys
+    if (typeof req.body.keys == 'object' ) {
+      if (typeof req.body.keys.p256dh != 'string' ) {
+        log.info('Type of keys.p256dh in content invalid');
+        res.status(400).send();
+        return;
+      }
+      if (typeof req.body.keys.auth != 'string' ) {
+        log.info('Type of keys.auth in content invalid');
+        res.status(400).send();
+        return;
+      }
+    }
+
+    // save to DB
+    dblib.setNotification({
+        area: 'test',
+        key: req.header('X-API-Key-Test'),
+        notification: req.body
+      }, (err, data) => {
+      if (err) {
+        switch (err) {
+          case dblib.msg.dbError:
+            log.info('Server error response');
+            res.status(500).send();
+            break;
+          case dblib.msg.keyMismatch:
+            log.info('Unauthorized access');
+            res.status(401).send();
+            break;
+          default:
+            log.error('Error result unexpected');
+            res.status(500).send();
+            break;
+        }
+      } else {
+        res.status(200).send();
+      }
+    });
+  }
+});
+
+module.exports = router;
index 07e3910185b7850abe910aea81fb720cc8672200..871fee7f15abf3f514e397358dc02722894333f7 100644 (file)
@@ -3,134 +3,255 @@ const log = require('./../../../lib/logger')(__filename.slice(__dirname.length +
 const qStr = require('query-string');
 const router = express.Router();
 const dblib = require('./../../../lib/dblib');
+const webpush = require('web-push');
 
-const delayMin = 1;
+const delayMin = -1;
 const delayMax = 300;
+var timer = null;
 
 /* POST /vN/test/settings */
 router.post('/', function(req, res, next) {
-       // check header
-       if (typeof req.header('X-API-Key-Test') === 'undefined') {
-               log.info('API key not set in request header');
-               res.status(401).send();
-               return;
-       } else {
-               log.debug('API key set in request header');
-       }
+  // check header
+  if (typeof req.header('X-API-Key-Test') === 'undefined') {
+    log.info('API key not set in request header');
+    res.status(401).send();
+    return;
+  } else {
+    log.debug('API key set in request header');
+  }
 
-       // check body
-       if (typeof req.body != 'object') {
-               log.info('Request body is not JSON');
-               res.status(400).send();
-               return;
-       } else{
-               var settings = {};
+  // check body
+  if (typeof req.body != 'object') {
+    log.info('Request body is not JSON');
+    res.status(400).send();
+    return;
+  } else{
+    var settings = {};
 
-               // populate status
-               if (typeof req.body.status == 'object' ) {
-                       if (typeof req.body.status.state == 'string' ) {
-                               switch (req.body.status.state) {
-                                       case 'open':
-                                               settings.status = {
-                                                       state: 'open'
-                                               };
-                                               break;
-                                       case 'closed':
-                                               settings.status = {
-                                                       state: 'closed'
-                                               };
-                                               break;
-                                       case 'moving':
-                                               settings.status = {
-                                                       state: 'moving'
-                                               };
-                                               break;
-                                       default:
-                                               log.info('Value of status.state in content unknown');
-                                               res.status(400).send();
-                                               return;
-                               }
-                       } else {
-                               log.info('Type of status.state in content invalid');
-                               res.status(400).send();
-                               return;
-                       }
-               }
+    // populate status
+    if (typeof req.body.status == 'object' ) {
+      if (typeof req.body.status.state == 'string' ) {
+        switch (req.body.status.state) {
+          case 'open':
+            settings.status = {
+              state: 'open'
+            };
+            break;
+          case 'closed':
+            settings.status = {
+              state: 'closed'
+            };
+            break;
+          case 'moving':
+            settings.status = {
+              state: 'moving'
+            };
+            break;
+          default:
+            log.info('Value of status.state in content unknown');
+            res.status(400).send();
+            return;
+        }
+      } else {
+        log.info('Type of status.state in content invalid');
+        res.status(400).send();
+        return;
+      }
+    }
 
-               // populate events
-               if (typeof req.body.events == 'object' ) {
-                       if (typeof req.body.events.state == 'string' ) {
-                               switch (req.body.events.state) {
-                                       case 'open':
-                                               settings.events = {
-                                                               state: 'open'
-                                               };
-                                               break;
-                                       case 'closed':
-                                               settings.events = {
-                                                               state: 'closed'
-                                               };
-                                               break;
-                                       default:
-                                               log.info('Value of events.state in content unknown');
-                                               res.status(400).send();
-                                               return;
-                               }
-                       } else {
-                               log.info('Type of events.state in content invalid');
-                               res.status(400).send();
-                               return;
-                       }
-                       if (typeof req.body.events.delay == 'number' ) {
-                               var delay = parseInt(req.body.events.delay);
-                               if (delay < delayMin || delay > delayMax) {
-                                       log.info('Value of events.delay in content not in range');
-                                       res.status(400).send();
-                                       return;
-                               } else {
-                                       settings.events.delay = delay;
-                               }
-                       } else {
-                               log.info('Type of events.delay in content invalid');
-                               res.status(400).send();
-                               return;
-                       }
-               }
-               log.debug('Parsed settings:', JSON.stringify(settings));
+    // populate events
+    if (typeof req.body.events == 'object' ) {
+      if (typeof req.body.events.state == 'string' ) {
+        switch (req.body.events.state) {
+          case 'open':
+            settings.events = {
+                state: 'open'
+            };
+            break;
+          case 'closed':
+            settings.events = {
+                state: 'closed'
+            };
+            break;
+          default:
+            log.info('Value of events.state in content unknown');
+            res.status(400).send();
+            return;
+        }
+      } else {
+        log.info('Type of events.state in content invalid');
+        res.status(400).send();
+        return;
+      }
+      if (typeof req.body.events.delay == 'number' ) {
+        var delay = parseInt(req.body.events.delay);
+        if (delay < delayMin || delay > delayMax) {
+          log.info('Value of events.delay in content not in range');
+          res.status(400).send();
+          return;
+        } else {
+          settings.events.delay = delay;
+        }
+      } else {
+        log.info('Type of events.delay in content invalid');
+        res.status(400).send();
+        return;
+      }
+    }
 
-               // check settings
-               if (Object.getOwnPropertyNames(settings).length === 0) {
-                       log.info('Settings is empty');
-                       res.status(400).send();
-                       return;
-               }
+    // populate notification
+    if (typeof req.body.notification == 'object' ) {
+      if (typeof req.body.notification.delay == 'number' ) {
+        var delay = parseInt(req.body.notification.delay);
+        if (delay < delayMin || delay > delayMax) {
+          log.info('Value of notification.delay in content not in range');
+          res.status(400).send();
+          return;
+        } else {
+          settings.notification = {
+            delay: delay
+          };
+        }
+      } else {
+        log.info('Type of notification.delay in content invalid');
+        res.status(400).send();
+        return;
+      }
+    }
+    log.debug('Parsed settings:', JSON.stringify(settings));
 
-               // save to DB
-               dblib.setSettings({
-                               area: 'test',
-                               key: req.header('X-API-Key-Test'),
-                               settings: settings
-                       }, (err, data) => {
-                       if (err) {
-                               switch (err) {
-                                       case dblib.msg.dbError:
-                                               log.info('Server error response');
-                                               res.status(500).send();
-                                               break;
-                                       case dblib.msg.keyMismatch:
-                                               log.info('Unauthorized access');
-                                               res.status(401).send();
-                                               break;
-                                       default:
-                                               log.error('Error result unexpected');
-                                               res.status(500).send();
-                                               break;
-                               }
-                       } else {
-                               res.status(200).send();
-                       }
-               });
-       }
+    // check settings
+    if (Object.getOwnPropertyNames(settings).length === 0) {
+      log.info('Settings is empty');
+      res.status(400).send();
+      return;
+    }
+
+    // save to DB
+    dblib.setSettings({
+        area: 'test',
+        key: req.header('X-API-Key-Test'),
+        settings: settings
+      }, (err, data) => {
+      if (err) {
+        switch (err) {
+          case dblib.msg.dbError:
+            log.info('Server error response');
+            res.status(500).send();
+            break;
+          case dblib.msg.keyMismatch:
+            log.info('Unauthorized access');
+            res.status(401).send();
+            break;
+          default:
+            log.error('Error result unexpected');
+            res.status(500).send();
+            break;
+        }
+      } else {
+        // notify
+        if (typeof req.body.notification == 'object' ) {
+          if (typeof req.body.notification.delay == 'number' &&
+                                               req.body.notification.delay !== delayMin) {
+            notify(req, res, next, settings.notification.delay);
+          } else {
+            res.status(200).send();
+          }
+        }
+      }
+    });
+  }
 });
 
+function notify(req, res, next, delay) {
+  // get settings
+  dblib.getNotification({
+      area: 'test',
+      key: req.header('X-API-Key-Test')
+    }, (err, data) => {
+    if (err) {
+      switch (err) {
+        case dblib.msg.dbError:
+          log.info('Server error response');
+          res.status(500).send();
+          break;
+        case dblib.msg.keyMismatch:
+          log.info('Unauthorized access');
+          res.status(401).send();
+          break;
+        default:
+          log.error('Error result unexpected');
+          res.status(500).send();
+          break;
+      }
+    } else {
+      // schedule event
+      scheduleNotification(delay, data);
+      res.status(200).send();
+    }
+  });
+}
+
+function scheduleNotification(delay, notification) {
+  if (timer !== null) {
+    clearTimeout(timer);
+    timer = null;
+  }
+  timer = setTimeout(executeNotification, delay * 1000, notification);
+  log.debug('Notification set to execute in ' + delay + ' seconds');
+}
+
+/*
+{
+  "endpoint":"https://fcm.googleapis.com/fcm/send/eGS6ZOFQcUk:APA91bGZliEmCLdFcuytpz2KFdLZVNm4YwAdbjaRxyF8tl6vhmuNd7L0NZagys77AjA3GWc3RNhad3i3Bc3rwBQEPKfAX4LZ0jOPzo_heG-WL7MNZx5w9lI2qycrdNk8UiQS0IlhL-j5",
+  "expirationTime":null,
+  "keys":{
+    "p256dh":"BAMu7XxsCuoB0j1zdaNGXoRterzytIpyG7yxzfEnAaA0SF4xTWJOztnbUoKX4IBdKpteJA9UhhyLI286mZKUlTQ",
+    "auth":"iPebCb94XG_zeVdfAmKN8Q"
+  }
+}
+*/
+function executeNotification(notification) {
+  clearTimeout(timer);
+  timer = null;
+
+  // web push
+  webpush.setGCMAPIKey('AAAAUtHuYco:APA91bEBTxCRGaez9_glljXAlit3PY5HMwhLSqWYMC1j-jFSp6nvnNqjI42jAVFApQbM0oyAOQjCUilIovB76cwTFxyZTP96wm9n09XwiMRXJjhwiJX1hO32mBB2zwK6X-w7epE1V67K');
+  webpush.setVapidDetails(
+    'mailto:ebelcrom@gmail.com',
+    'BLDSdGasI5sLks30brbIWvlLMFqzoxxkOs7aW_E9PDBzIO_mDs6-tvtb2U0-BVFDafNd58DJgoXxdK5711FF29c',
+    '_AFTIegzYV_l_5RYwzOCc22cpYcMUmpkA8bLbrlNq9I'
+  );
+
+  log.debug('Notification data:', JSON.stringify(notification))
+  const pushSubscription = {
+    endpoint: notification.endpoint,
+    keys: {
+      auth: notification.keys.auth,
+      p256dh: notification.keys.p256dh
+    }
+  };
+  const options = {
+    actions: [
+      {
+        action: 'Open Garage Node',
+        title: 'Garage Node'
+      }
+    ],
+    body: 'The garage door is open!',
+    badge: './img/icons/android-chrome-192x192.png',
+    icon: './img/icons/android-chrome-192x192.png'
+  };
+
+  webpush.sendNotification(pushSubscription, 'The garage door is open!')
+  .then(data => {
+    if (data) {
+      log.info('sent, data:', JSON.stringify(data));
+    }
+  })
+  .catch(err => {
+    log.info('sent, err:', JSON.stringify(err));
+  });
+}
+
 module.exports = router;
index 9255b8afc57ca2416976c6ea9b5612be406cb39e..75b025d3cfdb842510b1b5b5e7392112c54f3987 100644 (file)
@@ -7,72 +7,72 @@ const fs = require('fs');
 
 /* GET /vN/status */
 router.get('/', function(req, res, next) {
-       // check header
-       if (typeof req.header('X-API-Key-Test') === 'undefined') {
-               log.info('API key not set in request header');
-               res.status(401).send();
-               return;
-       } else {
-               log.debug('API key set in request header');
-       }
+  // check header
+  if (typeof req.header('X-API-Key-Test') === 'undefined') {
+    log.info('API key not set in request header');
+    res.status(401).send();
+    return;
+  } else {
+    log.debug('API key set in request header');
+  }
 
-       // check query parameter
-       var image = true;
-       if (typeof req.query.image != 'undefined') {
-               if (req.query.image === 'false') {
-                       image = false;
-               }
-       }
+  // check query parameter
+  var image = true;
+  if (typeof req.query.image != 'undefined') {
+    if (req.query.image === 'false') {
+      image = false;
+    }
+  }
 
-       // read settings
-       dblib.getSettings({
-                       area: 'test',
-                       key: req.header('X-API-Key-Test')
-               }, (err, data) => {
-               if (err) {
-                       switch (err) {
-                               case dblib.msg.dbError:
-                                       log.info('Server error response');
-                                       res.status(500).send();
-                                       break;
-                               case dblib.msg.keyMismatch:
-                                       log.info('Unauthorized access');
-                                       res.status(401).send();
-                                       break;
-                               default:
-                                       log.error('Error result unexpected');
-                                       res.status(500).send();
-                                       break;
-                       }
-               } else {
-                       // response
-                       var content = null;
-                       if (image) {
-                               var file = null;
-                               switch (data.status.state) {
-                                       case 'open':
-                                               file = fs.readFileSync(__dirname + '/../../../public/images/open.jpg', 'base64');
-                                               break;
-                                       case 'closed':
-                                               file = fs.readFileSync(__dirname + '/../../../public/images/closed.jpg', 'base64');
-                                               break;
-                                       default:
-                                               log.error('Unexpected status from settings');
-                                               res.status(500).send();
-                                               return;
-                               }
-                               content = {
-                                       'image': file,
-                                       'state': data.status.state
-                               };
-                       } else {
-                               content = {
-                                       'state': data.status.state
-                               };
-                       }
-                       res.json(content);
-               }
-       });
+  // read settings
+  dblib.getSettings({
+      area: 'test',
+      key: req.header('X-API-Key-Test')
+    }, (err, data) => {
+    if (err) {
+      switch (err) {
+        case dblib.msg.dbError:
+          log.info('Server error response');
+          res.status(500).send();
+          break;
+        case dblib.msg.keyMismatch:
+          log.info('Unauthorized access');
+          res.status(401).send();
+          break;
+        default:
+          log.error('Error result unexpected');
+          res.status(500).send();
+          break;
+      }
+    } else {
+      // response
+      var content = null;
+      if (image) {
+        var file = null;
+        switch (data.status.state) {
+          case 'open':
+            file = fs.readFileSync(__dirname + '/../../../public/images/open.jpg', 'base64');
+            break;
+          case 'closed':
+            file = fs.readFileSync(__dirname + '/../../../public/images/closed.jpg', 'base64');
+            break;
+          default:
+            log.error('Unexpected status from settings');
+            res.status(500).send();
+            return;
+        }
+        content = {
+          'image': file,
+          'state': data.status.state
+        };
+      } else {
+        content = {
+          'state': data.status.state
+        };
+      }
+      res.json(content);
+    }
+  });
 });
 
 module.exports = router;
index 0c3449e7495837c6bb5990b27d8ed56967cb561a..1c31f6bf2d6cce2c1a29f3c2917e353dba38652d 100644 (file)
@@ -1,7 +1,7 @@
 {
   "openapi": "3.0.2",
   "info": {
-    "description": "Garage Node is inteded to be a garage door watch an control application based on a RESTful API. The (web) server is watching the door state and shall inform the user via a push notification when a garage door is open for a while. Then the user shall see the current door state and alternatively perform an motion action using a client application such as a PWA.",
+    "description": "Garage Node is inteded to be a garage door watch and control application based on a RESTful API. The (web) server is watching the door state and shall inform the user via a push notification when a garage door is open for a while. Then the user shall see the current door state and alternatively perform an motion action using a client application such as a PWA.",
     "contact": {
       "name": "ebelcrom"
     },
   },
   "servers": [
     {
-      "url": "http://localhost:3000/v1"
+      "url": "https://binomiant.duckdns.org/mVk7Yr3k/v1"
     }
   ],
   "tags": [
     {
       "name": "production",
-      "description": "Production area of this API. All requests go to the real server  instance as well as all responses come from it."
+      "description": "Production area of this API. All requests go to the real server instance as well as all responses come from it."
     },
     {
       "name": "test",
-      "description": "Test area of this API. All requests go to a simulated server  instance as well as all responses come from it."
+      "description": "Test area of this API. All requests go to a simulated server instance as well as all responses come from it."
     }
   ],
   "paths": {
@@ -62,7 +62,7 @@
     "/events": {
       "get": {
         "summary": "Listen on events.",
-        "description": "For listening on events, you have to long poll this  ressource. If no events occur within the timeout range, request again. <p> <b>Note</b>: This call only makes sense if you have sent a control command before. </p>",
+        "description": "For listening on events, you have to long poll this ressource. If no events occur within the timeout range, request again. <p> <b>Note</b>: This call only makes sense if you have sent a control command before. </p>",
         "operationId": "prod_events",
         "tags": [
           "production"
@@ -99,7 +99,7 @@
     "/control": {
       "post": {
         "summary": "Send a command.",
-        "description": "After sending a command you have to poll a state change.  The command result only returns the command state.",
+        "description": "After sending a command you have to poll a state change. The command result only returns the command state.",
         "operationId": "prod_control",
         "tags": [
           "production"
         }
       }
     },
+    "/notification": {
+      "post": {
+        "summary": "Setup web push notification.",
+        "description": "To be able to send web push notifications the server has to have subscription data from the client.",
+        "operationId": "prod_notification",
+        "tags": [
+          "production"
+        ],
+        "security": [
+          {
+            "api_key_auth": []
+          }
+        ],
+        "requestBody": {
+          "description": "Request body for setup push notification.",
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/notification"
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "Ok, settings accepted"
+          },
+          "400": {
+            "description": "Bad request, check request body"
+          },
+          "401": {
+            "$ref": "#/components/responses/error_authentication"
+          }
+        }
+      }
+    },
     "/test/status": {
       "get": {
         "summary": "Get the current state.",
     "/test/events": {
       "get": {
         "summary": "Listen on events.",
-        "description": "For listening on events, you have to long poll this  ressource. If no events occur within the timeout range, request again. <p> <b>Note</b>: This call only makes sense if you have sent a control command before. </p>",
+        "description": "For listening on events, you have to long poll this ressource. If no events occur within the timeout range, request again. <p> <b>Note</b>: This call only makes sense if you have sent a control command before. </p>",
         "operationId": "test_events",
         "tags": [
           "test"
     "/test/control": {
       "post": {
         "summary": "Send a command.",
-        "description": "After sending a command you have to poll a state change.  The command result only returns the command state.",
+        "description": "After sending a command you have to poll a state change. The command result only returns the command state.",
         "operationId": "test_control",
         "tags": [
           "test"
                 "$ref": "#/components/schemas/state"
               },
               "delay": {
-                "description": "Delay for event to occur.",
+                "description": "Delay for event to occur. If set to -1 the event is disabled.\n",
                 "type": "integer",
                 "format": "int32",
-                "minimum": 1,
+                "minimum": -1,
                 "maximum": 300
               }
             }
+          },
+          "notification": {
+            "description": "Settings for push notification of an open door.\n",
+            "type": "object",
+            "properties": {
+              "delay": {
+                "description": "Delay for the notification to occur. If set to -1 the notification is\ndisabled.\n",
+                "type": "integer",
+                "format": "int32",
+                "minimum": -1,
+                "maximum": 300
+              }
+            }
+          }
+        }
+      },
+      "notification": {
+        "description": "Notification Data.",
+        "type": "object",
+        "required": [
+          "endpoint",
+          "expirationTime",
+          "keys"
+        ],
+        "properties": {
+          "endpoint": {
+            "description": "Push service endpoint.",
+            "type": "string"
+          },
+          "expirationTime": {
+            "description": "Expiration time of subscription.",
+            "type": "string"
+          },
+          "keys": {
+            "type": "object",
+            "required": [
+              "p256dh",
+              "auth"
+            ],
+            "properties": {
+              "p256dh": {
+                "description": "An elliptic curve Diffie–Hellman public key on the P-256 curve.",
+                "type": "string"
+              },
+              "auth": {
+                "description": "An authentication secret.",
+                "type": "string"
+              }
+            }
           }
         }
       },
         }
       },
       "timeout": {
-        "description": "Time in seconds for returning even when no events are available.",
+        "description": "Time in seconds for returning even when no events are available.\n",
         "name": "timeout",
         "in": "query",
         "schema": {
index 10839558c9f7119bbe4a2c3b100138852e1c6dd5..5f7374e81c32e19cbd4b05175059e2e069cf0632 100644 (file)
@@ -1,7 +1,7 @@
 openapi: 3.0.2
 info:
   description: >-
-    Garage Node is inteded to be a garage door watch an control application
+    Garage Node is inteded to be a garage door watch and control application
     based on a RESTful API. The (web) server is watching the door state and
     shall inform the user via a push notification when a garage door is open for
     a while. Then the user shall see the current door state and alternatively
@@ -14,16 +14,15 @@ info:
     name: GPL 3.0
     url: 'https://www.gnu.org/licenses/gpl-3.0.txt'
 servers:
-#  - url: 'https://binomiant.duckdns.org/TYtse53t/v1'
-  - url: 'http://localhost:3000/v1'
+  - url: 'https://binomiant.duckdns.org/mVk7Yr3k/v1'
 tags:
   - name: production
     description: >-
-      Production area of this API. All requests go to the real server  instance
+      Production area of this API. All requests go to the real server instance
       as well as all responses come from it.
   - name: test
     description: >-
-      Test area of this API. All requests go to a simulated server  instance as
+      Test area of this API. All requests go to a simulated server instance as
       well as all responses come from it.
 paths:
   /status:
@@ -48,7 +47,7 @@ paths:
     get:
       summary: Listen on events.
       description: >-
-        For listening on events, you have to long poll this  ressource. If no
+        For listening on events, you have to long poll this ressource. If no
         events occur within the timeout range, request again. <p> <b>Note</b>:
         This call only makes sense if you have sent a control command before.
         </p>
@@ -73,7 +72,7 @@ paths:
     post:
       summary: Send a command.
       description: >-
-        After sending a command you have to poll a state change.  The command
+        After sending a command you have to poll a state change. The command
         result only returns the command state.
       operationId: prod_control
       tags:
@@ -89,6 +88,30 @@ paths:
           $ref: '#/components/responses/error_param'
         '401':
           $ref: '#/components/responses/error_authentication'
+  /notification:
+    post:
+      summary: Setup web push notification.
+      description: >-
+        To be able to send web push notifications the server has to have
+        subscription data from the client.
+      operationId: prod_notification
+      tags:
+        - production
+      security:
+        - api_key_auth: []
+      requestBody:
+        description: Request body for setup push notification.
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/notification'
+      responses:
+        '200':
+          description: 'Ok, settings accepted'
+        '400':
+          description: 'Bad request, check request body'
+        '401':
+          $ref: '#/components/responses/error_authentication'
   /test/status:
     get:
       summary: Get the current state.
@@ -111,7 +134,7 @@ paths:
     get:
       summary: Listen on events.
       description: >-
-        For listening on events, you have to long poll this  ressource. If no
+        For listening on events, you have to long poll this ressource. If no
         events occur within the timeout range, request again. <p> <b>Note</b>:
         This call only makes sense if you have sent a control command before.
         </p>
@@ -136,7 +159,7 @@ paths:
     post:
       summary: Send a command.
       description: >-
-        After sending a command you have to poll a state change.  The command
+        After sending a command you have to poll a state change. The command
         result only returns the command state.
       operationId: test_control
       tags:
@@ -214,11 +237,51 @@ components:
             state:
               $ref: '#/components/schemas/state'
             delay:
-              description: Delay for event to occur.
+              description: |
+                Delay for event to occur. If set to -1 the event is disabled.
+              type: integer
+              format: int32
+              minimum: -1
+              maximum: 300
+        notification:
+          description: |
+            Settings for push notification of an open door.
+          type: object
+          properties:
+            delay:
+              description: |
+                Delay for the notification to occur. If set to -1 the notification is
+                disabled.
               type: integer
               format: int32
-              minimum: 1
+              minimum: -1
               maximum: 300
+    notification:
+      description: Notification Data.
+      type: object
+      required:
+        - endpoint
+        - expirationTime
+        - keys
+      properties:
+        endpoint:
+          description: Push service endpoint.
+          type: string
+        expirationTime:
+          description: Expiration time of subscription.
+          type: string
+        keys:
+          type: object
+          required:
+            - p256dh
+            - auth
+          properties:
+            p256dh:
+              description: An elliptic curve Diffie–Hellman public key on the P-256 curve.
+              type: string
+            auth:
+              description: An authentication secret.
+              type: string
     state:
       description: Current door state.
       type: string
@@ -255,7 +318,8 @@ components:
         type: boolean
         default: true
     timeout:
-      description: Time in seconds for returning even when no events are available.
+      description: |
+        Time in seconds for returning even when no events are available.
       name: timeout
       in: query
       schema: