diff --git a/.eslintrc.json b/.eslintrc.json index 2bf1bee..3c278d5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -11,7 +11,8 @@ "node_modules", "logs", "plugins/*", - "dist" + "dist", + "build" ], "extends": [ "eslint:recommended" diff --git a/.gitignore b/.gitignore index d1f9c7a..04adead 100644 --- a/.gitignore +++ b/.gitignore @@ -124,4 +124,7 @@ plugins/* tmp demo-database* -.vscode/archive-browser \ No newline at end of file +.vscode/archive-browser + +build +dist \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 790c17f..8179132 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ WORKDIR /opt/OpenHaus/backend COPY --from=builder node_modules node_modules RUN apk --no-cache add openssl -COPY . ./ +COPY ./build/ ./ #COPY ./package.json ./ #ENV HTTP_PORT=8080 diff --git a/Gruntfile.js b/Gruntfile.js index 14cca01..9c309d6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,28 +1,31 @@ const path = require("path"); const pkg = require("./package.json"); const cp = require("child_process"); +const crypto = require("crypto"); +const fs = require("fs"); +const os = require("os"); + +const PATH_DIST = path.resolve(process.cwd(), "dist"); +const PATH_BUILD = path.resolve(process.cwd(), "build"); + +process.env = Object.assign({ + NODE_ENV: "production" +}, process.env); module.exports = function (grunt) { // Project configuration. grunt.initConfig({ pkg, - env: { - options: { - //Shared Options Hash - }, - prod: { - NODE_ENV: "production", - } - }, uglify: { - /* // NOTE: if true, this truncate variables&class names // Are original names neede for production?! // i dont thinks so, its only usefull in development options: { - mangle: false - },*/ + mangle: { + toplevel: true + } + }, build: { files: [{ expand: true, @@ -37,85 +40,52 @@ module.exports = function (grunt) { "!scripts/**", "!tests/**" ], - dest: path.join(process.cwd(), "dist"), + dest: PATH_BUILD, //cwd: process.cwd() }] } - }, - run: { - install: { - options: { - cwd: path.join(process.cwd(), "dist") - }, - cmd: "npm", - args: [ - "install", - "--prod-only" - ] - }, - clean: { - cmd: "rm", - args: [ - "-rf", - "./dist" - ] - }, - copy: { - exec: "cp ./package*.json ./dist" - }, - folder: { - exec: "mkdir ./dist/logs && mkdir ./dist/plugins" - }, - "scripts-mock": { - exec: "mkdir ./dist/scripts && echo 'exit 0' > ./dist/scripts/post-install.sh && chmod +x ./dist/scripts/post-install.sh" - }, - "scripts-cleanup": { - exec: "rm ./dist/scripts/post-install.sh && rmdir ./dist/scripts" - } - }, - compress: { - main: { - options: { - archive: `backend-v${pkg.version}.tgz` - }, - files: [{ - expand: true, - src: "**/*", - cwd: "dist/" - }] - } } }); - // Load the plugin that provides the "uglify" task. grunt.loadNpmTasks("grunt-contrib-uglify"); - grunt.loadNpmTasks("grunt-run"); - grunt.loadNpmTasks("grunt-contrib-compress"); - grunt.loadNpmTasks("grunt-env"); - - grunt.registerTask("clean", ["run:clean"]); - - grunt.registerTask("build", [ - "run:clean", - "env:prod", - "uglify", - "run:folder", - "run:copy", - ]); - - // install npm dependencies - grunt.registerTask("install", [ - "env:prod", - "run:scripts-mock", - "run:install", - "run:scripts-cleanup" - ]); - - grunt.registerTask("bundle", [ - "build", - "install", - "compress" - ]); + + + grunt.registerTask("build", () => { + [ + `rm -rf ${path.join(PATH_BUILD, "/*")}`, + `rm -rf ${path.join(PATH_DIST, "/*")}`, + `mkdir -p ${PATH_BUILD}`, + `mkdir ${path.join(PATH_BUILD, "logs")}`, + `mkdir ${path.join(PATH_BUILD, "plugins")}`, + `mkdir ${path.join(PATH_BUILD, "scripts")}`, + `echo "exit 0" > ${path.join(PATH_BUILD, "scripts/post-install.sh")}`, + `chmod +x ${path.join(PATH_BUILD, "scripts/post-install.sh")}`, + `cp ./package*.json ${PATH_BUILD}`, + "grunt uglify", + ].forEach((cmd) => { + cp.execSync(cmd, { + env: process.env, + stdio: "inherit" + }); + }); + }); + + + grunt.registerTask("install", () => { + cp.execSync(`cd ${PATH_BUILD} && npm install --prod-only`, { + env: process.env, + stdio: "inherit" + }); + }); + + + grunt.registerTask("compress", () => { + cp.execSync(`cd ${PATH_BUILD} && tar -czvf ${path.join(PATH_DIST, `${pkg.name}-v${pkg.version}.tgz`)} *`, { + env: process.env, + stdio: "inherit" + }); + }); + grunt.registerTask("build:docker", () => { cp.execSync(`docker build . -t openhaus/${pkg.name}:latest --build-arg version=${pkg.version}`, { @@ -124,15 +94,40 @@ module.exports = function (grunt) { }); }); + + grunt.registerTask("checksum", () => { + + let m5f = path.join(PATH_DIST, "./checksums.md5"); + + fs.rmSync(m5f, { force: true }); + let files = fs.readdirSync(PATH_DIST); + let fd = fs.openSync(m5f, "w"); + + files.forEach((name) => { + + let file = path.join(PATH_DIST, name); + let content = fs.readFileSync(file); + let hasher = crypto.createHash("md5"); + let hash = hasher.update(content).digest("hex"); + fs.writeSync(fd, `${hash}\t${name}${os.EOL}`); + + }); + + fs.closeSync(fd); + + }); + + grunt.registerTask("release", () => { [ - "rm -rf ./dist/*", - "npm run build", - "npm run build:docker", - `docker save openhaus/frontend:latest | gzip > ./${pkg.name}-v${pkg.version}-docker.tgz`, + `mkdir -p ${PATH_DIST}`, + "grunt build", "grunt compress", - `cd dist && NODE_ENV=production npm install --prod-only --ignore-scripts`, - `cd dist && tar -czvf ../${pkg.name}-v${pkg.version}-bundle.tgz *` + "grunt build:docker", + `docker save openhaus/${pkg.name}:latest | gzip > ${path.join(PATH_DIST, `${pkg.name}-v${pkg.version}-docker.tgz`)}`, + "grunt install", + `cd ${PATH_BUILD} && tar -czvf ${path.join(PATH_DIST, `${pkg.name}-v${pkg.version}-bundle.tgz`)} *`, + "grunt checksum" ].forEach((cmd) => { cp.execSync(cmd, { env: process.env, @@ -141,9 +136,5 @@ module.exports = function (grunt) { }); }); - // Default task(s). - //grunt.registerTask("default", ["uglify"]); - //grunt.registerTask("install", ["install"]); - //grunt.registerTask("compress", ["compress"]); }; \ No newline at end of file diff --git a/components/plugins/index.js b/components/plugins/index.js index 6935dae..bbc0538 100644 --- a/components/plugins/index.js +++ b/components/plugins/index.js @@ -74,6 +74,12 @@ class C_PLUGINS extends COMPONENT { recursive: true }, (err) => { + // ignore when folder not exists + if (err?.code === "ENOENT") { + this.logger.warn("Plugin folder does not exists, continue anway.", err); + err = null; + } + next(err || null, item, result, _id); }); diff --git a/index.js b/index.js index 6479644..8109e2e 100644 --- a/index.js +++ b/index.js @@ -33,7 +33,7 @@ process.env = Object.assign({ DATABASE_UPDATE_DEBOUNCE_TIMER: "15", HTTP_PORT: "8080", HTTP_ADDRESS: "0.0.0.0", - HTTP_SOCKET: "", + HTTP_SOCKET: "/tmp/open-haus.sock", LOG_PATH: path.resolve(process.cwd(), "logs"), LOG_LEVEL: "info", LOG_DATEFORMAT: "yyyy.mm.dd - HH:MM.ss.l", @@ -473,9 +473,9 @@ const starter = new Promise((resolve) => { }); if (bootable.length > started) { - logger.debug(`${started}/${bootable.length} Plugins started (Someones are ignored! Check the logfiles.)`); + logger.warn(`${started}/${bootable.length} Plugins started (Check the previously logs)`); } else { - logger.debug(`${started}/${bootable.length} Plugins started`); + logger.info(`${started}/${bootable.length} Plugins started`); } logger.info("Startup complete"); diff --git a/postman.json b/postman.json index 32a5038..327ead9 100644 --- a/postman.json +++ b/postman.json @@ -238,7 +238,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"name\": \"New room name ;)\"\n}", + "raw": "{\n \"name\": \"Garage\",\n \"icon\": \"fa-solid fa-warehouse\"\n}", "options": { "raw": { "language": "json" @@ -246,7 +246,7 @@ } }, "url": { - "raw": "http://{{HOST}}:{{PORT}}/api/rooms/{{_id}}", + "raw": "http://{{HOST}}:{{PORT}}/api/rooms/63bf017582fa29f44cf8dfd6", "protocol": "http", "host": [ "{{HOST}}" @@ -255,7 +255,7 @@ "path": [ "api", "rooms", - "{{_id}}" + "63bf017582fa29f44cf8dfd6" ] } }, @@ -291,7 +291,7 @@ "method": "DELETE", "header": [], "url": { - "raw": "http://{{HOST}}:{{PORT}}/api/rooms/{{_id}}", + "raw": "http://{{HOST}}:{{PORT}}/api/rooms/63bf017582fa29f44cf8dfd6", "protocol": "http", "host": [ "{{HOST}}" @@ -300,7 +300,294 @@ "path": [ "api", "rooms", - "{{_id}}" + "63bf017582fa29f44cf8dfd6" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ] + }, + { + "name": "SSDP", + "item": [ + { + "name": "Create new ssdp item", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"status code: 200\", () => {", + " pm.response.to.have.status(200);", + "});", + "", + "pm.test(\"Check room name: input = output\", () => {", + "", + " let res = pm.response.json();", + " let req = JSON.parse(pm.request.body);", + "", + " pm.expect(res.name).to.eql(req.name);", + "", + "});", + "", + "pm.test(\"Check properties\", () => {", + "", + " let res = pm.response.json();", + "", + " let props = [", + " \"name\", \"timestamps\", \"_id\",", + " \"number\", \"floor\", \"icon\"", + " ];", + "", + " Object.keys(res).forEach((key) => {", + " pm.expect(props.includes(key)).to.be.true;", + " });", + "", + " props.forEach((item) => {", + " pm.expect(Object.prototype.hasOwnProperty.call(res, item)).to.be.true;", + " });", + "", + "})" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"nt\": \"urn:schemas-upnp-org:device:sensor:1\",\n \"description\": null,\n \"usn\": null\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{HOST}}:{{PORT}}/api/ssdp", + "protocol": "http", + "host": [ + "{{HOST}}" + ], + "port": "{{PORT}}", + "path": [ + "api", + "ssdp" + ] + } + }, + "response": [] + }, + { + "name": "Get all ssdp items", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"The response has all properties\", () => {", + " let json = pm.response.json();", + " pm.expect(json).to.have.lengthOf(json.length);", + "});", + "", + "pm.test(\"Status code is 200\", () => {", + " pm.response.to.have.status(200);", + "});", + "", + "console.log(\"Fooo\")" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [ + { + "key": "x-auth-token", + "value": "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImhhbnMuaHViZXJ0QGV4YW1wbGUuY29tIiwidXVpZCI6ImM3N2E3NjJkLWM4ODYtNGQ2My1iNGM1LWU0MDJhZGNmYTdiZSIsImlhdCI6MTY1NDI2ODI4NX0.w4mkvTuJ-OXzTcmvWhwIT84oOmo2399hSEfWGbA-9SUWndMWUiHvly1A7-kSV93e", + "type": "text", + "disabled": true + } + ], + "url": { + "raw": "http://{{HOST}}:{{PORT}}/api/ssdp", + "protocol": "http", + "host": [ + "{{HOST}}" + ], + "port": "{{PORT}}", + "path": [ + "api", + "ssdp" + ] + } + }, + "response": [] + }, + { + "name": "Get sinlge ssdp item", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://{{HOST}}:{{PORT}}/api/ssdp/63cbcdb1f2c46434242de489", + "protocol": "http", + "host": [ + "{{HOST}}" + ], + "port": "{{PORT}}", + "path": [ + "api", + "ssdp", + "63cbcdb1f2c46434242de489" + ] + } + }, + "response": [] + }, + { + "name": "Update existing ssdp item", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "PATCH", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"description\": \"Search for sensor via SSDP\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://{{HOST}}:{{PORT}}/api/ssdp/63cbcdb1f2c46434242de489", + "protocol": "http", + "host": [ + "{{HOST}}" + ], + "port": "{{PORT}}", + "path": [ + "api", + "ssdp", + "63cbcdb1f2c46434242de489" + ] + } + }, + "response": [] + }, + { + "name": "Delete exisiting ssdp item", + "event": [ + { + "listen": "prerequest", + "script": { + "exec": [ + "console.log(\"_id varaible\", pm.variables.get(\"_id\"));" + ], + "type": "text/javascript" + } + }, + { + "listen": "test", + "script": { + "exec": [ + "console.log(\"_id varaible\", pm.variables.get(\"_id\"));", + "", + "pm.test(\"status code: 200\", () => {", + " pm.response.to.have.status(200);", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "http://{{HOST}}:{{PORT}}/api/ssdp/63cbcdb1f2c46434242de489", + "protocol": "http", + "host": [ + "{{HOST}}" + ], + "port": "{{PORT}}", + "path": [ + "api", + "ssdp", + "63cbcdb1f2c46434242de489" ] } }, @@ -896,7 +1183,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"enabled\": false,\n \"name\": \"SaMsUnG FrIdGe\"\n}", + "raw": "{\n \"enabled\": true,\n \"name\": \"SaMsUnG FrIdGe\"\n}", "options": { "raw": { "language": "json" @@ -904,7 +1191,7 @@ } }, "url": { - "raw": "http://{{HOST}}:{{PORT}}/api/devices/611038f1f897a74ae9ca89c9", + "raw": "http://{{HOST}}:{{PORT}}/api/devices/63bf020e82fa29f44cf8dfd8", "protocol": "http", "host": [ "{{HOST}}" @@ -913,7 +1200,7 @@ "path": [ "api", "devices", - "611038f1f897a74ae9ca89c9" + "63bf020e82fa29f44cf8dfd8" ] } }, diff --git a/routes/rest-handler.js b/routes/rest-handler.js index 572bc75..5bd3700 100644 --- a/routes/rest-handler.js +++ b/routes/rest-handler.js @@ -15,7 +15,12 @@ module.exports = (C_COMPONENT, router) => { // no password should ever be sent to the client res.json = function (obj) { - _iterate(obj, (key, value) => { + // use this when node >= 17 + //obj = structuredClone(obj); + // fix #279 + obj = JSON.parse(JSON.stringify(obj)); + + obj = _iterate(obj, (key, value) => { // remove password key if present // this must be first