diff --git a/Backend/.eslintrc.json b/Backend/.eslintrc.json index f959725..1e11aff 100644 --- a/Backend/.eslintrc.json +++ b/Backend/.eslintrc.json @@ -20,8 +20,8 @@ ".eslintrc.js" ], "rules": { - "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/interface-name-prefix": "off", + "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-explicit-any": "off" } diff --git a/Backend/.prettierrc b/Backend/.prettierrc new file mode 100644 index 0000000..dcb7279 --- /dev/null +++ b/Backend/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/Backend/package-lock.json b/Backend/package-lock.json index 4d8786c..0efaeac 100644 --- a/Backend/package-lock.json +++ b/Backend/package-lock.json @@ -13,14 +13,19 @@ "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.2.0", "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-socket.io": "^10.2.10", + "@nestjs/platform-ws": "^10.2.10", "@nestjs/swagger": "^7.1.16", + "@nestjs/websockets": "^10.2.10", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "dotenv": "^16.3.1", "mongodb": "^6.3.0", "node-telegram-bot-api": "^0.64.0", "reflect-metadata": "^0.1.13", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "simple-peer": "^9.11.1", + "ws": "^8.14.2" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -33,8 +38,10 @@ "@types/multer": "^1.4.10", "@types/node": "^20.3.1", "@types/node-telegram-bot-api": "^0.63.3", + "@types/simple-peer": "^9.11.8", "@types/source-map-support": "^0.5.10", "@types/supertest": "^2.0.12", + "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "eslint": "^8.42.0", @@ -1788,6 +1795,42 @@ "@nestjs/core": "^10.0.0" } }, + "node_modules/@nestjs/platform-socket.io": { + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/@nestjs/platform-socket.io/-/platform-socket.io-10.2.10.tgz", + "integrity": "sha512-JBuemeIBp2mpp+z7D12oa22k83TnDTxyQDMKZpO/B2/QnBVR+2C4EZ07/XOct14LQXO6vIjmT0iYYCZbNvczjw==", + "dependencies": { + "socket.io": "4.7.2", + "tslib": "2.6.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/platform-ws": { + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/@nestjs/platform-ws/-/platform-ws-10.2.10.tgz", + "integrity": "sha512-x9L7jixAEtbNjP9hIm9Fmx+kL9ruFQLu2cUb0EdSNtwK/efAJZ3+Taz9T8g/Nm5DG4k0356X6hmRk74ChJHg9g==", + "dependencies": { + "tslib": "2.6.2", + "ws": "8.14.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "rxjs": "^7.1.0" + } + }, "node_modules/@nestjs/schematics": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.0.3.tgz", @@ -1862,6 +1905,28 @@ } } }, + "node_modules/@nestjs/websockets": { + "version": "10.2.10", + "resolved": "https://registry.npmjs.org/@nestjs/websockets/-/websockets-10.2.10.tgz", + "integrity": "sha512-L1AkxwLUj/ntk26jO1SXYl3GRElQE6Fikzfy/3MPFURk0GDs7tHUzLcb8BC8q8u5ZpUjBAC2wFVQzrY5R0MHNw==", + "dependencies": { + "iterare": "1.2.1", + "object-hash": "3.0.0", + "tslib": "2.6.2" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/platform-socket.io": "^10.0.0", + "reflect-metadata": "^0.1.12", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/platform-socket.io": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1968,6 +2033,11 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -2058,12 +2128,25 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, "node_modules/@types/cookiejar": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.3.tgz", "integrity": "sha512-LZ8SD3LpNmLMDLkG2oCBjZg+ETnx6XdCjydUE0HwojDmnDfDUnhMKKbtth1TZh+hzcqb03azrYWoXLS8sMXdqg==", "dev": true }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/eslint": { "version": "8.44.7", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.7.tgz", @@ -2281,6 +2364,15 @@ "@types/node": "*" } }, + "node_modules/@types/simple-peer": { + "version": "9.11.8", + "resolved": "https://registry.npmjs.org/@types/simple-peer/-/simple-peer-9.11.8.tgz", + "integrity": "sha512-rvqefdp2rvIA6wiomMgKWd2UZNPe6LM2EV5AuY3CPQJF+8TbdrL5TjYdMf0VAjGczzlkH4l1NjDkihwbj3Xodw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/source-map-support": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.10.tgz", @@ -2348,6 +2440,15 @@ "@types/webidl-conversions": "*" } }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.29", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz", @@ -3128,7 +3229,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -3144,6 +3244,14 @@ } ] }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -3887,7 +3995,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -4297,6 +4404,62 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -4310,6 +4473,11 @@ "node": ">=10.13.0" } }, + "node_modules/err-code": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-3.0.1.tgz", + "integrity": "sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==" + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -5274,6 +5442,11 @@ "node": ">=6.9.0" } }, + "node_modules/get-browser-rtc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-browser-rtc/-/get-browser-rtc-1.1.0.tgz", + "integrity": "sha512-MghbMJ61EJrRsDe7w1Bvqt3ZsBuqhce5nrn/XAwgwOXhcsz53/ltdxOse1h/8eKXj5slzxdsz56g5rzOFSGwfQ==" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5672,7 +5845,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -7661,6 +7833,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -8219,7 +8399,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, "funding": [ { "type": "github", @@ -8239,7 +8418,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "dependencies": { "safe-buffer": "^5.1.0" } @@ -9016,6 +9194,70 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-peer": { + "version": "9.11.1", + "resolved": "https://registry.npmjs.org/simple-peer/-/simple-peer-9.11.1.tgz", + "integrity": "sha512-D1SaWpOW8afq1CZGWB8xTfrT3FekjQmPValrqncJMX7QFl8YwhrPTZvMCANLtgBwwdS+7zURyqxDDEmY558tTw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "buffer": "^6.0.3", + "debug": "^4.3.2", + "err-code": "^3.0.1", + "get-browser-rtc": "^1.1.0", + "queue-microtask": "^1.2.3", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/simple-peer/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/simple-peer/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -9031,6 +9273,63 @@ "node": ">=8" } }, + "node_modules/socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -10412,6 +10711,26 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, + "node_modules/ws": { + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/Backend/package.json b/Backend/package.json index 9ad6c5e..8fd2285 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -24,14 +24,19 @@ "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.2.0", "@nestjs/platform-express": "^10.0.0", + "@nestjs/platform-socket.io": "^10.2.10", + "@nestjs/platform-ws": "^10.2.10", "@nestjs/swagger": "^7.1.16", + "@nestjs/websockets": "^10.2.10", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "dotenv": "^16.3.1", "mongodb": "^6.3.0", "node-telegram-bot-api": "^0.64.0", "reflect-metadata": "^0.1.13", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "simple-peer": "^9.11.1", + "ws": "^8.14.2" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -44,8 +49,10 @@ "@types/multer": "^1.4.10", "@types/node": "^20.3.1", "@types/node-telegram-bot-api": "^0.63.3", + "@types/simple-peer": "^9.11.8", "@types/source-map-support": "^0.5.10", "@types/supertest": "^2.0.12", + "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "eslint": "^8.42.0", diff --git a/Backend/src/DataType.ts b/Backend/src/DataType.ts index 31ca56f..4cd26ba 100644 --- a/Backend/src/DataType.ts +++ b/Backend/src/DataType.ts @@ -2,7 +2,7 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Binary } from "mongodb"; +import { Binary } from 'mongodb'; export default class DataType { cameraId: ID; diff --git a/Backend/src/app.module.ts b/Backend/src/app.module.ts index ee1bd58..92466c3 100644 --- a/Backend/src/app.module.ts +++ b/Backend/src/app.module.ts @@ -2,13 +2,14 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Module } from "@nestjs/common"; -import { MachineLearningController } from "./app/machineLearning/machineLearning.controller"; -import { FrontendController } from "./app/frontend/frontend.controller"; -import { DatabaseService } from "./database/database.service"; -import { TelegramService } from "./telegram/telegram.service"; -import { JwtModule } from "@nestjs/jwt"; -import { LoginController } from "./app/login.controller"; +import { Module } from '@nestjs/common'; +import { MachineLearningController } from './app/machineLearning/machineLearning.controller'; +import { FrontendController } from './app/frontend/frontend.controller'; +import { DatabaseService } from './database/database.service'; +import { TelegramService } from './telegram/telegram.service'; +import { JwtModule } from '@nestjs/jwt'; +import { LoginController } from './app/login.controller'; +import { CameraStreamModule } from './cameraStream/cameraStream.module'; @Module({ imports: [ @@ -18,6 +19,7 @@ import { LoginController } from "./app/login.controller"; // FIXME Error: Payload as string is not allowed with the following sign options: expiresIn // signOptions: { expiresIn: "60s" }, }), + CameraStreamModule, ], controllers: [MachineLearningController, FrontendController, LoginController], providers: [DatabaseService, TelegramService], diff --git a/Backend/src/app/frontend/frontend.controller.spec.ts b/Backend/src/app/frontend/frontend.controller.spec.ts index 5c1a0b2..1bc4d4b 100644 --- a/Backend/src/app/frontend/frontend.controller.spec.ts +++ b/Backend/src/app/frontend/frontend.controller.spec.ts @@ -2,12 +2,12 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from "@nestjs/testing"; -import { FrontendController } from "./frontend.controller"; -import { DatabaseService } from "../../database/database.service"; -import { JwtModule } from "@nestjs/jwt"; +import { Test, TestingModule } from '@nestjs/testing'; +import { FrontendController } from './frontend.controller'; +import { DatabaseService } from '../../database/database.service'; +import { JwtModule } from '@nestjs/jwt'; -describe("FrontendController", () => { +describe('FrontendController', () => { let controller: FrontendController; beforeEach(async () => { @@ -25,7 +25,7 @@ describe("FrontendController", () => { controller = module.get(FrontendController); }); - it("should be defined", () => { + it('should be defined', () => { expect(controller).toBeDefined(); }); }); diff --git a/Backend/src/app/frontend/frontend.controller.ts b/Backend/src/app/frontend/frontend.controller.ts index d051f86..863a0c1 100644 --- a/Backend/src/app/frontend/frontend.controller.ts +++ b/Backend/src/app/frontend/frontend.controller.ts @@ -5,8 +5,8 @@ import { Param, StreamableFile, UseGuards, -} from "@nestjs/common"; -import { DatabaseService } from "../../database/database.service"; +} from '@nestjs/common'; +import { DatabaseService } from '../../database/database.service'; import { ApiBadRequestResponse, ApiBearerAuth, @@ -14,45 +14,45 @@ import { ApiParam, ApiTags, ApiUnauthorizedResponse, -} from "@nestjs/swagger"; +} from '@nestjs/swagger'; import { filters, FiltersAvailable, FiltersValidator, -} from "../../validators/filters/filters.pipe"; +} from '../../validators/filters/filters.pipe'; import { CameraIds, CameraValidator, -} from "../../validators/camera-id/camera.pipe"; -import { AuthGuard } from "../../auth/auth.guard"; +} from '../../validators/camera-id/camera.pipe'; +import { AuthGuard } from '../../auth/auth.guard'; const filterParams = { - name: "filter", - type: "string", + name: 'filter', + type: 'string', examples: { online: { - value: "online", + value: 'online', }, offline: { - value: "offline", + value: 'offline', }, intrusionDetection: { - value: "intrusionDetection", + value: 'intrusionDetection', }, all: { - value: "all", + value: 'all', }, }, }; -@ApiTags("AuthenticatedFrontend") -@ApiBearerAuth("CSS-Auth") +@ApiTags('AuthenticatedFrontend') +@ApiBearerAuth('CSS-Auth') @ApiOkResponse() @ApiUnauthorizedResponse({ - description: "Unauthorized", + description: 'Unauthorized', }) @ApiBadRequestResponse({ - description: "Camera id or filter is invalid", + description: 'Camera id or filter is invalid', }) @UseGuards(AuthGuard) @Controller() @@ -60,37 +60,37 @@ export class FrontendController { constructor(private readonly databaseService: DatabaseService) {} @ApiParam(filterParams) - @Get(`:filter(${filters.join("|")})/aggregate`) + @Get(`:filter(${filters.join('|')})/aggregate`) getAggregateValues( - @Param("filter", FiltersValidator) filter: FiltersAvailable, + @Param('filter', FiltersValidator) filter: FiltersAvailable, ) { return this.databaseService.aggregateCamera(filter); } @ApiParam(filterParams) - @Get(`:filter(${filters.join("|")})`) - getValues(@Param("filter", FiltersValidator) filter: FiltersAvailable) { + @Get(`:filter(${filters.join('|')})`) + getValues(@Param('filter', FiltersValidator) filter: FiltersAvailable) { return this.databaseService.getData(filter); } @ApiParam({ - name: "id", - type: "number", - description: "Camera id", + name: 'id', + type: 'number', + description: 'Camera id', example: 1, }) @ApiParam({ - name: "timestamp", - type: "string", - example: "2023-11-23T18:38:35.571Z", + name: 'timestamp', + type: 'string', + example: '2023-11-23T18:38:35.571Z', }) - @Header("Content-Type", "image/jpeg") - @Get("/:id(\\d+)/:timestamp") + @Header('Content-Type', 'image/jpeg') + @Get('/:id(\\d+)/:timestamp') async getImage( - @Param("id", CameraValidator) cameraId: CameraIds, - @Param("timestamp") timestamp: string, + @Param('id', CameraValidator) cameraId: CameraIds, + @Param('timestamp') timestamp: string, ) { - const array = await this.databaseService.getRawDataArray("cameras", { + const array = await this.databaseService.getRawDataArray('cameras', { cameraId: cameraId, timestamp: timestamp, }); diff --git a/Backend/src/app/login.controller.ts b/Backend/src/app/login.controller.ts index 03640db..47f7f17 100644 --- a/Backend/src/app/login.controller.ts +++ b/Backend/src/app/login.controller.ts @@ -2,20 +2,20 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Body, Controller, Header, Post } from "@nestjs/common"; +import { Body, Controller, Header, Post } from '@nestjs/common'; import { ApiBody, ApiCreatedResponse, ApiNotFoundResponse, ApiTags, -} from "@nestjs/swagger"; -import UserDTO from "../user.dto"; -import { DatabaseService } from "../database/database.service"; -import { JwtService } from "@nestjs/jwt"; -import * as process from "process"; +} from '@nestjs/swagger'; +import UserDTO from '../user.dto'; +import { DatabaseService } from '../database/database.service'; +import { JwtService } from '@nestjs/jwt'; +import * as process from 'process'; -@ApiTags("Frontend") -@Controller("/") +@ApiTags('Frontend') +@Controller('/') export class LoginController { constructor( private readonly databaseService: DatabaseService, @@ -24,27 +24,27 @@ export class LoginController { @ApiBody({ type: String, - description: "User", + description: 'User', examples: { a: { - summary: "Existing user", + summary: 'Existing user', value: { name: process.env.CSD_USER, password: process.env.CSD_PASSWORD, }, }, b: { - summary: "Non existing user", - value: { name: "non", password: "Basic" }, + summary: 'Non existing user', + value: { name: 'non', password: 'Basic' }, }, }, }) @ApiCreatedResponse() @ApiNotFoundResponse() - @Header("Content-Type", "application/json") - @Post("login") + @Header('Content-Type', 'application/json') + @Post('login') async login(@Body() user: UserDTO) { - await this.databaseService.getRawDataArray("users", user, "User Not found"); + await this.databaseService.getRawDataArray('users', user, 'User Not found'); return { access_token: await this.jwtService.signAsync(user.name), diff --git a/Backend/src/app/machineLearning/machineLearning.controller.spec.ts b/Backend/src/app/machineLearning/machineLearning.controller.spec.ts index a6c7e48..c37c9a2 100644 --- a/Backend/src/app/machineLearning/machineLearning.controller.spec.ts +++ b/Backend/src/app/machineLearning/machineLearning.controller.spec.ts @@ -2,13 +2,13 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from "@nestjs/testing"; -import { MachineLearningController } from "./machineLearning.controller"; -import { DatabaseService } from "../../database/database.service"; -import { TelegramService } from "../../telegram/telegram.service"; -import { JwtModule } from "@nestjs/jwt"; +import { Test, TestingModule } from '@nestjs/testing'; +import { MachineLearningController } from './machineLearning.controller'; +import { DatabaseService } from '../../database/database.service'; +import { TelegramService } from '../../telegram/telegram.service'; +import { JwtModule } from '@nestjs/jwt'; -describe("MachineLearningController", () => { +describe('MachineLearningController', () => { let controller: MachineLearningController; beforeEach(async () => { @@ -26,7 +26,7 @@ describe("MachineLearningController", () => { controller = app.get(MachineLearningController); }); - it("should exists", () => { + it('should exists', () => { expect(controller).toBeDefined(); }); diff --git a/Backend/src/app/machineLearning/machineLearning.controller.ts b/Backend/src/app/machineLearning/machineLearning.controller.ts index a24e4c3..ade432b 100644 --- a/Backend/src/app/machineLearning/machineLearning.controller.ts +++ b/Backend/src/app/machineLearning/machineLearning.controller.ts @@ -12,10 +12,10 @@ import { UploadedFile, UseGuards, UseInterceptors, -} from "@nestjs/common"; -import { DatabaseService } from "../../database/database.service"; -import { FileInterceptor } from "@nestjs/platform-express"; -import DataType from "../../DataType"; +} from '@nestjs/common'; +import { DatabaseService } from '../../database/database.service'; +import { FileInterceptor } from '@nestjs/platform-express'; +import DataType from '../../DataType'; import { ApiBadRequestResponse, ApiBearerAuth, @@ -26,28 +26,28 @@ import { ApiParam, ApiProperty, ApiTags, -} from "@nestjs/swagger"; +} from '@nestjs/swagger'; import { CameraIds, CameraValidator, -} from "../../validators/camera-id/camera.pipe"; -import { AuthGuard } from "../../auth/auth.guard"; -import { TelegramService } from "../../telegram/telegram.service"; +} from '../../validators/camera-id/camera.pipe'; +import { AuthGuard } from '../../auth/auth.guard'; +import { TelegramService } from '../../telegram/telegram.service'; class ImageUploadDto { @ApiProperty({ - type: "string", - format: "binary", - description: "Image file to upload", + type: 'string', + format: 'binary', + description: 'Image file to upload', }) file: any; } -@ApiTags("Machine Learning") +@ApiTags('Machine Learning') @ApiBadRequestResponse({ - description: "Invalid filter or camera id", + description: 'Invalid filter or camera id', }) -@Controller("/:id(\\d+)") +@Controller('/:id(\\d+)') export class MachineLearningController { constructor( private readonly database: DatabaseService, @@ -55,66 +55,66 @@ export class MachineLearningController { ) {} @ApiOperation({ - description: "Updates the online status of the camera", + description: 'Updates the online status of the camera', }) @ApiParam({ - name: "id", - type: "number", + name: 'id', + type: 'number', example: 1, }) @ApiParam({ - name: "status", - type: "string", + name: 'status', + type: 'string', examples: { online: { - value: "online", + value: 'online', }, offline: { - value: "offline", + value: 'offline', }, }, }) @ApiCreatedResponse() - @ApiBearerAuth("CSS-Auth") + @ApiBearerAuth('CSS-Auth') @UseGuards(AuthGuard) @Post(`:status(online|offline)`) saveStatus( - @Param("id", CameraValidator) cameraId: CameraIds, - @Param("status") status: string, + @Param('id', CameraValidator) cameraId: CameraIds, + @Param('status') status: string, ) { // Following condition could be removed as the path can only be online or offline - if (status.toLowerCase() != "online" && status.toLowerCase() != "offline") + if (status.toLowerCase() != 'online' && status.toLowerCase() != 'offline') throw new BadRequestException(`Invalid status ${status}`); return this.database.addData({ cameraId: cameraId, timestamp: new Date().toISOString(), - online: status.toLowerCase() === "online", + online: status.toLowerCase() === 'online', }); } @ApiOperation({ - description: "Used to send image", + description: 'Used to send image', }) - @ApiBearerAuth("CSS-Auth") - @ApiConsumes("multipart/form-data") + @ApiBearerAuth('CSS-Auth') + @ApiConsumes('multipart/form-data') @ApiBody({ description: `The image file to upload`, type: ImageUploadDto, }) @ApiParam({ - name: "id", - type: "number", + name: 'id', + type: 'number', example: 1, }) // @UseGuards(AuthGuard) - @UseInterceptors(FileInterceptor("file")) + @UseInterceptors(FileInterceptor('file')) @Post() async uploadImage( - @Param("id", CameraValidator) cameraId: CameraIds, + @Param('id', CameraValidator) cameraId: CameraIds, @UploadedFile( new ParseFilePipeBuilder() - .addFileTypeValidator({ fileType: "image/jpeg" }) + .addFileTypeValidator({ fileType: 'image/jpeg' }) .addMaxSizeValidator({ maxSize: 100000, // 100Kb }) diff --git a/Backend/src/auth/auth.guard.spec.ts b/Backend/src/auth/auth.guard.spec.ts index f9433df..7c10573 100644 --- a/Backend/src/auth/auth.guard.spec.ts +++ b/Backend/src/auth/auth.guard.spec.ts @@ -1,7 +1,7 @@ -import { JwtService } from "@nestjs/jwt"; +import { JwtService } from '@nestjs/jwt'; -describe("AuthGuard", () => { - const payload = "complexUserNamePayload"; +describe('AuthGuard', () => { + const payload = 'complexUserNamePayload'; let service: JwtService; let jwtToken: string; @@ -12,20 +12,20 @@ describe("AuthGuard", () => { jwtToken = await service.signAsync(payload); }); - it("Should be defined", () => { + it('Should be defined', () => { expect(service).toBeDefined(); expect(jwtToken).toBeDefined(); }); - it("Get Payload from token", () => { + it('Get Payload from token', () => { const user = service.verify(jwtToken); expect(user).toBe(payload); }); - it("Should fail JwtVerify of another token", () => { + it('Should fail JwtVerify of another token', () => { expect(() => service.verify( - "eyJhbGciOiJIUzI1NiJ9.QmFzaWM.MTnCJYESf5QRL9N8gqn5Di5PEZX8eZB5sN8W4TJTDKF", + 'eyJhbGciOiJIUzI1NiJ9.QmFzaWM.MTnCJYESf5QRL9N8gqn5Di5PEZX8eZB5sN8W4TJTDKF', ), ).toThrow(); }); diff --git a/Backend/src/auth/auth.guard.ts b/Backend/src/auth/auth.guard.ts index 0e6c571..9b3b54d 100644 --- a/Backend/src/auth/auth.guard.ts +++ b/Backend/src/auth/auth.guard.ts @@ -5,9 +5,9 @@ import { HttpException, HttpStatus, Injectable, -} from "@nestjs/common"; -import { JwtService } from "@nestjs/jwt"; -import { Request } from "express"; +} from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { IncomingHttpHeaders } from 'http'; @Injectable() export class AuthGuard implements CanActivate { @@ -21,24 +21,38 @@ export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); - const token = this.extractTokenFromHeader(request); try { - request["user"] = this.jwtService.verify(token); - token; + const headers = this.getGenericHeaders(context); + + //TODO check if the user is saved + request['user'] = this.checkToken(headers); } catch (e) { throw new HttpException(e.message, HttpStatus.UNAUTHORIZED); } return true; } - private extractTokenFromHeader(request: Request): string { - const [type, token] = request.headers.authorization?.split(" ") ?? []; + // token saved as `Bearer ${token}` + checkToken(headers: Record | IncomingHttpHeaders) { + const token = headers.authorization; - if (type != "Bearer") { - throw new ForbiddenException("No token provided"); + if (token == undefined || !token.startsWith('Bearer ')) { + throw new ForbiddenException('No token provided'); } - return token; + return this.jwtService.verify(token.slice(7, token.length)); + } + + private getGenericHeaders(context: ExecutionContext): Record { + const request = context.switchToHttp().getRequest(); + switch (context.getType()) { + case 'rpc': + throw new HttpException('rpc protocol', HttpStatus.NOT_IMPLEMENTED); + case 'ws': + return request.handshake.headers; + case 'http': + return request.headers; + } } } diff --git a/Backend/src/cameraStream/cameraStream.gateway.spec.ts b/Backend/src/cameraStream/cameraStream.gateway.spec.ts new file mode 100644 index 0000000..04ff551 --- /dev/null +++ b/Backend/src/cameraStream/cameraStream.gateway.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CameraStreamGateway } from './cameraStreamGateway'; + +describe('WebrtcGateway', () => { + let gateway: CameraStreamGateway; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [CameraStreamGateway], + }).compile(); + + gateway = module.get(CameraStreamGateway); + }); + + it('should be defined', () => { + expect(gateway).toBeDefined(); + }); +}); diff --git a/Backend/src/cameraStream/cameraStream.module.ts b/Backend/src/cameraStream/cameraStream.module.ts new file mode 100644 index 0000000..af65b7e --- /dev/null +++ b/Backend/src/cameraStream/cameraStream.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { CameraStreamGateway } from './cameraStreamGateway'; +import { JwtModule } from '@nestjs/jwt'; + +@Module({ + imports: [ + JwtModule.register({ + global: true, + secret: process.env.JWT_SECRET, + // FIXME Error: Payload as string is not allowed with the following sign options: expiresIn + // signOptions: { expiresIn: "60s" }, + }), + ], + providers: [CameraStreamGateway], +}) +export class CameraStreamModule {} diff --git a/Backend/src/cameraStream/cameraStreamGateway.ts b/Backend/src/cameraStream/cameraStreamGateway.ts new file mode 100644 index 0000000..4208a3f --- /dev/null +++ b/Backend/src/cameraStream/cameraStreamGateway.ts @@ -0,0 +1,71 @@ +// websocket.gateway.ts +import { + WebSocketGateway, + SubscribeMessage, + ConnectedSocket, + MessageBody, + OnGatewayConnection, + WsException, +} from '@nestjs/websockets'; +import { Socket } from 'socket.io'; +import { AuthGuard } from '../auth/auth.guard'; +import { + Catch, + ExecutionContext, + HttpException, + UseFilters, + UseGuards, + ValidationPipe, +} from '@nestjs/common'; +import * as console from 'console'; +import { SocketsContainer } from '@nestjs/websockets/sockets-container'; +import { DatabaseService } from '../database/database.service'; +import { TelegramService } from '../telegram/telegram.service'; +import { JwtService } from '@nestjs/jwt'; + +@Catch(WsException, HttpException) +export class WsExceptionFilter implements WsExceptionFilter { + catch(exception: WsException, host: ExecutionContext) { + host.switchToWs().getClient().disconnect(); + } +} + +type Message = { id: number; data: Buffer }; + +@WebSocketGateway({ + transports: ['websocket'], + cors: { + origin: '*', + }, + namespace: '/', +}) +@UseGuards(AuthGuard) +@UseFilters(WsExceptionFilter) +export class CameraStreamGateway implements OnGatewayConnection { + constructor(private readonly jwtService: JwtService) {} + + handleConnection(@ConnectedSocket() client: Socket) { + try { + new AuthGuard(this.jwtService).checkToken(client.handshake.headers); + } catch (e) { + client.disconnect(); + return; + } + client.join('clients'); + } + + @SubscribeMessage('message') + private broadcastMessage( + @ConnectedSocket() client: Socket, + @MessageBody() data: string, + ) { + try { + const message = JSON.parse(data) as Message; + console.log(message.id, message.data); + + client.to('clients').emit(message.id.toString(), message.data); + } catch (e) { + return e.message; + } + } +} diff --git a/Backend/src/database/database.service.spec.ts b/Backend/src/database/database.service.spec.ts index 271a3f6..9d1f7cb 100644 --- a/Backend/src/database/database.service.spec.ts +++ b/Backend/src/database/database.service.spec.ts @@ -2,10 +2,10 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from "@nestjs/testing"; -import { DatabaseService } from "./database.service"; +import { Test, TestingModule } from '@nestjs/testing'; +import { DatabaseService } from './database.service'; -describe("DatabaseService", () => { +describe('DatabaseService', () => { let databaseService: DatabaseService; beforeEach(async () => { @@ -16,23 +16,23 @@ describe("DatabaseService", () => { databaseService = module.get(DatabaseService); }); - it("should be defined", () => { + it('should be defined', () => { expect(databaseService).toBeDefined(); }); - it("should get aggregated data", async () => { + it('should get aggregated data', async () => { const aggregateData = await databaseService.aggregateCamera(); expect(aggregateData).not.toBeNull(); }); - it("should get single data", async () => { - const aggregateData = await databaseService.getData("all"); + it('should get single data', async () => { + const aggregateData = await databaseService.getData('all'); expect(aggregateData).not.toBeNull(); }); - it("should get image data", async () => { - const aggregateData = await databaseService.getData("all"); + it('should get image data', async () => { + const aggregateData = await databaseService.getData('all'); aggregateData .filter((value) => value.intrusionDetection) @@ -43,7 +43,7 @@ describe("DatabaseService", () => { ); const spy = jest.fn(); - await databaseService.getImage(1, "this will make it throw").catch(spy); + await databaseService.getImage(1, 'this will make it throw').catch(spy); expect(spy).toHaveBeenCalled(); }); }); diff --git a/Backend/src/database/database.service.ts b/Backend/src/database/database.service.ts index e3b6501..f369556 100644 --- a/Backend/src/database/database.service.ts +++ b/Backend/src/database/database.service.ts @@ -6,14 +6,14 @@ import { Injectable, NotAcceptableException, NotFoundException, -} from "@nestjs/common"; -import { Db, Document, Filter, MatchKeysAndValues, MongoClient } from "mongodb"; -import "dotenv/config"; -import DataType from "../DataType"; -import { CameraIds } from "../validators/camera-id/camera.pipe"; -import { FiltersAvailable } from "../validators/filters/filters.pipe"; -import * as process from "process"; -import UserDTO from "../user.dto"; +} from '@nestjs/common'; +import { Db, Document, Filter, MatchKeysAndValues, MongoClient } from 'mongodb'; +import 'dotenv/config'; +import DataType from '../DataType'; +import { CameraIds } from '../validators/camera-id/camera.pipe'; +import { FiltersAvailable } from '../validators/filters/filters.pipe'; +import * as process from 'process'; +import UserDTO from '../user.dto'; const url = `mongodb://${process.env.MONGO_INITDB_ROOT_USERNAME}:${process.env.MONGO_INITDB_ROOT_PASSWORD}@${process.env.MONGO_HOST}`; @@ -25,9 +25,9 @@ export class DatabaseService { const client = new MongoClient(url); client.connect(); - this.DB = client.db("csd"); + this.DB = client.db('csd'); // If no user exists it automatically creates one with the default credentials in env file - this.DB.collection("users") + this.DB.collection('users') .countDocuments() .then((size) => { if (size == 0) { @@ -45,14 +45,14 @@ export class DatabaseService { } getData(filter?: FiltersAvailable): Promise { - return this.DB.collection("cameras") + return this.DB.collection('cameras') .aggregate([ { $addFields: { intrusionDetection: { $cond: { if: { - $ifNull: ["$intrusionDetection", false], + $ifNull: ['$intrusionDetection', false], }, then: true, else: false, @@ -66,11 +66,11 @@ export class DatabaseService { } aggregateCamera(filter?: FiltersAvailable): Promise { - return this.DB.collection("cameras") + return this.DB.collection('cameras') .aggregate() .match(this.getFilter(filter)) .group({ - _id: "$cameraId", + _id: '$cameraId', count: { $sum: 1, }, @@ -79,7 +79,7 @@ export class DatabaseService { } async getImage(cameraId: number, timestamp: string): Promise { - const array = await this.getRawDataArray("cameras", { + const array = await this.getRawDataArray('cameras', { cameraId: cameraId, timestamp: timestamp, }); @@ -89,9 +89,9 @@ export class DatabaseService { async getRawDataArray( collection: string, filter: Filter = {}, - errorString0: string = "Data Not found", + errorString0: string = 'Data Not found', limit: number = 1, - errorStringExceed: string = "Too much data found", + errorStringExceed: string = 'Too much data found', ) { const array = await this.DB.collection(collection).find(filter).toArray(); @@ -106,9 +106,9 @@ export class DatabaseService { user: Filter, newData: MatchKeysAndValues, ) { - await this.getRawDataArray("users", user, "User Not found"); + await this.getRawDataArray('users', user, 'User Not found'); - return this.DB.collection("users").updateOne(user, { + return this.DB.collection('users').updateOne(user, { $set: newData, }); } @@ -120,19 +120,19 @@ export class DatabaseService { private getFilter(filter?: FiltersAvailable) { switch (filter) { - case "intrusionDetection": + case 'intrusionDetection': return { intrusionDetection: { $eq: true }, }; - case "online": + case 'online': return { online: { $eq: true }, }; - case "offline": + case 'offline': return { online: { $eq: false }, }; - case "all": + case 'all': default: return {}; } diff --git a/Backend/src/env.spec.ts b/Backend/src/env.spec.ts index e0c50dc..df587da 100644 --- a/Backend/src/env.spec.ts +++ b/Backend/src/env.spec.ts @@ -2,26 +2,26 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -describe("FiltersPipe", () => { - it("MONGO_INITDB_ROOT_USERNAME should be defined", () => { +describe('FiltersPipe', () => { + it('MONGO_INITDB_ROOT_USERNAME should be defined', () => { expect(process.env.MONGO_INITDB_ROOT_USERNAME).toBeDefined(); }); - it("MONGO_INITDB_ROOT_PASSWORD should be defined", () => { + it('MONGO_INITDB_ROOT_PASSWORD should be defined', () => { expect(process.env.MONGO_INITDB_ROOT_PASSWORD).toBeDefined(); }); - it("JWT_SECRET should be defined", () => { + it('JWT_SECRET should be defined', () => { expect(process.env.JWT_SECRET).toBeDefined(); }); - it("CSD_USER should be defined", () => { + it('CSD_USER should be defined', () => { expect(process.env.CSD_USER).toBeDefined(); }); - it("CSD_PASSWORD should be defined", () => { + it('CSD_PASSWORD should be defined', () => { expect(process.env.CSD_PASSWORD).toBeDefined(); }); - it("TELEGRAM_TOKEN should be defined", () => { + it('TELEGRAM_TOKEN should be defined', () => { expect(process.env.TELEGRAM_TOKEN).toBeDefined(); }); - it("MONGO_HOST should be defined", () => { + it('MONGO_HOST should be defined', () => { expect(process.env.MONGO_HOST).toBeDefined(); }); }); diff --git a/Backend/src/main.ts b/Backend/src/main.ts index 4f5c2e3..2bde435 100644 --- a/Backend/src/main.ts +++ b/Backend/src/main.ts @@ -2,39 +2,43 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { NestFactory } from "@nestjs/core"; -import { AppModule } from "./app.module"; -import { ValidationPipe } from "@nestjs/common"; -import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger"; +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; +import { ValidationPipe } from '@nestjs/common'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { IoAdapter } from '@nestjs/platform-socket.io'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); + // app.useWebSocketAdapter(new WsAdapter(app)); + app.useWebSocketAdapter(new IoAdapter(app)); const config = new DocumentBuilder() - .setTitle("Complex System Design") - .setVersion("1.0") - .setDescription("The backend API description") - .addServer("", "localhost") + .setTitle('Complex System Design') + .setVersion('1.0') + .setDescription('The backend API description') + .addServer('', 'localhost') .addBearerAuth( { - type: "http", - scheme: "bearer", - bearerFormat: "JWT", - name: "JWT", - description: "Enter your JWT token", - in: "header", + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + name: 'JWT', + description: 'Enter your JWT token', + in: 'header', }, - "CSS-Auth", + 'CSS-Auth', ) .build(); const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup("swagger-api", app, document); + SwaggerModule.setup('swagger-api', app, document); await app.listen(8080); console.log( - "\nApp started, look at http://localhost:8080/swagger-api for the documentation", + '\nApp started, look at http://localhost:8080/swagger-api for the documentation', ); } + bootstrap(); diff --git a/Backend/src/telegram/telegram.service.ts b/Backend/src/telegram/telegram.service.ts index 4f4be9a..d0706a3 100644 --- a/Backend/src/telegram/telegram.service.ts +++ b/Backend/src/telegram/telegram.service.ts @@ -2,11 +2,11 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Injectable, NotFoundException } from "@nestjs/common"; -import * as TelegramBot from "node-telegram-bot-api"; -import * as process from "process"; -import { DatabaseService } from "../database/database.service"; -import UserDTO from "../user.dto"; +import { Injectable, NotFoundException } from '@nestjs/common'; +import * as TelegramBot from 'node-telegram-bot-api'; +import * as process from 'process'; +import { DatabaseService } from '../database/database.service'; +import UserDTO from '../user.dto'; @Injectable() export class TelegramService { @@ -24,8 +24,8 @@ export class TelegramService { private async welcome(msg: TelegramBot.Message) { await this.bot.sendMessage( msg.chat.id, - "Welcome to CSS bot\n" + - "This bot was developed by Leonardo Migliorelli\n" + + 'Welcome to CSS bot\n' + + 'This bot was developed by Leonardo Migliorelli\n' + "Please login with '/user '\n" + "To disable intrusion detection, type '/disable'" + "To reenable intrusion detection, type '/enable'", @@ -34,7 +34,7 @@ export class TelegramService { private async onLogin(msg: TelegramBot.Message) { // removes /start command and then format name and password - const array = msg.text.substring(6).split(" "); + const array = msg.text.substring(6).split(' '); const userData: UserDTO = { name: array[0], password: array[1], @@ -74,13 +74,13 @@ export class TelegramService { } catch (e) { await this.bot.sendMessage( msg.chat.id, - e == NotFoundException ? "You are not logged in" : e.message, + e == NotFoundException ? 'You are not logged in' : e.message, ); return; } await this.bot.sendMessage( msg.chat.id, - `Intrusion detection ${status ? "enabled" : "disabled"}`, + `Intrusion detection ${status ? 'enabled' : 'disabled'}`, ); return; } @@ -90,7 +90,7 @@ export class TelegramService { date: Date, image: Buffer, ) { - const users = await this.databaseService.getRawDataArray("users"); + const users = await this.databaseService.getRawDataArray('users'); users .filter((user) => user.getsAlerts) diff --git a/Backend/src/user.dto.ts b/Backend/src/user.dto.ts index a3243b0..5619f14 100644 --- a/Backend/src/user.dto.ts +++ b/Backend/src/user.dto.ts @@ -2,7 +2,7 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { IsString } from "class-validator"; +import { IsString } from 'class-validator'; export default class UserDTO { @IsString() diff --git a/Backend/src/validators/camera-id/camera.pipe.spec.ts b/Backend/src/validators/camera-id/camera.pipe.spec.ts index 28c0517..4dbd627 100644 --- a/Backend/src/validators/camera-id/camera.pipe.spec.ts +++ b/Backend/src/validators/camera-id/camera.pipe.spec.ts @@ -1,12 +1,12 @@ -import { cameraIds, CameraValidator } from "./camera.pipe"; +import { cameraIds, CameraValidator } from './camera.pipe'; -describe("CameraIdPipe", () => { +describe('CameraIdPipe', () => { const validator = new CameraValidator(); - it("should throw as not correct value", () => { - expect(() => validator.transform("200")).toThrow(); + it('should throw as not correct value', () => { + expect(() => validator.transform('200')).toThrow(); }); - it("should accept all the values", () => { + it('should accept all the values', () => { cameraIds.forEach((i) => expect(validator.transform(`${i}`)).toBe(i)); }); }); diff --git a/Backend/src/validators/camera-id/camera.pipe.ts b/Backend/src/validators/camera-id/camera.pipe.ts index e561411..5570e6c 100644 --- a/Backend/src/validators/camera-id/camera.pipe.ts +++ b/Backend/src/validators/camera-id/camera.pipe.ts @@ -3,7 +3,7 @@ import { HttpStatus, Injectable, PipeTransform, -} from "@nestjs/common"; +} from '@nestjs/common'; @Injectable() export class CameraValidator implements PipeTransform { @@ -12,7 +12,7 @@ export class CameraValidator implements PipeTransform { if (!cameraIds.includes(cameraId)) { throw new HttpException( - "Invalid camera Id " + cameraId, + 'Invalid camera Id ' + cameraId, HttpStatus.BAD_REQUEST, ); } diff --git a/Backend/src/validators/filters/filters.pipe.spec.ts b/Backend/src/validators/filters/filters.pipe.spec.ts index cb796de..7901b57 100644 --- a/Backend/src/validators/filters/filters.pipe.spec.ts +++ b/Backend/src/validators/filters/filters.pipe.spec.ts @@ -1,12 +1,12 @@ -import { filters, FiltersValidator } from "./filters.pipe"; +import { filters, FiltersValidator } from './filters.pipe'; -describe("FiltersPipe", () => { +describe('FiltersPipe', () => { const validator = new FiltersValidator(); - it("should throw as not correct value", () => { - expect(() => validator.transform("this throws error")).toThrow(); + it('should throw as not correct value', () => { + expect(() => validator.transform('this throws error')).toThrow(); }); - it("should accept all the values", () => { + it('should accept all the values', () => { filters.forEach((f) => { expect(validator.transform(f)).toBeDefined(); }); diff --git a/Backend/src/validators/filters/filters.pipe.ts b/Backend/src/validators/filters/filters.pipe.ts index 0772a12..7420d48 100644 --- a/Backend/src/validators/filters/filters.pipe.ts +++ b/Backend/src/validators/filters/filters.pipe.ts @@ -3,7 +3,7 @@ import { HttpStatus, Injectable, PipeTransform, -} from "@nestjs/common"; +} from '@nestjs/common'; @Injectable() export class FiltersValidator implements PipeTransform { @@ -20,9 +20,9 @@ export class FiltersValidator implements PipeTransform { } export const filters = [ - "intrusionDetection", - "online", - "offline", - "all", + 'intrusionDetection', + 'online', + 'offline', + 'all', ] as const; export type FiltersAvailable = (typeof filters)[number]; diff --git a/Backend/test/app.e2e-spec.ts b/Backend/test/app.e2e-spec.ts index ffddb8c..7db7b53 100644 --- a/Backend/test/app.e2e-spec.ts +++ b/Backend/test/app.e2e-spec.ts @@ -2,11 +2,11 @@ * Copyright (c) 2023. Leonardo Migliorelli */ -import { Test, TestingModule } from "@nestjs/testing"; -import { INestApplication } from "@nestjs/common"; -import { AppModule } from "../src/app.module"; +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import { AppModule } from '../src/app.module'; -describe("MachinelearningController (e2e)", () => { +describe('MachinelearningController (e2e)', () => { let app: INestApplication; beforeEach(async () => { @@ -18,7 +18,7 @@ describe("MachinelearningController (e2e)", () => { await app.init(); }); - it("/ (GET)", () => { + it('/ (GET)', () => { // return request(app.getHttpServer()) // .get('/') // .expect(200)