diff --git a/.env b/.env index 688b25d..3af7f25 100644 --- a/.env +++ b/.env @@ -3,7 +3,7 @@ MONGO_USER=adminForever MONGO_PASSWORD=Pass@2025@admin DOCKER_MONGO_URI=mongodb://mongo/ JWT_SECRET=your_jwt_secret -PORT=2000 +PORT=5002 # LOCAL_MONGO_URI=mongodb://192.168.0.110:27017/ # MONGO_USER=mydata # MONGO_PASSWORD=mongodb@hexr2002 diff --git a/package-lock.json b/package-lock.json index 7ece056..fff8a46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,17 +12,63 @@ "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", - "mongoose": "^8.13.0" + "ip": "^2.0.1", + "mongoose": "^8.13.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" }, "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^5.0.1", + "@types/ip": "^1.1.3", "@types/node": "^22.13.13", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", "nodemon": "^3.1.9", "ts-node": "^10.9.2", "typescript": "^5.8.2" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -60,6 +106,11 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, "node_modules/@mongodb-js/saslprep": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz", @@ -68,6 +119,12 @@ "sparse-bitfield": "^3.0.3" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -149,6 +206,20 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "node_modules/@types/ip": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.3.tgz", + "integrity": "sha512-64waoJgkXFTYnCYDUWgSATJ/dXEBanVkaP5d4Sbk7P6U7cTTMhxVyROTckc6JKdwCrgnAjZMn0k3177aQxtDEA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -197,6 +268,22 @@ "@types/send": "*" } }, + "node_modules/@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "dev": true + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "dev": true, + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -265,6 +352,11 @@ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "dev": true }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -273,8 +365,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -315,7 +406,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -376,6 +466,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -400,11 +495,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "engines": { + "node": ">= 6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/content-disposition": { "version": "0.5.4", @@ -490,6 +592,17 @@ "node": ">=0.3.1" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -559,6 +672,14 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -657,6 +778,11 @@ "node": ">= 0.6" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -714,6 +840,26 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -800,11 +946,26 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz", + "integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -855,6 +1016,17 @@ "node": ">=0.12.0" } }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/kareem": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", @@ -863,6 +1035,23 @@ "node": ">=12.0.0" } }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead." + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==" + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -940,7 +1129,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1171,6 +1359,20 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1179,6 +1381,14 @@ "node": ">= 0.8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -1474,6 +1684,58 @@ "node": ">=4" } }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.20.1.tgz", + "integrity": "sha512-qBPCis2w8nP4US7SvUxdJD3OwKcqiWeZmjN2VWhq2v+ESZEXOP/7n4DeiOiiZcGYTKMHAHUUrroHaTsjUWTEGw==", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1616,6 +1878,14 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/validator": { + "version": "13.15.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.0.tgz", + "integrity": "sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1644,6 +1914,19 @@ "node": ">=18" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "engines": { + "node": ">= 6" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -1652,6 +1935,34 @@ "engines": { "node": ">=6" } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } } } } diff --git a/package.json b/package.json index ad7bb11..2522680 100644 --- a/package.json +++ b/package.json @@ -14,12 +14,18 @@ "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", - "mongoose": "^8.13.0" + "ip": "^2.0.1", + "mongoose": "^8.13.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" }, "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^5.0.1", + "@types/ip": "^1.1.3", "@types/node": "^22.13.13", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", "nodemon": "^3.1.9", "ts-node": "^10.9.2", "typescript": "^5.8.2" diff --git a/src/API/app.ts b/src/API/app.ts index 053f531..f3bec6c 100644 --- a/src/API/app.ts +++ b/src/API/app.ts @@ -1,10 +1,17 @@ import express from "express"; import cors from "cors"; -import zoneRouter from "../API/routes/zoneRoutes.ts"; -import panelRouter from "../API/routes/panelRoutes.ts"; -import widgetRouter from "../API/routes/widgetRoute.ts"; +import swaggerOptions from "./swagger/swagger.ts"; +import swaggerUi from "swagger-ui-express"; +import swaggerJSDoc from "swagger-jsdoc"; +import zoneRouter from "./routes/zoneRoutes.ts"; +import panelRouter from "./routes/panelRoutes.ts"; +import widgetRouter from "./routes/widgetRoute.ts"; +import pointRouter from "./routes/pointroutes.ts"; const app = express(); +const swaggerSpec = swaggerJSDoc(swaggerOptions); +app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec)); app.use("/api/v1", zoneRouter); app.use("/api/v1", panelRouter); app.use("/api/v1", widgetRouter); +app.use("/api/v1", pointRouter); export default app; diff --git a/src/API/main.ts b/src/API/main.ts index 498cc10..668f773 100644 --- a/src/API/main.ts +++ b/src/API/main.ts @@ -1,11 +1,15 @@ import express from "express"; import cors from "cors"; +import ip from "ip"; import approutes from "./app.ts"; const app = express(); app.use(express.json()); app.use(approutes); app.use(cors()); -const port = process.env.PORT || 3000; +const port = process.env.PORT || 5004; app.listen(port, () => { console.log(`Server is running in the port ${port}`); + console.log( + `Swagger docs available at http://${ip.address()}:${port}/api-docs` + ); }); diff --git a/src/API/routes/panelRoutes.ts b/src/API/routes/panelRoutes.ts index e847dd8..aa7082d 100644 --- a/src/API/routes/panelRoutes.ts +++ b/src/API/routes/panelRoutes.ts @@ -1,6 +1,140 @@ import * as express from "express"; import { panelService } from "../service/panelService.ts"; const router = express.Router(); +/** + * @swagger + * tags: + * - name: Panels + * description: API operations related to panels + */ +/** + * @swagger + * components: + * schemas: + * Panel: + * type: object + * properties: + * _id: + * type: string + * example: "60d0fe4f5311236168a109ca" + * zoneID: + * type: string + * example: "67e3d030ed12ffa47b4eade3" + * panelName: + * type: string + * example: "New Panel" + * widgets: + * type: array + * items: + * type: string + * example: [] + * isArchive: + * type: boolean + * example: false + * required: + * - zoneID + * - panelName + * - widgets + * - isArchive + */ + +/** + * @swagger + * /panel/save: + * post: + * summary: Create new panels for a given zone + * tags: [Panels] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * organization: + * type: string + * example: "NEWORG" + * zoneID: + * type: string + * example: "67e3d030ed12ffa47b4eade3" + * panelOrder: + * type: array + * items: + * type: string + * example: ["up", "right", "left", "down"] + * responses: + * 201: + * description: Panels created successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Panels created successfully" + * panelID: + * type: array + * items: + * type: string + * example: ["60d0fe4f5311236168a109ca", "60d0fe4f5311236168a109cb"] + * 200: + * description: No new panels were created as they already exist + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "No new panels were created. All panels already exist." + * 404: + * description: Zone not found + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Zone not found" + * 500: + * description: Server error + */ router.post("/panel/save", panelService.AddPanel); + +/** + * @swagger + * /panel/delete: + * patch: + * summary: Delete the panel on the Zone + * tags: [Panels] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * organization: + * type: string + * example: "NEWORG" + * zoneID: + * type: string + * example: "0114-58-064-925" + * panelID: + * type: string + * example: "67e4f754cc778ad6c123394b" + * responses: + * 200: + * description: Panel deleted successfully + * 409: + * description: Panel Already Deleted + * 404: + * description: Zone not found + * 500: + * description: Server error + */ +router.patch("/panel/delete", panelService.deletePanel); // router.get("/zone/:sceneID", Zoneservice.allZones); export default router; diff --git a/src/API/routes/pointroutes.ts b/src/API/routes/pointroutes.ts new file mode 100644 index 0000000..e320e80 --- /dev/null +++ b/src/API/routes/pointroutes.ts @@ -0,0 +1,6 @@ +import * as express from "express"; +import { pointService } from "../service/pointservice.ts"; +const router = express.Router(); +router.post("/pointcreate/:organization", pointService.pointCreate); +router.get("/pointget/:modelfileID/:organization", pointService.GetPoints); +export default router; diff --git a/src/API/routes/widgetRoute.ts b/src/API/routes/widgetRoute.ts index 0632cfb..697a51b 100644 --- a/src/API/routes/widgetRoute.ts +++ b/src/API/routes/widgetRoute.ts @@ -1,5 +1,143 @@ import * as express from "express"; import { widgetService } from "../service/widgetService.ts"; const router = express.Router(); +/** + * @swagger + * tags: + * - name: Widgets + * description: API operations related to widgets + */ +/** + * @swagger + * components: + * schemas: + * Widget: + * type: object + * properties: + * _id: + * type: string + * example: "60d0fe4f5311236168a109cd" + * widgetName: + * type: string + * example: "Temperature Widget" + * widgetside: + * type: string + * example: "right" + * widgetID: + * type: string + * example: "wid-12345" + * widgetOrder: + * type: string + * example: "1" + * elementType: + * type: string + * example: "chart" + * elementColor: + * type: string + * example: "#FF5733" + * fontFamily: + * type: string + * example: "Arial" + * fontStyle: + * type: string + * example: "bold" + * fontWeight: + * type: string + * example: "600" + * isArchive: + * type: boolean + * example: false + * panelID: + * type: string + * example: "67e3d030ed12ffa47b4eade3" + * Data: + * type: array + * items: + * type: object + * example: [] + * required: + * - widgetName + * - widgetID + * - widgetOrder + * - elementType + * - panelID + * + */ +/** + * @swagger + * /widget/save: + * post: + * summary: Create a new widget inside a panel + * tags: [Widgets] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * organization: + * type: string + * example: "NEWORG" + * panelID: + * type: string + * example: "67e3d030ed12ffa47b4eade3" + * widgetName: + * type: string + * example: "Temperature Widget" + * widgetOrder: + * type: string + * example: "1" + * type: + * type: string + * example: "chart" + * widgetID: + * type: string + * example: "wid-12345" + * panel: + * type: string + * example: "right" + * responses: + * 201: + * description: Widget created successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Widget created successfully" + * widgetID: + * type: string + * example: "60d0fe4f5311236168a109cd" + * 404: + * description: Panel not found + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "PanelID not found" + * 409: + * description: Widget with the given widgetID already exists + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Widget already exists for the widgetID" + * 500: + * description: Server error + */ router.post("/widget/save", widgetService.addWidget); +router.patch("/widget/delete", widgetService.deleteWidget); +router.get( + "/WidgetData/:widgetID/:organization", + widgetService.getDatafromWidget +); export default router; diff --git a/src/API/routes/zoneRoutes.ts b/src/API/routes/zoneRoutes.ts index 1516e16..d2601e4 100644 --- a/src/API/routes/zoneRoutes.ts +++ b/src/API/routes/zoneRoutes.ts @@ -1,10 +1,539 @@ import * as express from "express"; import { Zoneservice } from "../service/zoneService.ts"; const router = express.Router(); +/** + * @swagger + * components: + * schemas: + * Zone: + * type: object + * required: + * - zoneName + * - zoneUUID + * - sceneID + * - createdBy + * - layer + * - viewPortCenter + * - viewPortposition + * properties: + * zoneName: + * type: string + * description: Name of the zone + * zoneUUID: + * type: string + * description: Unique identifier for the zone + * sceneID: + * type: string + * description: ID of the scene associated with the zone + * createdBy: + * type: string + * description: User ID who created the zone + * layer: + * type: integer + * description: Layer number in the scene + * zonePoints: + * type: array + * description: List of points defining the zone + * items: + * type: array + * items: + * type: object + * properties: + * x: + * type: number + * y: + * type: number + * viewPortCenter: + * type: array + * description: Center position of the viewport + * items: + * type: object + * properties: + * x: + * type: number + * y: + * type: number + * viewPortposition: + * type: array + * description: Position of the viewport + * items: + * type: object + * properties: + * x: + * type: number + * panelOrder: + * type: array + * description: Order of panels + * items: + * type: string + * enum: ["left", "right", "up", "down"] + * lockedPanel: + * type: array + * description: Locked panels in the zone + * items: + * type: string + * enum: ["left", "right", "up", "down"] + * isArchive: + * type: boolean + * description: Flag to mark if the zone is archived + * createdAt: + * type: string + * format: date-time + * description: Timestamp when the zone was created + * updatedAt: + * type: string + * format: date-time + * description: Timestamp when the zone was last updated + * example: + * zoneName: "Zone 2" + * zoneUUID: "0114-58-064-9" + * sceneID: "125789632548" + * createdBy: "012357894268" + * layer: 1 + * zonePoints: [[{"x":5,"y":5}], [{"x":5,"y":5}], [{"x":5,"y":5}]] + * viewPortCenter: [{"x":5, "y":5}] + * viewPortposition: [{"x":5}] + * panelOrder: ["left", "right"] + * lockedPanel: ["up"] + * isArchive: false + * createdAt: "2025-01-01T00:00:00Z" + * updatedAt: "2025-01-01T00:00:00Z" + */ + +/** + * @swagger + * tags: + * name: Zones + * description: API for managing zones + */ + +/** + * @swagger + * /zone/save: + * post: + * summary: Create or update a zone + * tags: [Zones] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - organization + * - zonesdata + * properties: + * organization: + * type: string + * description: Organization name + * example: "NEWORG" + * zonesdata: + * type: object + * required: + * - zonename + * - zoneId + * - sceneID + * - userid + * - layer + * - points + * - viewportPosition + * properties: + * zonename: + * type: string + * description: Name of the zone + * example: "Zone 2" + * zoneId: + * type: string + * description: Unique identifier for the zone + * example: "0114-58-064-9" + * sceneID: + * type: string + * description: ID of the scene associated with the zone + * example: "125789632548" + * userid: + * type: string + * description: User ID who created the zone + * example: "012357894268" + * layer: + * type: integer + * description: Layer number in the scene + * example: 1 + * points: + * type: array + * description: List of points defining the zone + * items: + * type: object + * properties: + * x: + * type: number + * y: + * type: number + * example: [{"x":5,"y":5}, {"x":10,"y":10}] + * viewportPosition: + * type: array + * description: Position of the viewport + * items: + * type: object + * properties: + * x: + * type: number + * y: + * type: number + * example: [{"x":5, "y":5}] + * viewPortCenter: + * type: array + * description: Center of the viewport + * items: + * type: object + * properties: + * x: + * type: number + * y: + * type: number + * example: [{"x":10, "y":10}] + * responses: + * 200: + * description: Zone created or updated successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Zone created successfully" + * zoneData: + * type: object + * properties: + * zoneName: + * type: string + * points: + * type: array + * items: + * type: object + * properties: + * x: + * type: number + * y: + * type: number + * viewPortposition: + * type: array + * items: + * type: object + * viewPortCenter: + * type: array + * items: + * type: object + * 404: + * description: Zone not updated + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Zone not updated" + * 500: + * description: Server error + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * example: "Internal server error" + */ router.post("/zone/save", Zoneservice.addandUpdateZone); //Zone create and update for the points + +/** + * @swagger + * /zones/{sceneID}: + * get: + * summary: Get all zones for a scene + * tags: [Zones] + * parameters: + * - in: path + * name: sceneID + * required: true + * schema: + * type: string + * description: ID of the scene + * - in: query + * name: organization + * required: true + * schema: + * type: string + * description: Organization name + * responses: + * 200: + * description: List of zones retrieved successfully + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * zoneName: + * type: string + * description: Name of the zone + * sceneID: + * type: string + * description: Scene ID associated with the zone + * zoneUUID: + * type: string + * description: Unique identifier for the zone + * 404: + * description: No zones found for the given scene ID + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Zone not found for the UUID" + * 500: + * description: Server error + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * example: "Internal Server Error" + * examples: + * Successful Response: + * value: + * - zoneName: "Zone A" + * sceneID: "scene123" + * zoneUUID: "UUID-12345" + * - zoneName: "Zone B" + * sceneID: "scene123" + * zoneUUID: "UUID-67890" + * Not Found Response: + * value: + * message: "Zone not found for the UUID" + */ router.get("/zones/:sceneID", Zoneservice.allZones); + +/** + * @swagger + * /ZoneVisualization/{zoneID}: + * get: + * summary: Get Zone Panel Data + * tags: [Zones] + * description: Fetches zone panel details including widgets, panel order, locked panels, and points. + * parameters: + * - in: path + * name: zoneID + * required: true + * description: The unique identifier of the zone. + * schema: + * type: string + * - in: query + * name: organization + * required: true + * description: The organization name to filter data. + * schema: + * type: string + * responses: + * 200: + * description: Zone panel data retrieved successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * zoneName: + * type: string + * activeSides: + * type: array + * items: + * type: string + * panelOrder: + * type: array + * items: + * type: string + * lockedPanels: + * type: array + * items: + * type: string + * points: + * type: array + * items: + * type: object + * properties: + * x: + * type: number + * y: + * type: number + * widgets: + * type: array + * items: + * type: object + * properties: + * id: + * type: string + * type: + * type: string + * title: + * type: string + * panel: + * type: string + * data: + * type: array + * items: + * type: object + * example: + * zoneName: "Zone 2" + * activeSides: [] + * panelOrder: [] + * lockedPanels: [] + * points: + * - x: 5 + * y: 5 + * - x: 10 + * y: 10 + * widgets: [] + * 404: + * description: Zone not found + * content: + * application/json: + * example: + * message: "Zone not found for the UUID" + * 500: + * description: Internal Server Error + * content: + * application/json: + * example: + * error: "Internal Server Error" + */ router.get("/ZoneVisualization/:zoneID", Zoneservice.singleZonePanelDatas); + +/** + * @swagger + * /A_zone/{zoneID}: + * get: + * summary: Get a specific zone's data + * tags: [Zones] + * parameters: + * - in: path + * name: zoneID + * required: true + * schema: + * type: string + * description: Unique identifier of the zone + * - in: query + * name: organization + * required: true + * schema: + * type: string + * description: Organization name + * responses: + * 200: + * description: Zone data retrieved successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * zoneUUID: + * type: string + * description: Unique identifier of the zone + * zoneName: + * type: string + * description: Name of the zone + * isArchive: + * type: boolean + * description: Indicates if the zone is archived + * 404: + * description: Zone not found + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Zone not found" + * 500: + * description: Server error + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * example: "Internal Server Error" + * examples: + * Successful Response: + * value: + * zoneUUID: "abc123" + * zoneName: "Zone A" + * isArchive: false + * otherProperties: {} + * Not Found Response: + * value: + * message: "Zone not found" + */ router.get("/A_zone/:zoneID", Zoneservice.ZoneData); + +/** + * @swagger + * /zone/{zoneID}: + * patch: + * summary: Soft delete a zone + * tags: [Zones] + * parameters: + * - in: path + * name: zoneID + * required: true + * schema: + * type: string + * description: Unique identifier of the zone + * - in: query + * name: organization + * required: true + * schema: + * type: string + * description: Organization name + * responses: + * 200: + * description: Zone successfully deleted + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Zone deleted successfully" + * 404: + * description: Zone not found + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * example: "Zone not found for the UUID" + * 500: + * description: Server error + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * example: "Internal Server Error" + */ router.patch("/zone/:zoneID", Zoneservice.deleteAZone); //delete Zone // router.get("/zone/:sceneID", Zoneservice.allZones); export default router; diff --git a/src/API/service/panelService.ts b/src/API/service/panelService.ts index 78c6928..fb14475 100644 --- a/src/API/service/panelService.ts +++ b/src/API/service/panelService.ts @@ -1,15 +1,17 @@ import { Request, Response } from "express"; import panelSchema from "../../shared/model/vizualization/panelmodel.ts"; import zoneSchema from "../../shared/model/builder/lines/zone-Model.ts"; +import widgetSchema from "../../shared/model/vizualization/widgemodel.ts"; + export class panelService { static async AddPanel(req: Request, res: Response): Promise { try { const organization = req.body.organization; - const zoneID = req.body.zoneID; + const zoneId = req.body.zoneId; const panelName = req.body.panelName; const panelOrder = req.body.panelOrder; const findZone = await zoneSchema(organization).findOne({ - _id: zoneID, + zoneId: zoneId, isArchive: false, }); @@ -17,12 +19,12 @@ export class panelService { return res.status(404).json({ message: "Zone not found" }); } const updatezone = await zoneSchema(organization).findOneAndUpdate( - { _id: findZone._id, isArchive: false }, + { zoneId: zoneId, isArchive: false }, { panelOrder: panelOrder }, { new: true } ); const existingPanels = await panelSchema(organization).find({ - zoneID: zoneID, + zoneId: zoneId, isArchive: false, }); @@ -37,7 +39,7 @@ export class panelService { const createdPanels = []; for (const panelName of missingPanels) { const newPanel = await panelSchema(organization).create({ - zoneID: zoneID, + zoneId: zoneId, panelName: panelName, widgets: [], isArchive: false, @@ -64,6 +66,43 @@ export class panelService { } static async deletePanel(req: Request, res: Response): Promise { try { + const { organization, panelID, zoneId } = req.body; + const existingZone = await zoneSchema(organization).findOne({ + zoneId: zoneId, + isArchive: false, + }); + if (!existingZone) + return res.status(404).json({ message: "Zone not found" }); + const existingPanel = await panelSchema(organization).findOne({ + zoneId: zoneId, + _id: panelID, + isArchive: false, + }); + if (!existingPanel) + return res.status(409).json({ message: "Panel Already Deleted" }); + const updatePanel = await panelSchema(organization).updateOne( + { _id: panelID, isArchive: false }, + { $set: { isArchive: true } } + ); + const existingWidgets = await widgetSchema(organization).find({ + panelID: panelID, + isArchive: false, + }); + + for (const widgetData of existingWidgets) { + widgetData.isArchive = true; + await widgetData.save(); + } + + if (existingZone.panelOrder.includes(existingPanel.panelName)) { + existingZone.panelOrder = existingZone.panelOrder.filter( + (panel: any) => panel !== existingPanel.panelName + ); + + await existingZone.save(); + } + + return res.status(200).json({ message: "Panel deleted successfully" }); } catch (error: any) { return res.status(500).send(error.message); } diff --git a/src/API/service/pointservice.ts b/src/API/service/pointservice.ts new file mode 100644 index 0000000..7d0cb89 --- /dev/null +++ b/src/API/service/pointservice.ts @@ -0,0 +1,35 @@ +import { Request, Response } from "express"; +import pointSchema from "../../shared/model/builder/assets/pointSchema.ts"; +export class pointService { + static async pointCreate(req: Request, res: Response): Promise { + const organization = req.params.organization; + const modelfileID = "6633215057b31fe671145959"; + try { + const existingData = await pointSchema(organization).findOne({ + modelfileID: modelfileID, + }); + if (existingData) return res.status(409).send("already data exists"); + const assetData = await pointSchema(organization).create({ + modelfileID: modelfileID, + }); + return res.status(200).send(assetData); + } catch (error: any) { + return res.status(500).send(error.message); + } + } + + static async GetPoints(req: Request, res: Response): Promise { + const { organization, modelfileID } = req.params; + try { + const existingPanel = await pointSchema(organization) + .findOne({ modelfileID: modelfileID }) + .select("-_id -__v -points._id"); + if (existingPanel) { + return res.status(200).json(existingPanel); + } + return res.status(404).json({ message: "No data found" }); + } catch (error: any) { + return res.status(500).send(error.message); + } + } +} diff --git a/src/API/service/widgetService.ts b/src/API/service/widgetService.ts index cb56f8a..ce946a8 100644 --- a/src/API/service/widgetService.ts +++ b/src/API/service/widgetService.ts @@ -12,6 +12,7 @@ export class widgetService { type, widgetID, panel, + Data, } = req.body; const existingPanel = await panelSchema(organization).findOne({ _id: panelID, @@ -19,35 +20,108 @@ export class widgetService { }); if (!existingPanel) return res.status(404).json({ message: "PanelID not found" }); - const existingWidget = await widgetSchema(organization).findOne({ - panelID: panelID, - widgetID: widgetID, - isArchive: false, - widgetOrder: widgetOrder, - }); - if (existingWidget) - return res - .status(409) - .json({ message: "Widget already exist for the widgetID" }); - const newWidget = await widgetSchema(organization).create({ - widgetID: widgetID, - elementType: type, - widgetOrder: widgetOrder, - widgetName: widgetName, - panelID: panelID, - widgetside: panel, - }); - if (newWidget) { - return res.status(201).json({ - message: "Widget created successfully", - widgetID: newWidget._id, + + if (existingPanel.panelName === type) { + const existingWidget = await widgetSchema(organization).findOne({ + panelID: panelID, + widgetID: widgetID, + isArchive: false, + widgetOrder: widgetOrder, }); + if (existingWidget) + return res + .status(409) + .json({ message: "Widget already exist for the widgetID" }); + const newWidget = await widgetSchema(organization).create({ + widgetID: widgetID, + elementType: type, + widgetOrder: widgetOrder, + widgetName: widgetName, + panelID: panelID, + widgetside: panel, + Data: { + measurements: Data.measurements || {}, + duration: Data.duration || "1hr", + }, + }); + if (newWidget) { + existingPanel.widgets.push(newWidget._id); + await existingPanel.save(); + return res.status(201).json({ + message: "Widget created successfully", + widgetID: newWidget._id, + }); + } } + return res.json({ message: "Type mismatch" }); + } catch (error: any) { + return res.status(500).send(error.message); + } + } + + static async deleteWidget(req: Request, res: Response): Promise { + try { + const { widgetID, organization } = req.body; + const findWidget = await widgetSchema(organization).findOne({ + _id: widgetID, + isArchive: false, + }); + if (!findWidget) + return res.status(409).json({ message: "Widget already deleted" }); + const widgetData = await widgetSchema(organization).updateOne( + { _id: widgetID, isArchive: false }, + { $set: { isArchive: true } } + ); + + if (widgetData) { + // Find all widgets in the same panel and sort them by widgetOrder + const widgets = await widgetSchema(organization) + .find({ + panelID: findWidget.panelID, + isArchive: false, + }) + .sort({ widgetOrder: 1 }); + + // Reassign widgetOrder values + for (let i = 0; i < widgets.length; i++) { + widgets[i].widgetOrder = (i + 1).toString(); // Convert to string + await widgets[i].save(); + } + const panelData = await panelSchema(organization).findOne({ + _id: findWidget.panelID, + isArchive: false, + }); + + if (panelData.widgets.includes(widgetID)) { + panelData.widgets = panelData.widgets.filter( + (widget: any) => widget !== findWidget._id + ); + } + await panelData.save(); + } + return res.status(200).json({ message: "Widget deleted successfully" }); + } catch (error: any) { + return res.status(500).send(error.message); + } + } + + static async getDatafromWidget(req: Request, res: Response): Promise { + const { organization, widgetID } = req.params; + try { + const existingWidget = await widgetSchema(organization) + .findOne({ + _id: widgetID, + isArchive: false, + }) + .select("Data -_id"); + const Datastructure = { + measurements: existingWidget.Data.measurements || {}, + duration: existingWidget.Data.duration || "1hr", + }; + + if (existingWidget) return res.status(200).json({ Data: Datastructure }); } catch (error: any) { return res.status(500).send(error.message); } } - // static async deleteWidget(req: Request, res: Response):Promise{ - // const findWidget = await widgetSchema(req.body.organization).findOne({_id:req.body.widget}) - // } } diff --git a/src/API/service/zoneService.ts b/src/API/service/zoneService.ts index 84fafde..f80f760 100644 --- a/src/API/service/zoneService.ts +++ b/src/API/service/zoneService.ts @@ -9,15 +9,16 @@ export class Zoneservice { try { const existingZone = await zoneSchema(organization).findOne({ - zoneUUID: zoneDatas.zoneId, + zoneId: zoneDatas.zoneId, isArchive: false, }); if (!existingZone) { const newZone = await zoneSchema(organization).create({ zoneName: zoneDatas.zonename, - zoneUUID: zoneDatas.zoneId, + zoneId: zoneDatas.zoneId, zonePoints: zoneDatas.points, - centerPoints: zoneDatas.viewportPosition, + viewPortposition: zoneDatas.viewportPosition, + viewPortCenter: zoneDatas.viewPortCenter, createdBy: zoneDatas.userid, layer: zoneDatas.layer, sceneID: zoneDatas.sceneID, @@ -28,15 +29,17 @@ export class Zoneservice { zoneData: { zoneName: newZone.zoneName, points: newZone.zonePoints, - centerPoints: newZone.centerPoints, + viewPortposition: zoneDatas.viewPortposition, + viewPortCenter: zoneDatas.viewPortCenter, }, }); } else { const replaceZone = await zoneSchema(organization).findOneAndUpdate( - { zoneUUID: zoneDatas.zoneId, isArchive: false }, + { zoneId: zoneDatas.zoneId, isArchive: false }, { zonePoints: zoneDatas.points, - centerPoints: zoneDatas.viewportPosition, + viewPortposition: zoneDatas.viewPortposition, + viewPortCenter: zoneDatas.viewPortCenter, }, { new: true } ); @@ -48,7 +51,8 @@ export class Zoneservice { zoneData: { zoneName: replaceZone.zoneName, points: replaceZone.zonePoints, - centerPoints: replaceZone.centerPoints, + viewPortposition: replaceZone.viewPortposition, + viewPortCenter: replaceZone.viewPortCenter, }, }); } @@ -59,17 +63,17 @@ export class Zoneservice { static async deleteAZone(req: Request, res: Response): Promise { const organization = req.query.organization; - const zoneUUID = req.params.zoneId; + const zoneId = req.params.zoneId; try { const existingZone = await zoneSchema(organization).findOne({ - zoneUUID: zoneUUID, + zoneId: zoneId, isArchive: false, }); if (!existingZone) { return res.status(404).json({ message: "Zone not found for the UUID" }); } else { const deleteZone = await zoneSchema(organization).findOneAndUpdate( - { zoneUUID: zoneUUID, isArchive: false }, + { zoneId: zoneId, isArchive: false }, { isArchive: true, }, @@ -87,19 +91,21 @@ export class Zoneservice { static async singleZonePanelDatas(req: Request, res: Response): Promise { const organization = req.query.organization; - const zoneID = req.params.zoneID; + const zoneId = req.params.zoneId; try { const existingZone = await zoneSchema(organization) .findOne({ - _id: req.params.zoneID, + zoneId: req.params.zoneId, + isArchive: false, }) .select("panelOrder zoneName zonePoints lockedPanel"); if (!existingZone) { return res.send({ message: "Zone not found for the UUID" }); } else { const panelData = await panelSchema(organization).find({ - zoneID: zoneID, + zoneId: zoneId, + isArchive: false, }); const zoneName = existingZone.zoneName as string; @@ -107,6 +113,7 @@ export class Zoneservice { panelData.map(async (data) => { const widgetDataArray = await widgetSchema(organization).find({ panelID: data._id, + isArchive: false, }); return widgetDataArray.map((widgetData) => ({ @@ -130,7 +137,7 @@ export class Zoneservice { widgets: flattenedWidgets, }; - return res.send(objectData); + return res.status(200).json(objectData); } } catch (error: any) { return res.status(500).send(error.message); @@ -143,10 +150,10 @@ export class Zoneservice { try { const Allzones = await zoneSchema(organization) .find({ sceneID: sceneID, isArchive: false }) - .select("zoneName sceneID zoneUUID"); + .select("zoneName sceneID zoneId"); if (!Allzones || Allzones.length === 0) { - return res.status(200).json({ message: "Zone not found for the UUID" }); + return res.status(404).json({ message: "Zone not found for the UUID" }); } return res.status(200).json(Allzones); } catch (error: any) { @@ -158,25 +165,25 @@ export class Zoneservice { try { const organization = req.query.organization; console.log("organization: ", organization); - const zoneID = req.params.zoneID; - console.log("zoneID: ", zoneID); - const findZone = await zoneSchema(organization) - .findOne({ _id: zoneID }) - // .select("zoneName"); - console.log("findZone: ", findZone); + const zoneId = req.params.zoneId; + console.log("zoneId: ", zoneId); + const findZone = await zoneSchema(organization).findOne({ + zoneId: zoneId, + }); + // .select("zoneName"); if (findZone) return res.status(200).json(findZone); } catch (error: any) { return res.status(500).send(error.message); } } - // static async ZoneIDgenerate(req: Request, res: Response): Promise { + // static async zoneIdgenerate(req: Request, res: Response): Promise { // const organization = req.query.organization; // const sceneID = req.params.sceneID; // try { // const Allzones = await zoneSchema(organization) // .find({ sceneID: sceneID, isArchive: false }) - // .select("zoneName sceneID zoneUUID"); + // .select("zoneName sceneID zoneId"); // if (!Allzones || Allzones.length === 0) { // return res.send({ message: "Zone not found for the UUID" }); diff --git a/src/API/swagger/swagger.ts b/src/API/swagger/swagger.ts new file mode 100644 index 0000000..6154296 --- /dev/null +++ b/src/API/swagger/swagger.ts @@ -0,0 +1,43 @@ +import swaggerJSDoc from "swagger-jsdoc"; +import ip from "ip"; +import dotenv from "dotenv"; +dotenv.config(); +const swaggerPort = process.env.PORT; +const swaggerOptions: swaggerJSDoc.Options = { + definition: { + openapi: "3.0.0", + info: { + title: "Dwinzo-Beta API", + version: "1.0.0", + description: "API documentation for BackendIOT", + contact: { + name: "Backend Developer", + email: "nivetha@hexrfactory.com", + }, + }, + servers: [ + { + url: `http://192.168.0.102:${swaggerPort}/api/v1`, + description: "Server Endpoint", + }, + ], + components: { + securitySchemes: { + bearerAuth: { + type: "http", + scheme: "bearer", + bearerFormat: "JWT", + }, + }, + }, + security: [ + { + bearerAuth: [], + }, + ], + }, + apis: ["./src/API/routes/*.ts"], // Adjust path based on where your routes are located + tryItOutEnabled: false, +}; + +export default swaggerOptions; diff --git a/src/shared/model/builder/assets/asset-Model.ts b/src/shared/model/builder/assets/asset-Model.ts new file mode 100644 index 0000000..950461b --- /dev/null +++ b/src/shared/model/builder/assets/asset-Model.ts @@ -0,0 +1,62 @@ +import mongoose, { Document, Schema } from "mongoose"; +import MainModel from "../../../connect/mongoose.ts"; + +// Interface for TypeScript with PascalCase +export interface assetData extends Document { + modeluuid: string; + modelfileID: string; + modelname: string; + isLocked: boolean; + isVisible: boolean; + position: []; + rotation: { + x: number; + y: number; + z: number; + }; + points: [ + { + uuid: string; + position: []; + rotation: []; + actions: [mongoose.Types.ObjectId]; + triggers: [mongoose.Types.ObjectId]; + // connections:{ + // source:{ + // pathuuid:string,pointuuid:string + // }, + // target:[] + // } + } + ]; +} + +// Define the Mongoose Schema +const assetDataSchema: Schema = new Schema({ + modeluuid: { type: String }, + modelfileID: { type: String }, + modelname: { type: String }, + position: { type: Array }, + points: [ + { + uuid: { type: String }, + position: { type: Array }, + rotation: { type: Array }, + actions: [{ type: mongoose.Schema.Types.ObjectId, ref: "Widget" }], + triggers: [{ type: mongoose.Schema.Types.ObjectId, ref: "Widget" }], + }, + ], + isLocked: { type: Boolean }, + isVisible: { type: Boolean }, + rotation: { + x: { type: Number, required: true }, + y: { type: Number, required: true }, + z: { type: Number, required: true }, + }, +}); + +// export default floorItemsModel; +const assetModel = (db: string) => { + return MainModel(db, "Assets", assetDataSchema, "Assets"); +}; +export default assetModel; diff --git a/src/shared/model/builder/assets/flooritems-Model.ts b/src/shared/model/builder/assets/flooritems-Model.ts deleted file mode 100644 index 8185d53..0000000 --- a/src/shared/model/builder/assets/flooritems-Model.ts +++ /dev/null @@ -1,38 +0,0 @@ -import mongoose, { Document, Schema } from "mongoose"; -import MainModel from "../../../connect/mongoose.ts"; - -// Interface for TypeScript with PascalCase -export interface floorItenms extends Document { - modeluuid: string; - modelfileID: string; - modelname: string; - isLocked: boolean; - isVisible: boolean; - position: []; - rotation: { - x: number; - y: number; - z: number; - }; -} - -// Define the Mongoose Schema -const floorItemsSchema: Schema = new Schema({ - modeluuid: { type: String }, - modelfileID: { type: String }, - modelname: { type: String }, - position: { type: Array }, - isLocked: { type: Boolean }, - isVisible: { type: Boolean }, - rotation: { - x: { type: Number, required: true }, - y: { type: Number, required: true }, - z: { type: Number, required: true }, - }, -}); - -// export default floorItemsModel; -const floorItemsModel = (db: string) => { - return MainModel(db, "floorItems", floorItemsSchema, "floorItems"); -}; -export default floorItemsModel; diff --git a/src/shared/model/builder/assets/pointSchema.ts b/src/shared/model/builder/assets/pointSchema.ts new file mode 100644 index 0000000..91e24c6 --- /dev/null +++ b/src/shared/model/builder/assets/pointSchema.ts @@ -0,0 +1,59 @@ +import mongoose, { Document, Schema } from "mongoose"; +import MainModel from "../../../connect/mongoose.ts"; + +// Interface for TypeScript with PascalCase +export interface Points extends Document { + modelfileID: string; + points: { + uuid: string; + position: number[]; + rotation: number[]; + actions: string[]; + triggers: string[]; + }[]; +} +const defaultPoints = [ + { + uuid: "", + position: [0, 1.25, 3.3], + rotation: [0, 0, 0], + actions: [], + triggers: [], + }, + { + uuid: "", + position: [0, 1.25, 3.3], + rotation: [0, 0, 0], + actions: [], + triggers: [], + }, + { + uuid: "", + position: [0, 1.25, 3.3], + rotation: [0, 0, 0], + actions: [], + triggers: [], + }, +]; +// Define the Mongoose Schema +const pointSchema: Schema = new Schema({ + modelfileID: { type: String, required: true }, + points: { + type: [ + { + uuid: { type: String }, + position: { type: [Number], default: [0, 1.25, 3.3] }, + rotation: { type: [Number], default: [0, 0, 0] }, + actions: { type: [String], default: [] }, + triggers: { type: [String], default: [] }, + }, + ], + default: defaultPoints, + }, +}); + +const pointModel = (db: string) => { + return MainModel(db, "Points", pointSchema, "Points"); +}; + +export default pointModel; diff --git a/src/shared/model/builder/lines/zone-Model.ts b/src/shared/model/builder/lines/zone-Model.ts index eb24763..a1e6e12 100644 --- a/src/shared/model/builder/lines/zone-Model.ts +++ b/src/shared/model/builder/lines/zone-Model.ts @@ -39,7 +39,7 @@ import MainModel from "../../../connect/mongoose.ts"; export interface Zone extends Document { zoneName: string; - // zoneUUID: string; + zoneId: string; zonePoints: []; viewPortCenter: []; viewPortposition: []; @@ -55,12 +55,11 @@ export interface Zone extends Document { const zoneSchema: Schema = new Schema( { zoneName: { type: String }, - // zoneUUID: { type: String }, + zoneId: { type: String }, createdBy: { type: String }, sceneID: { type: String }, layer: { type: Number }, - centerPoints: { type: Array }, - zonePoints: { type: Array }, + points: { type: Array }, isArchive: { type: Boolean, default: false }, panelOrder: { type: [String], @@ -79,7 +78,7 @@ const zoneSchema: Schema = new Schema( { timestamps: true } ); -const dataModel = (db: any) => { +const zoneModel = (db: any) => { return MainModel(db, "zones", zoneSchema, "zones"); }; -export default dataModel; +export default zoneModel; diff --git a/src/shared/model/simulation/actionmodel.ts b/src/shared/model/simulation/actionmodel.ts new file mode 100644 index 0000000..67197ff --- /dev/null +++ b/src/shared/model/simulation/actionmodel.ts @@ -0,0 +1,39 @@ +import mongoose, { Schema, Document, model } from "mongoose"; +import MainModel from "../../connect/mongoose.ts"; + +export interface Event extends Document { + pointsUUID: string; + actionUUID: string; + isArchive: string; + sceneID: string; + eventData: { + uuid: string; + type: string; + material: string; + delay: number; + spawn_Interval: number; + }; +} +const eventSchema: Schema = new Schema( + { + pointsUUID: { type: String }, + isArchive: { type: Boolean, default: false }, + actionUUID: { type: String }, + sceneID: { type: String }, + eventData: [ + { + uuid: { type: String }, + type: { type: String }, + material: { type: String }, + delay: { type: Number }, + spawn_Interval: { type: Number }, + }, + ], + }, + { timestamps: true } +); + +const actionModel = (db: any) => { + return MainModel(db, "Events", eventSchema, "Events"); +}; +export default actionModel; diff --git a/src/shared/model/simulation/eventmodel.ts b/src/shared/model/simulation/eventmodel.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/shared/model/simulation/triggersmodel.ts b/src/shared/model/simulation/triggersmodel.ts index e69de29..49cd3c8 100644 --- a/src/shared/model/simulation/triggersmodel.ts +++ b/src/shared/model/simulation/triggersmodel.ts @@ -0,0 +1,39 @@ +import mongoose, { Schema, Document, model } from "mongoose"; +import MainModel from "../../connect/mongoose.ts"; + +export interface Trigger extends Document { + pointsUUID: string; + actionUUID: string; + isArchive: string; + sceneID: string; + triggerData: { + uuid: string; + type: string; + // material: string; + // delay: number; + // spawn_Interval: number; + }; +} +const triggerSchema: Schema = new Schema( + { + pointsUUID: { type: String }, + isArchive: { type: Boolean, default: false }, + actionUUID: { type: String }, + sceneID: { type: String }, + triggerData: [ + { + uuid: { type: String }, + type: { type: String }, + // material: { type: String }, + // delay: { type: Number }, + // spawn_Interval: { type: Number }, + }, + ], + }, + { timestamps: true } +); + +const triggerModel = (db: any) => { + return MainModel(db, "Triggers", triggerSchema, "Triggers"); +}; +export default triggerModel; diff --git a/src/shared/model/vizualization/panelmodel.ts b/src/shared/model/vizualization/panelmodel.ts index 38ad66c..b2dd773 100644 --- a/src/shared/model/vizualization/panelmodel.ts +++ b/src/shared/model/vizualization/panelmodel.ts @@ -2,14 +2,16 @@ import mongoose, { Schema, Document, model } from "mongoose"; import MainModel from "../../connect/mongoose.ts"; export interface Panel extends Document { - zoneID: mongoose.Types.ObjectId; + // zoneID: mongoose.Types.ObjectId; + zoneId: string; panelName: string; widgets: [mongoose.Types.ObjectId]; isArchive: boolean; } const panelSchema: Schema = new Schema( { - zoneID: { type: mongoose.Schema.Types.ObjectId, ref: "Zone" }, + // zoneID: { type: mongoose.Schema.Types.ObjectId, ref: "Zone" }, + zoneId: { type: String }, panelName: { type: String }, widgets: [{ type: mongoose.Schema.Types.ObjectId, ref: "Widget" }], isArchive: { type: Boolean, default: false }, @@ -17,7 +19,7 @@ const panelSchema: Schema = new Schema( { timestamps: true } ); -const dataModel = (db: any) => { +const panelModel = (db: any) => { return MainModel(db, "Panel", panelSchema, "Panel"); }; -export default dataModel; +export default panelModel; diff --git a/src/shared/model/vizualization/templatemodel.ts b/src/shared/model/vizualization/templatemodel.ts index cae9b70..3ac42d8 100644 --- a/src/shared/model/vizualization/templatemodel.ts +++ b/src/shared/model/vizualization/templatemodel.ts @@ -2,17 +2,18 @@ import mongoose, { Schema, Document, model } from "mongoose"; import MainModel from "../../connect/mongoose.ts"; export interface Template extends Document { - // templateName:; + templateName: string; isArchive: boolean; } const templateSchema: Schema = new Schema( { + templateName: { type: String }, isArchive: { type: Boolean, default: false }, }, { timestamps: true } ); -const dataModel = (db: any) => { - return MainModel(db, "template", templateSchema, "template"); +const templateModel = (db: any) => { + return MainModel(db, "Template", templateSchema, "Template"); }; -export default dataModel; +export default templateModel; diff --git a/src/shared/model/vizualization/widgemodel.ts b/src/shared/model/vizualization/widgemodel.ts index 6d844f2..e0ba874 100644 --- a/src/shared/model/vizualization/widgemodel.ts +++ b/src/shared/model/vizualization/widgemodel.ts @@ -13,7 +13,10 @@ export interface widget extends Document { fontWeight: string; isArchive: boolean; panelID: mongoose.Types.ObjectId; - Data: []; + Data: { + measurement: Record; + duration: string; + }; } const widgetSchema: Schema = new Schema( { @@ -25,7 +28,13 @@ const widgetSchema: Schema = new Schema( elementColor: { type: String }, fontFamily: { type: String }, fontStyle: { type: String }, - Data: { type: Array }, + Data: { + measurements: { + type: Object, + default: {}, + }, + duration: { type: String, default: "1hr" }, + }, fontWeight: { type: String }, isArchive: { type: Boolean, default: false }, panelID: { type: mongoose.Schema.Types.ObjectId, ref: "Panel" }, @@ -33,7 +42,7 @@ const widgetSchema: Schema = new Schema( { timestamps: true } ); -const dataModel = (db: any) => { +const widgetModel = (db: any) => { return MainModel(db, "Widget", widgetSchema, "Widget"); }; -export default dataModel; +export default widgetModel;