From ba541817b84de0481a4169d929f2860e5c03c463 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Wed, 29 Dec 2021 10:14:02 +0530 Subject: [PATCH 001/128] added feathersjs project --- Goobieverse/.editorconfig | 13 ++ Goobieverse/.env.local.default | 31 +++++ Goobieverse/.eslintrc.json | 38 ++++++ Goobieverse/.gitignore | 115 +++++++++++++++++ Goobieverse/README.md | 45 +++++++ Goobieverse/build_scripts/createVersion.js | 32 +++++ Goobieverse/jest.config.js | 9 ++ Goobieverse/package.json | 85 +++++++++++++ Goobieverse/public/favicon.ico | Bin 0 -> 5533 bytes Goobieverse/public/index.html | 75 +++++++++++ Goobieverse/src/app.hooks.ts | 34 +++++ Goobieverse/src/app.ts | 74 +++++++++++ Goobieverse/src/appconfig.ts | 113 +++++++++++++++++ Goobieverse/src/authentication.ts | 23 ++++ Goobieverse/src/channels.ts | 65 ++++++++++ .../src/controllers/PublicRoutesController.ts | 31 +++++ Goobieverse/src/declarations.d.ts | 6 + Goobieverse/src/index.ts | 13 ++ Goobieverse/src/interfaces/MetaverseInfo.ts | 8 ++ Goobieverse/src/logger.ts | 16 +++ Goobieverse/src/middleware/index.ts | 6 + Goobieverse/src/mongodb.ts | 20 +++ Goobieverse/src/routes/publicRoutes.ts | 8 ++ Goobieverse/src/services/index.ts | 7 ++ Goobieverse/src/utils/Misc.ts | 118 ++++++++++++++++++ Goobieverse/test/app.test.ts | 69 ++++++++++ Goobieverse/test/authentication.test.ts | 32 +++++ .../test/services/metaverse_info.test.ts | 8 ++ Goobieverse/tsconfig.json | 13 ++ 29 files changed, 1107 insertions(+) create mode 100644 Goobieverse/.editorconfig create mode 100644 Goobieverse/.env.local.default create mode 100644 Goobieverse/.eslintrc.json create mode 100644 Goobieverse/.gitignore create mode 100644 Goobieverse/README.md create mode 100644 Goobieverse/build_scripts/createVersion.js create mode 100644 Goobieverse/jest.config.js create mode 100644 Goobieverse/package.json create mode 100644 Goobieverse/public/favicon.ico create mode 100644 Goobieverse/public/index.html create mode 100644 Goobieverse/src/app.hooks.ts create mode 100644 Goobieverse/src/app.ts create mode 100644 Goobieverse/src/appconfig.ts create mode 100644 Goobieverse/src/authentication.ts create mode 100644 Goobieverse/src/channels.ts create mode 100644 Goobieverse/src/controllers/PublicRoutesController.ts create mode 100644 Goobieverse/src/declarations.d.ts create mode 100644 Goobieverse/src/index.ts create mode 100644 Goobieverse/src/interfaces/MetaverseInfo.ts create mode 100644 Goobieverse/src/logger.ts create mode 100644 Goobieverse/src/middleware/index.ts create mode 100644 Goobieverse/src/mongodb.ts create mode 100644 Goobieverse/src/routes/publicRoutes.ts create mode 100644 Goobieverse/src/services/index.ts create mode 100644 Goobieverse/src/utils/Misc.ts create mode 100644 Goobieverse/test/app.test.ts create mode 100644 Goobieverse/test/authentication.test.ts create mode 100644 Goobieverse/test/services/metaverse_info.test.ts create mode 100644 Goobieverse/tsconfig.json diff --git a/Goobieverse/.editorconfig b/Goobieverse/.editorconfig new file mode 100644 index 00000000..e717f5eb --- /dev/null +++ b/Goobieverse/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/Goobieverse/.env.local.default b/Goobieverse/.env.local.default new file mode 100644 index 00000000..0acb07ea --- /dev/null +++ b/Goobieverse/.env.local.default @@ -0,0 +1,31 @@ + +# DB variables ------------------- +DB_HOST=localhost +DB_PORT=27017 +DB_NAME=tester +DB_USER=metaverse +DB_PASSWORD=nooneknowsit +DB_AUTHDB=admin +DATABASE_URL= + +# Authentication variables ------------------- +AUTH_SECRET=M7iszvSxttilkptn22GT4/NbFGY= + +# Server variables --------------- +SERVER_HOST=localhost +SERVER_PORT=3030 +SERVER_VERSION=1.1.1-20200101-abcdefg + +# General ------------------------ +LOCAL=true +APP_ENV=development +PUBLIC_PATH=./public + +# Metaverse ------------------------ +METAVERSE_NAME=Vircadia noobie +METAVERSE_NICK_NAME=Noobie +METAVERSE_SERVER_URL= +DEFAULT_ICE_SERVER_URL= +DASHBOARD_URL=https://dashboard.vircadia.com +LISTEN_HOST=0.0.0.0 +LISTEN_PORT=9400 \ No newline at end of file diff --git a/Goobieverse/.eslintrc.json b/Goobieverse/.eslintrc.json new file mode 100644 index 00000000..d0fbe201 --- /dev/null +++ b/Goobieverse/.eslintrc.json @@ -0,0 +1,38 @@ +{ + "env": { + "es6": true, + "node": true, + "jest": true + }, + "parserOptions": { + "parser": "@typescript-eslint/parser", + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "indent": [ + "error", + 2 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-interface": "off" + } +} diff --git a/Goobieverse/.gitignore b/Goobieverse/.gitignore new file mode 100644 index 00000000..25d7c18a --- /dev/null +++ b/Goobieverse/.gitignore @@ -0,0 +1,115 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# Commenting this out is preferred by some people, see +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules + +# Users Environment Variables +.lock-wscript + +# IDEs and editors (shamelessly copied from @angular/cli's .gitignore) +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Others +lib/ +data/ + +.env.local +package-lock.json \ No newline at end of file diff --git a/Goobieverse/README.md b/Goobieverse/README.md new file mode 100644 index 00000000..6a184037 --- /dev/null +++ b/Goobieverse/README.md @@ -0,0 +1,45 @@ +# Goobieverse + +> + +## About + +This project uses [Feathers](http://feathersjs.com). An open source web framework for building modern real-time applications. + +## Getting Started + +Getting up and running is as easy as 1, 2, 3. + +1. Make sure you have [NodeJS](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed. +2. Install your dependencies + + ``` + cd path/to/Goobieverse + npm install + ``` + +3. Start your app + + ``` + npm start + ``` + +## Testing + +Simply run `npm test` and all your tests in the `test/` directory will be run. + +## Scaffolding + +Feathers has a powerful command line interface. Here are a few things it can do: + +``` +$ npm install -g @feathersjs/cli # Install Feathers CLI + +$ feathers generate service # Generate a new Service +$ feathers generate hook # Generate a new Hook +$ feathers help # Show all commands +``` + +## Help + +For more information on all the things you can do with Feathers visit [docs.feathersjs.com](http://docs.feathersjs.com). diff --git a/Goobieverse/build_scripts/createVersion.js b/Goobieverse/build_scripts/createVersion.js new file mode 100644 index 00000000..7943714a --- /dev/null +++ b/Goobieverse/build_scripts/createVersion.js @@ -0,0 +1,32 @@ + +const fse = require('fs-extra'); + +var gitVer = require('child_process').execSync('git rev-parse --short HEAD').toString().trim(); +var gitVerFull = require('child_process').execSync('git rev-parse HEAD').toString().trim(); +var packageVersion = process.env.npm_package_version; +const filePath = './lib/metaverse_info.json'; + +console.log('Found package version', packageVersion); +console.log('Found Git commit short hash', gitVer); +console.log('Found Git commit long hash', gitVerFull); + +function yyyymmdd() { + var x = new Date(); + var y = x.getFullYear().toString(); + var m = (x.getMonth() + 1).toString(); + var d = x.getDate().toString(); + (d.length == 1) && (d = '0' + d); + (m.length == 1) && (m = '0' + m); + var yyyymmdd = y + m + d; + return yyyymmdd; +} + +var jsonToWrite = { + npm_package_version: packageVersion, + git_commit: gitVerFull, + version_tag: (packageVersion + '-' + yyyymmdd() + '-' + gitVer) +}; + +jsonToWrite = JSON.stringify(jsonToWrite); + +var attemptFileWrite = fse.outputFileSync(filePath, jsonToWrite); diff --git a/Goobieverse/jest.config.js b/Goobieverse/jest.config.js new file mode 100644 index 00000000..57070898 --- /dev/null +++ b/Goobieverse/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + globals: { + 'ts-jest': { + diagnostics: false + } + } +}; diff --git a/Goobieverse/package.json b/Goobieverse/package.json new file mode 100644 index 00000000..6cb56b3c --- /dev/null +++ b/Goobieverse/package.json @@ -0,0 +1,85 @@ +{ + "name": "goobie-verse", + "description": "Goobieverse", + "version": "0.0.1", + "homepage": "", + "private": true, + "main": "src", + "keywords": [ + "feathers" + ], + "author": { + "name": "", + "email": "" + }, + "contributors": [], + "bugs": {}, + "directories": { + "lib": "src", + "test": "test/" + }, + "engines": { + "node": "^16.0.0", + "npm": ">= 3.0.0" + }, + "scripts": { + "test": "npm run lint && npm run compile && npm run jest", + "lint": "eslint src/. test/. --config .eslintrc.json --ext .ts --fix", + "dev": "cross-env APP_ENV=development ts-node-dev --no-notify src/", + "start": "cross-env APP_ENV=prod npm run compile && node lib/", + "jest": "jest --forceExit", + "compile": "shx rm -rf lib/ && tsc", + "create-version": "node build_scripts/createVersion.js" + }, + "standard": { + "env": [ + "jest" + ], + "ignore": [] + }, + "types": "lib/", + "dependencies": { + "@feathersjs/authentication": "^4.5.11", + "@feathersjs/authentication-local": "^4.5.11", + "@feathersjs/authentication-oauth": "^4.5.11", + "@feathersjs/configuration": "^4.5.11", + "@feathersjs/errors": "^4.5.11", + "@feathersjs/express": "^4.5.11", + "@feathersjs/feathers": "^4.5.11", + "@feathersjs/socketio": "^4.5.11", + "@feathersjs/transport-commons": "^4.5.11", + "@types/dotenv-flow": "^3.2.0", + "app-root-path": "^3.0.0", + "compression": "^1.7.4", + "cors": "^2.8.5", + "cross-env": "^7.0.3", + "dotenv-flow": "^3.2.0", + "feathers-hooks-common": "^5.0.6", + "feathers-mongodb": "^6.4.1", + "helmet": "^4.6.0", + "mongodb": "^4.2.2", + "mongodb-core": "^3.2.7", + "serve-favicon": "^2.5.0", + "winston": "^3.3.3" + }, + "devDependencies": { + "@types/app-root-path": "^1.2.4", + "@types/compression": "^1.7.2", + "@types/cors": "^2.8.12", + "@types/dotenv-flow": "^3.2.0", + "@types/jest": "^27.0.3", + "@types/jsonwebtoken": "^8.5.6", + "@types/mongodb": "^4.0.7", + "@types/morgan": "^1.9.3", + "@types/serve-favicon": "^2.5.3", + "@typescript-eslint/eslint-plugin": "^5.8.0", + "@typescript-eslint/parser": "^5.8.0", + "axios": "^0.24.0", + "eslint": "^8.5.0", + "jest": "^27.4.5", + "shx": "^0.3.3", + "ts-jest": "^27.0.7", + "ts-node-dev": "^1.1.8", + "typescript": "^4.5.4" + } +} diff --git a/Goobieverse/public/favicon.ico b/Goobieverse/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7ed25a60b04996fb9f339aa73cbfd25dd394430e GIT binary patch literal 5533 zcmV;O6=Ld%P)Px~S4l)cRCodHoe7jx#hJ(Hg+-u2K|lyPOhlU~Ad91h!~|sw6K7&#jHqLXlMs&@ zjTsn|=r~43qfwJlaf?TC3_?Hq;WiQWT+*&1{MNE%%XS?p)vT&iphJfaJ%xrn7ktC81Mt`5cFPwp0y#w9 z4{;jFy9)a{{`rd+FJ7N-S#hMV7=ftwQ{k^*C4_UZ$6?E9Rw0;qF!=qB9XsZ}^UgcX z+0(Mfgbz~>=yyuHdo-KOow2$QQlKbe^Zw%S@K#b%g(o~L9foKv(r^!H3($_IC_O(`Au_k^YzP@ zFaIDUu4qy@1X9CPa4U^|GnGo4pP{Y9-pFW}1M)*fqc39tef#!3s#mXGSHj{}Y})>Z zI%rK9M~8Z*)DTK45CqX1fHSZoEl?`33wt|L*S)OK`)ypA{Z(N4GCVFvBb&H%>CzSX z@=L8V_mJieEAD8QByGGzR5cHHP)6jg_Il2Qxbuo&R&ineo5h0$KMOs@6ia4 z1(}vPT#GR7g7}$ad}8^*-=l6R5)m`28Jq)!JB;DOw;1V>}4{ zgUZ{ow22#23V`|4F4bVO$fIe>nLT)pQh?3g05*HtSQcfd^BAf-nL@glg0?eKE?|9p z#nAHh+8&g5X7xmX-D`z`Af91fJw0w=2z-|=U8dkzVeBTn1M3+!g`J(h7?-*|MgC#b zb)_k%D{JI$X%n5t6>JI?2y4cjaQ$I`^P_;hm_<&<9kCjE?NxM)O9M=o6Yk|`^xzK8 zU#yFA^6u{4yU#`_3r$%ne>QESbGd>{K?0!{XtQ^d0mhNF+>K)O-|TUa+QrvvCf)R}JZ|1E9S!x3_Ea~6mS zhQX5*>Z`T@m`o43B^O5JNORh0r)iIoHg%@>JOMVlxO-$eQ@I4vMw`Q3yLNpAMvGDy z+caz+R|FM)j*+1?BPmnOS()}YX{X2FYQ}B?>(N1AjF7UP z);2aaHlbQ;L0|27gJU$)>WwbknwyhAm9p++4=7i5MO>TOLRRcxkyPELPX5F;@REiEP@ z*S~kbD&u~I&hjewhDX6*;q^C(QE5s=r^7w~o@Y&YC(xEsK(d;w0%4eao7`ub5`HH} zCLFt$L%tzY^qXOf5yqp?|1m6%Jk6y%l?~$&q_LiigYRZX2j6&3CYTsR4r!7&mXqat`ur?Qts#EJY-mbkJ-F{4bS@CAm!NM@d| znsAyoR3JR5IgW~Jx^iaq#*G_wc9p21!Eeg~U^=5DWACAngPp|awpwDAVoDyR(}C|` z{g*Se?Y~8F2^9#7=D(1^KZ;-~n7D4;x;;^a#6f57)dg6T>bMAuy@+>v54{w50orZQ zqyThhL47$6oMAi|B@N*Dm5Ce5Xg}YCeQ&MrOL-U_nx?Kc^wnrpx|U7UFgkSa5@n_m z|1}fY1wnjKRy17Q7_K$(v3uE+{)COJUTAuQ0K7K?BI5;1#t30_e3wj}9bzhSH-3M( zIeMHfd!duT5!$1mYf5+)%wK8+)5=!j%Nm_pe`h-;;2puJ|C|duHz*LDx%et2bzRRx zQSz-ImNbD#VgUaumk5FUBCc12 zj%SEl1CC2W=#Dg-cyDTQgfN#he+r|=N#pEq88}{bJn>(|5l}}LmYXIJgrV~(-w>YW z2NLqoNu=rqU$XdU_U+sEYvOYe$U;9;f*!C=b?otXMkr}2urClj3?qTRFp}_fB>Ila zoRkZuG{6BI4Xk<6s4@;21L;0MF0QLjoO zkK2`xdVkFN(GXEaB4qGvMhMFdbAK>+0@08mfv@K?T~&lY(6BB8Th!_3OLQcCsoTj^ zEW=B@j!X__L?le0qu4eclo}L=)m}CzLo7Z7qNyNh1FY%?0sbyFL)+u9@KuC$Mr$C+ z`+RF@5{mzWd)I({q znFFNx&uj^jilfu7u)L|KCj(C)RL~a)6=<{xlk(GJ&J%AnI#vs}J&0SR+dt^pRAQ|k z3s5r?xd&;j-8kENk2LmWr7w_bDt59dpdthUCSMiAe{Blio|L>y4Q2oE>gU#W5gAt_NdB*jDOHJBCd<&TP zUdrJKb8TC@L7|1SyG=}@bvB%YpXW0D0O(%h!PD>AY|B5YfOR~@jb7Gjz} z5YWz~@v=idB@2dSskop(^ixc8v9Iy`X8sW{I;3>yr=+~<;8rs;bgYF~=ZK1-cP2hU zz@!ads@>Ba;+Chbng3cbgmtX0mx9f>=iy$-h1O{NA7CrmfHkeHQHAR?Lel1HdRO8j z1Wb|l9|&MYyLRn7?F#=c(rTo5>k0mf7C;ajv(YXFfKTQsp|ez8`zW{#m5n;g^D~y) zLqoTZ;sIW5sJtl+zm82ZG_6#cs3Izf?tj`LP?AM8l}{TsNG)x|B!oWlAvfP(tY~G? zU&o@qJ|WzBu|)uB>o>QbP13T`1VUxg66M9AM2P{qGNP#W^$iAb*|abnCLR$cC=e#6 zowR@YB94F(7YMC69eW_ys*L8pq-ZAw<6M|DFF1dtyI}R(8hKUi&B?-15%zL|L6f~Ha@*>DnmfKFkgs$tXK zQi1J#&afnmnh0Dk>drbMuHwjnwq7fM)tZxu@E1WE!$0{=s0Pvlb?p8mxNfo7psz(x z{j%Vik|nO2qrAvEb72$1H^6?q}9QJ0YxPN%JAkmga=T zRU%E_-+)WjGfoC>S7HUCDa+AQ$>Vc$&tXP|KENLjcc-sQAUd7SFyy%#qK>qW2*a)l*gljd~@<#}I;kS?d#s5GL6UcIYJo7P|r_%kL*9C7oR|&H> z5~%EJwgNQ3IW`=(pL>j-f$43_7fc@#J_!92qT?RIS&f~|b?!wK@CI41ayGy}*Cock zjp=r|!k$0~EQaXjY0>?}3dC)gwssV&$kL5I!TO<@*oxSxyCH6*3lDLZD zP3IbI^K-)Px4kbAcI+?NL{$9iHk`0u>ft6XaHI-LC>s;TQxtnTl;5`0Wj&qFe-h*i z1Y#qRe>R-P=r;^aI1w-VlFkT0IP_y-JyIYxCjzDfO!+)dkNd33JDsq%PO5lcAPOLm zsd8ng{4x4h5{E;{{pAGI+dP473q9-D6q1jULsT4RefVihpq;1LaGF4fn2GftZ_xnX zw271OgTWIBz_(&OKMVbqB(N0AvtPe{9jL>lrko`xE)6D}K0@>=o1W;Omf}C3d3&JE z5Vs#?qnmi*eheP(bd>dOTiiHG!LM7lZsRD&>r^U_`uH=FD?d0wlxUKRM4DA?+qV5z z7f!+jf)mJgMvJ#&*YQdcSc>6^)~at^eqhSbkDX01Vb7Z(5Jrh^+}GLwhrT;0AjR-ygpVcv38svP!ydI~2t*mE_#GyrHi$QyaI9W>m7At?fzuIt z#0exdI&Lxzw};Vjp9%ZkP=V+e8=)-qQ}Hi~7$s+!aGc(w#SHCa7fvSxT7 zjE)K5wzJG+wi)i^A+79CfuvI0;!;wW%p-)8I8M=*!3WSrr2T1x?8P22M*jefw)5K( z;7!=OqACz6kklxdV={6oKlD2$9E+Ecs)gK*ilgu!Q37EfH3@-e?eh%yZ>4i>aN(dE zDG()Os9lfa8bi-aZ|7jFpW=?mR$KrFD zD^GH?GP#;_y%+iLYmcqiH3VT?1ipuDI}pAqpDX5^1tKQ=`_bzW%Bzkc%yfIFUsUMr zXk~EqZ&@`}$+X8`kpiJ(j-)-NWBoP->pkkohoR^8*Uy|wat%9FVXYkvEiVBDi~%P zSg1zh-C!8?1=s?H?g&FG+KT|zsSpIN+d>^-SIk`q8^3G8Hxo?W8Yi5DHKmWiX5DWn zZI~%YASy@;6(^RT0l?d?V1OON(0v)8S2|j;F6FbMSNCBK!t_FmJ@}|kJYs$v}JVOr@480&P0$8Yw zl=(>jH+Td~4j0=nX1@NK-B0#$KWj8+(@9z{g<}w}> zzexp#8(Q98M&gH5b}53;RdQPdv?lLDN|b@R=wHk(qPU^9NHF~bVT^<3P84i(UjC9C zr*Gc8dEbT&8)o$B(?^=n*?{&oav;&aycpNJckdp={gE^UsjTGJ;wT-z7avAW5Wl=? zS!81UvNpfk`Xv0nPf=Er(hpi*PN#gRNEUwfqDP2d(&0Oc9|2H{y?w~0cdXYzYov#L z-K5XiOa8~P>U}ffd_t<9&SmPoopn)a{&^NK_52fS=uk$+GnQVg-}U5T%Sp@6+Ho9O z(G(g{!U>wi4DmbYRjNq7YN0yN!C--<4)fBc6GFHOMdAj^4sLqYF(&aT;7#RtY*7}0 zxCWArPEWcI!FAQ6!v5%EKjkt*aYH$(i{6Bm>*EtZ_yA|V{u75mw3H0 zSwNcq!fhbjNDp0&KQGl%3o}2XeiG%>lXn>QT&z^_NDUKS`Zr1U^6$AHN&RvJ$>TAx z3&d3`weXLnp~gZR%od6vDyD$JGukN*#rp#`)+ fS9<5lsoMVwztWhI*cqZ+00000NkvXXu0mjf9$Six literal 0 HcmV?d00001 diff --git a/Goobieverse/public/index.html b/Goobieverse/public/index.html new file mode 100644 index 00000000..c5879ef8 --- /dev/null +++ b/Goobieverse/public/index.html @@ -0,0 +1,75 @@ + + + + Goobieverse + + + + + +
+ + + +
+ + diff --git a/Goobieverse/src/app.hooks.ts b/Goobieverse/src/app.hooks.ts new file mode 100644 index 00000000..1be53383 --- /dev/null +++ b/Goobieverse/src/app.hooks.ts @@ -0,0 +1,34 @@ +// Application hooks that run for every service +// Don't remove this comment. It's needed to format import lines nicely. + +export default { + before: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, + + after: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, + + error: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } +}; diff --git a/Goobieverse/src/app.ts b/Goobieverse/src/app.ts new file mode 100644 index 00000000..5f08aa78 --- /dev/null +++ b/Goobieverse/src/app.ts @@ -0,0 +1,74 @@ +import path from 'path'; +import favicon from 'serve-favicon'; +import compress from 'compression'; +import helmet from 'helmet'; +import cors from 'cors'; + +import feathers from '@feathersjs/feathers'; +import configuration from '@feathersjs/configuration'; +import express from '@feathersjs/express'; +import socketio from '@feathersjs/socketio'; +import { publicRoutes } from './routes/publicRoutes'; +import config from './appconfig'; +import { Application } from './declarations'; +import logger from './logger'; +import middleware from './middleware'; +import services from './services'; +import appHooks from './app.hooks'; +import channels from './channels'; +import { HookContext as FeathersHookContext } from '@feathersjs/feathers'; +import authentication from './authentication'; +import mongodb from './mongodb'; +// Don't remove this comment. It's needed to format import lines nicely. + +const app: Application = express(feathers()); +export type HookContext = { app: Application } & FeathersHookContext; + +// Enable security, CORS, compression, favicon and body parsing +app.use(helmet({ + contentSecurityPolicy: false +})); +app.use(cors()); +app.use(compress()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); + +//set Public folder path +app.set('public',config.server.publicPath); + +//page favicon +app.use(favicon(path.join(app.settings.public, 'favicon.ico'))); + + +// routes +app.use('/', publicRoutes); +// Host the public folder +app.use('/', express.static(app.settings.public)); + + +// Set up Plugins and providers +app.configure(express.rest()); +app.configure(socketio()); + +app.set('host', config.server.local ? config.server.hostName + ':' + config.server.port : config.server.hostName); +app.set('port',config.server.port); +app.set('paginate',config.server.paginate); +app.set('authentication', config.authentication); + +app.configure(mongodb); + +// Configure other middleware (see `middleware/index.ts`) +app.configure(middleware); +app.configure(authentication); +// Set up our services (see `services/index.ts`) +app.configure(services); +// Set up event channels (see channels.ts) +app.configure(channels); + +// Configure a middleware for 404s and the error handler +app.use(express.notFound()); +app.use(express.errorHandler({ logger } as any)); + +app.hooks(appHooks); + +export default app; diff --git a/Goobieverse/src/appconfig.ts b/Goobieverse/src/appconfig.ts new file mode 100644 index 00000000..bf90a4c6 --- /dev/null +++ b/Goobieverse/src/appconfig.ts @@ -0,0 +1,113 @@ +import dotenv from 'dotenv-flow'; +import appRootPath from 'app-root-path'; +import {IsNullOrEmpty,getMyExternalIPAddress} from './utils/Misc'; + +if(globalThis.process?.env.APP_ENV === 'development') { + const fs = require('fs'); + if (!fs.existsSync(appRootPath.path + '/.env') && !fs.existsSync(appRootPath.path + '/.env.local')) { + const fromEnvPath = appRootPath.path + '/.env.local.default'; + const toEnvPath = appRootPath.path + '/.env.local'; + fs.copyFileSync(fromEnvPath, toEnvPath, fs.constants.COPYFILE_EXCL); + } +} + +dotenv.config({ + path: appRootPath.path, + silent: true +}); + +/** + * Server + */ + +const server = { + local : process.env.LOCAL === 'true', + hostName:process.env.SERVER_HOST, + port:process.env.PORT ?? 3030, + paginate:{ + default:10, + max:100 + }, + publicPath:process.env.PUBLIC_PATH, + version:process.env.SERVER_VERSION ?? '' +}; + +/** + * Metaverse Server + */ + +const metaverseServer = { + listen_host: process.env.LISTEN_HOST ??'0.0.0.0', + listen_port: process.env.LISTEN_PORT ?? 9400, + metaverseInfoAdditionFile: process.env.METAVERSE_INFO_File ?? '', +}; + +/** + * Authentication + */ +const authentication= { + entity: null, + service: null, + secret: process.env.AUTH_SECRET ?? 'testing', + authStrategies: ['jwt','local'], + jwtOptions: { + expiresIn: '60 days' + }, + local: { + usernameField: 'email', + passwordField: 'password' + }, + bearerToken: { + numBytes: 16 + }, + oauth: { + redirect: '/', + auth0: { + key: '', + secret: '', + subdomain: '', + scope: ['profile','openid','email'] + } + } +}; + + +/** + * Metaverse + */ + +const metaverse = { + metaverseName: process.env.METAVERSE_NAME ?? '', + metaverseNickName: process.env.METAVERSE_NICK_NAME ?? '', + metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? '', // if empty, set to self + defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? '', // if empty, set to self + dashboardUrl: process.env.DASHBOARD_URL +}; + +if (IsNullOrEmpty(metaverse.metaverseServerUrl) || IsNullOrEmpty(metaverse.defaultIceServerUrl)) { + getMyExternalIPAddress().then((ipAddress)=>{ + if(IsNullOrEmpty(metaverse.metaverseServerUrl)){ + const newUrl = `http://${ipAddress}:${metaverseServer.listen_port.toString()}/`; + metaverse.metaverseServerUrl = newUrl; + } + if(IsNullOrEmpty(metaverse.defaultIceServerUrl)){ + metaverse.defaultIceServerUrl = ipAddress; + } + }); +} + + + + +/** + * Full config + */ +const config = { + deployStage: process.env.DEPLOY_STAGE, + authentication, + server, + metaverse, + metaverseServer +}; + +export default config; \ No newline at end of file diff --git a/Goobieverse/src/authentication.ts b/Goobieverse/src/authentication.ts new file mode 100644 index 00000000..e5031c25 --- /dev/null +++ b/Goobieverse/src/authentication.ts @@ -0,0 +1,23 @@ +import { ServiceAddons } from '@feathersjs/feathers'; +import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'; +import { LocalStrategy } from '@feathersjs/authentication-local'; +import { expressOauth } from '@feathersjs/authentication-oauth'; + +import { Application } from './declarations'; + +declare module './declarations' { + interface ServiceTypes { + 'authentication': AuthenticationService & ServiceAddons; + } +} + +export default function(app: Application): void { + const authentication = new AuthenticationService(app); + + authentication.register('jwt', new JWTStrategy()); + authentication.register('local', new LocalStrategy()); + + app.use('/authentication', authentication); + app.configure(expressOauth()); +} + \ No newline at end of file diff --git a/Goobieverse/src/channels.ts b/Goobieverse/src/channels.ts new file mode 100644 index 00000000..687c756e --- /dev/null +++ b/Goobieverse/src/channels.ts @@ -0,0 +1,65 @@ +import '@feathersjs/transport-commons'; +import { HookContext } from '@feathersjs/feathers'; +import { Application } from './declarations'; + +export default function(app: Application): void { + if(typeof app.channel !== 'function') { + // If no real-time functionality has been configured just return + return; + } + + app.on('connection', (connection: any): void => { + // On a new real-time connection, add it to the anonymous channel + app.channel('anonymous').join(connection); + }); + + app.on('login', (authResult: any, { connection }: any): void => { + // connection can be undefined if there is no + // real-time connection, e.g. when logging in via REST + if(connection) { + // Obtain the logged in user from the connection + // const user = connection.user; + + // The connection is no longer anonymous, remove it + app.channel('anonymous').leave(connection); + + // Add it to the authenticated user channel + app.channel('authenticated').join(connection); + + // Channels can be named anything and joined on any condition + + // E.g. to send real-time events only to admins use + // if(user.isAdmin) { app.channel('admins').join(connection); } + + // If the user has joined e.g. chat rooms + // if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(connection)); + + // Easily organize users by email and userid for things like messaging + // app.channel(`emails/${user.email}`).join(connection); + // app.channel(`userIds/${user.id}`).join(connection); + } + }); + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + app.publish((data: any, hook: HookContext) => { + // Here you can add event publishers to channels set up in `channels.ts` + // To publish only for a specific event use `app.publish(eventname, () => {})` + + console.log('Publishing all events to all authenticated users. See `channels.ts` and https://docs.feathersjs.com/api/channels.html for more information.'); // eslint-disable-line + + // e.g. to publish all service events to all authenticated users use + return app.channel('authenticated'); + }); + + // Here you can also add service specific event publishers + // e.g. the publish the `users` service `created` event to the `admins` channel + // app.service('users').publish('created', () => app.channel('admins')); + + // With the userid and email organization from above you can easily select involved users + // app.service('messages').publish(() => { + // return [ + // app.channel(`userIds/${data.createdBy}`), + // app.channel(`emails/${data.recipientEmail}`) + // ]; + // }); +} diff --git a/Goobieverse/src/controllers/PublicRoutesController.ts b/Goobieverse/src/controllers/PublicRoutesController.ts new file mode 100644 index 00000000..8f88c306 --- /dev/null +++ b/Goobieverse/src/controllers/PublicRoutesController.ts @@ -0,0 +1,31 @@ +import { MetaverseInfoModel } from './../interfaces/MetaverseInfo'; +import config from '../appconfig'; +import {IsNotNullOrEmpty, readInJSON } from '../utils/Misc'; + +export const PublicRoutesController = ()=>{ + const metaverseInfo = async(req:any,res:any,next:any) => { + const response:MetaverseInfoModel = { + metaverse_name:config.metaverse.metaverseName, + metaverse_nick_name: config.metaverse.metaverseNickName, + ice_server_url: config.metaverse.defaultIceServerUrl , + metaverse_url: config.metaverse.metaverseServerUrl, + metaverse_server_version:{ + version_tag:config.server.version + } + }; + try { + const additionUrl: string = config.metaverseServer.metaverseInfoAdditionFile; + if (IsNotNullOrEmpty(additionUrl)) { + const additional = await readInJSON(additionUrl); + if (IsNotNullOrEmpty(additional)) { + response.metaverse_server_version = additional; + } + } + } + catch (err) { + //console.error(`procMetaverseInfo: exception reading additional info file: ${err}`); + } + res.status(200).json(response); + }; + return {metaverseInfo}; +}; \ No newline at end of file diff --git a/Goobieverse/src/declarations.d.ts b/Goobieverse/src/declarations.d.ts new file mode 100644 index 00000000..56550834 --- /dev/null +++ b/Goobieverse/src/declarations.d.ts @@ -0,0 +1,6 @@ +import { Application as ExpressFeathers } from '@feathersjs/express'; + +// A mapping of service names to types. Will be extended in service files. +export interface ServiceTypes {} +// The application instance type that will be used everywhere else +export type Application = ExpressFeathers; diff --git a/Goobieverse/src/index.ts b/Goobieverse/src/index.ts new file mode 100644 index 00000000..7c8a791c --- /dev/null +++ b/Goobieverse/src/index.ts @@ -0,0 +1,13 @@ +import logger from './logger'; +import app from './app'; + +const port = app.get('port'); +const server = app.listen(port); + +process.on('unhandledRejection', (reason, p) => + logger.error('Unhandled Rejection at: Promise ', p, reason) +); + +server.on('listening', () => + logger.info('Feathers application started on http://%s:%d', app.get('host'), port) +); diff --git a/Goobieverse/src/interfaces/MetaverseInfo.ts b/Goobieverse/src/interfaces/MetaverseInfo.ts new file mode 100644 index 00000000..741123f2 --- /dev/null +++ b/Goobieverse/src/interfaces/MetaverseInfo.ts @@ -0,0 +1,8 @@ + +export interface MetaverseInfoModel{ + metaverse_name:string, + metaverse_nick_name:string, + metaverse_url:string, + ice_server_url:string, + metaverse_server_version:any +} \ No newline at end of file diff --git a/Goobieverse/src/logger.ts b/Goobieverse/src/logger.ts new file mode 100644 index 00000000..739c222b --- /dev/null +++ b/Goobieverse/src/logger.ts @@ -0,0 +1,16 @@ +import { createLogger, format, transports } from 'winston'; + +// Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston +const logger = createLogger({ + // To see more detailed errors, change this to 'debug' + level: 'info', + format: format.combine( + format.splat(), + format.simple() + ), + transports: [ + new transports.Console() + ], +}); + +export default logger; diff --git a/Goobieverse/src/middleware/index.ts b/Goobieverse/src/middleware/index.ts new file mode 100644 index 00000000..e7826837 --- /dev/null +++ b/Goobieverse/src/middleware/index.ts @@ -0,0 +1,6 @@ +import { Application } from '../declarations'; +// Don't remove this comment. It's needed to format import lines nicely. + +// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function +export default function (app: Application): void { +} diff --git a/Goobieverse/src/mongodb.ts b/Goobieverse/src/mongodb.ts new file mode 100644 index 00000000..67703836 --- /dev/null +++ b/Goobieverse/src/mongodb.ts @@ -0,0 +1,20 @@ +import { MongoClient } from 'mongodb'; +import { Application } from './declarations'; +import { IsNotNullOrEmpty } from './utils/Misc'; +export default function (app: Application): void { + let connection = ''; + if(IsNotNullOrEmpty(process.env.DATABASE_URL)){ + connection = process.env.DATABASE_URL || ''; + }else{ + const userSpec = `${process.env.DB_USER}:${process.env.DB_PASSWORD}`; + const hostSpec = `${process.env.DB_HOST}:${process.env.DB_PORT}`; + let optionsSpec = ''; + if (process.env.DB_AUTHDB !== 'admin') { + optionsSpec += `?authSource=${process.env.DB_AUTHDB}`; + } + connection = `mongodb://${userSpec}@${hostSpec}/${optionsSpec}`; + } + const database = process.env.DB_NAME; + const mongoClient = MongoClient.connect(connection).then(client => client.db(database)); + app.set('mongoClient', mongoClient); +} diff --git a/Goobieverse/src/routes/publicRoutes.ts b/Goobieverse/src/routes/publicRoutes.ts new file mode 100644 index 00000000..4a1df329 --- /dev/null +++ b/Goobieverse/src/routes/publicRoutes.ts @@ -0,0 +1,8 @@ +import express from 'express'; +import { PublicRoutesController} from '../controllers/PublicRoutesController'; + +const publicRoutes = express.Router(); + +publicRoutes.get('/metaverse_info',PublicRoutesController().metaverseInfo); + +export { publicRoutes}; diff --git a/Goobieverse/src/services/index.ts b/Goobieverse/src/services/index.ts new file mode 100644 index 00000000..bdb42649 --- /dev/null +++ b/Goobieverse/src/services/index.ts @@ -0,0 +1,7 @@ +import { Application } from '../declarations'; +//import metaverseInfo from './metaverse_info/metaverse_info.service'; +// Don't remove this comment. It's needed to format import lines nicely. + +export default function (app: Application): void { + //app.configure(metaverseInfo); +} diff --git a/Goobieverse/src/utils/Misc.ts b/Goobieverse/src/utils/Misc.ts new file mode 100644 index 00000000..e355e4ec --- /dev/null +++ b/Goobieverse/src/utils/Misc.ts @@ -0,0 +1,118 @@ +import fs from 'fs'; +import http from 'http'; +import https from 'https'; +import os from 'os'; + +// Return 'true' if the passed value is null or empty +export function IsNullOrEmpty(pVal: any): boolean {return (typeof(pVal) === 'undefined')|| (pVal === null)|| (typeof(pVal) === 'string' && String(pVal).length === 0);} + +// Return 'true' if the passed value is not null or empty +export function IsNotNullOrEmpty(pVal: any): boolean {return !IsNullOrEmpty(pVal);} + +// Utility routine that reads in JSON content from either an URL or a filename. +// Returns the parsed JSON object or 'undefined' if any errors. +export async function readInJSON(pFilenameOrURL: string): Promise { + let configBody:string; + if (pFilenameOrURL.startsWith('http://')) { + configBody = await httpRequest(pFilenameOrURL); + } + else { + if (pFilenameOrURL.startsWith('https://')) { + configBody = await httpsRequest(pFilenameOrURL); + } + else { + try { + // We should technically sanitize this filename but if one can change the environment + // or config file variables, the app is already poned. + configBody = fs.readFileSync(pFilenameOrURL, 'utf-8'); + } + catch (err) { + configBody = ''; + console.debug(`readInJSON: failed read of user config file ${pFilenameOrURL}: ${err}`); + } + } + } + if (IsNotNullOrEmpty(configBody)) { + return JSON.parse(configBody); + } + return undefined; +} + + +// Do a simple https GET and return the response as a string +export async function httpsRequest(pUrl: string): Promise { + return new Promise( ( resolve, reject) => { + https.get(pUrl, (resp:any) => { + let data = ''; + resp.on('data', (chunk: string) => { + data += chunk; + }); + resp.on('end', () => { + resolve(data); + }); + }).on('error', (err: any) => { + reject(err); + }); + }); +} + +// Do a simple http GET and return the response as a string +export async function httpRequest(pUrl: string): Promise { + return new Promise( ( resolve, reject) => { + http.get(pUrl, (resp:any) => { + let data = ''; + resp.on('data', (chunk: string) => { + data += chunk; + }); + resp.on('end', () => { + resolve(data); + }); + }).on('error', (err: any) => { + reject(err); + }); + }); +} + + +let myExternalAddr: string; +export async function getMyExternalIPAddress(): Promise { + if (IsNotNullOrEmpty(myExternalAddr)) { + return Promise.resolve(myExternalAddr); + } + return new Promise(( resolve, reject) => { + httpsRequest('https://api.ipify.org').then( resp => { + myExternalAddr = resp; + resolve(myExternalAddr); + }).catch ( err => { + // Can't get it that way for some reason. Ask our interface + const networkInterfaces = os.networkInterfaces(); + // { 'lo1': [ info, info ], 'eth0': [ info, info ]} where 'info' could be v4 and v6 addr infos + + let addressv4 = ''; + let addressv6 = ''; + + Object.keys(networkInterfaces).forEach(dev => { + networkInterfaces[dev]?.filter(details => { + if (details.family === 'IPv4' && details.internal === false) { + addressv4 = details.address; + } + if (details.family === 'IPv6' && details.internal === false) { + addressv6 = details.address; + } + }); + }); + let address = ''; + if (IsNullOrEmpty(addressv4)) { + address = addressv6; + }else{ + address = addressv6; + } + + if(IsNullOrEmpty(address)) { + reject('No address found'); + } + myExternalAddr = address.toString(); + resolve(myExternalAddr); + }); + }); +} \ No newline at end of file diff --git a/Goobieverse/test/app.test.ts b/Goobieverse/test/app.test.ts new file mode 100644 index 00000000..4f7d365e --- /dev/null +++ b/Goobieverse/test/app.test.ts @@ -0,0 +1,69 @@ +import assert from 'assert'; +import { Server } from 'http'; +import url from 'url'; +import axios from 'axios'; + +import app from '../src/app'; + +const port = app.get('port') || 8998; +const getUrl = (pathname?: string): string => url.format({ + hostname: app.get('host') || 'localhost', + protocol: 'http', + port, + pathname +}); + +describe('Feathers application tests (with jest)', () => { + let server: Server; + + beforeAll(done => { + server = app.listen(port); + server.once('listening', () => done()); + }); + + afterAll(done => { + server.close(done); + }); + + it('starts and shows the index page', async () => { + expect.assertions(1); + + const { data } = await axios.get(getUrl()); + + expect(data.indexOf('')).not.toBe(-1); + }); + + describe('404', () => { + it('shows a 404 HTML page', async () => { + expect.assertions(2); + + try { + await axios.get(getUrl('path/to/nowhere'), { + headers: { + 'Accept': 'text/html' + } + }); + } catch (error: any) { + const { response } = error; + + expect(response.status).toBe(404); + expect(response.data.indexOf('')).not.toBe(-1); + } + }); + + it('shows a 404 JSON error without stack trace', async () => { + expect.assertions(4); + + try { + await axios.get(getUrl('path/to/nowhere')); + } catch (error: any) { + const { response } = error; + + expect(response.status).toBe(404); + expect(response.data.code).toBe(404); + expect(response.data.message).toBe('Page not found'); + expect(response.data.name).toBe('NotFound'); + } + }); + }); +}); diff --git a/Goobieverse/test/authentication.test.ts b/Goobieverse/test/authentication.test.ts new file mode 100644 index 00000000..c92c9bb9 --- /dev/null +++ b/Goobieverse/test/authentication.test.ts @@ -0,0 +1,32 @@ +import app from '../src/app'; + +describe('authentication', () => { + it('registered the authentication service', () => { + expect(app.service('authentication')).toBeTruthy(); + }); + + describe('local strategy', () => { + const userInfo = { + email: 'someone@example.com', + password: 'supersecret' + }; + + beforeAll(async () => { + try { + await app.service('users').create(userInfo); + } catch (error) { + // Do nothing, it just means the user already exists and can be tested + } + }); + + it('authenticates user and creates accessToken', async () => { + const { user, accessToken } = await app.service('authentication').create({ + strategy: 'local', + ...userInfo + }, {}); + + expect(accessToken).toBeTruthy(); + expect(user).toBeTruthy(); + }); + }); +}); diff --git a/Goobieverse/test/services/metaverse_info.test.ts b/Goobieverse/test/services/metaverse_info.test.ts new file mode 100644 index 00000000..a4d9320f --- /dev/null +++ b/Goobieverse/test/services/metaverse_info.test.ts @@ -0,0 +1,8 @@ +import app from '../../src/app'; + +describe('\'metaverse_info\' service', () => { + it('registered the service', () => { + const service = app.service('metaverse_info'); + expect(service).toBeTruthy(); + }); +}); diff --git a/Goobieverse/tsconfig.json b/Goobieverse/tsconfig.json new file mode 100644 index 00000000..70dd6b8a --- /dev/null +++ b/Goobieverse/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "outDir": "./lib", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true + }, + "exclude": [ + "test" + ] +} From 0ccb3d445ebb1d4adda4501b74522a2ff5b357aa Mon Sep 17 00:00:00 2001 From: khilanlalani1503 Date: Thu, 30 Dec 2021 15:46:18 +0530 Subject: [PATCH 002/128] users register and login --- Goobieverse/package.json | 4 + Goobieverse/src/appconfig.ts | 111 ++++++----- Goobieverse/src/authentication.ts | 48 +++-- Goobieverse/src/hooks/userData.ts | 7 + Goobieverse/src/interfaces/AccountModel.ts | 42 +++++ .../src/services/friends/friends.class.ts | 47 +++++ .../src/services/friends/friends.hooks.ts | 34 ++++ .../src/services/friends/friends.service.ts | 26 +++ Goobieverse/src/services/index.ts | 5 +- Goobieverse/src/services/users/users.class.ts | 105 +++++++++++ Goobieverse/src/services/users/users.hooks.ts | 40 ++++ .../src/services/users/users.service.ts | 26 +++ Goobieverse/src/utils/Misc.ts | 172 ++++++++++-------- Goobieverse/src/utils/messages.ts | 47 +++++ Goobieverse/src/utils/response.ts | 8 + Goobieverse/test/services/friends.test.ts | 8 + Goobieverse/test/services/users.test.ts | 8 + 17 files changed, 601 insertions(+), 137 deletions(-) create mode 100644 Goobieverse/src/hooks/userData.ts create mode 100644 Goobieverse/src/interfaces/AccountModel.ts create mode 100644 Goobieverse/src/services/friends/friends.class.ts create mode 100644 Goobieverse/src/services/friends/friends.hooks.ts create mode 100644 Goobieverse/src/services/friends/friends.service.ts create mode 100644 Goobieverse/src/services/users/users.class.ts create mode 100644 Goobieverse/src/services/users/users.hooks.ts create mode 100644 Goobieverse/src/services/users/users.service.ts create mode 100644 Goobieverse/src/utils/messages.ts create mode 100644 Goobieverse/src/utils/response.ts create mode 100644 Goobieverse/test/services/friends.test.ts create mode 100644 Goobieverse/test/services/users.test.ts diff --git a/Goobieverse/package.json b/Goobieverse/package.json index 6cb56b3c..9c7a9928 100644 --- a/Goobieverse/package.json +++ b/Goobieverse/package.json @@ -50,6 +50,7 @@ "@feathersjs/transport-commons": "^4.5.11", "@types/dotenv-flow": "^3.2.0", "app-root-path": "^3.0.0", + "bcrypt": "^5.0.1", "compression": "^1.7.4", "cors": "^2.8.5", "cross-env": "^7.0.3", @@ -60,10 +61,13 @@ "mongodb": "^4.2.2", "mongodb-core": "^3.2.7", "serve-favicon": "^2.5.0", + "trim": "^1.0.1", + "uuidv4": "^6.2.12", "winston": "^3.3.3" }, "devDependencies": { "@types/app-root-path": "^1.2.4", + "@types/bcrypt": "^5.0.0", "@types/compression": "^1.7.2", "@types/cors": "^2.8.12", "@types/dotenv-flow": "^3.2.0", diff --git a/Goobieverse/src/appconfig.ts b/Goobieverse/src/appconfig.ts index bf90a4c6..e1681f90 100644 --- a/Goobieverse/src/appconfig.ts +++ b/Goobieverse/src/appconfig.ts @@ -1,19 +1,22 @@ -import dotenv from 'dotenv-flow'; -import appRootPath from 'app-root-path'; -import {IsNullOrEmpty,getMyExternalIPAddress} from './utils/Misc'; +import dotenv from "dotenv-flow"; +import appRootPath from "app-root-path"; +import { IsNullOrEmpty, getMyExternalIPAddress } from "./utils/Misc"; -if(globalThis.process?.env.APP_ENV === 'development') { - const fs = require('fs'); - if (!fs.existsSync(appRootPath.path + '/.env') && !fs.existsSync(appRootPath.path + '/.env.local')) { - const fromEnvPath = appRootPath.path + '/.env.local.default'; - const toEnvPath = appRootPath.path + '/.env.local'; +if (globalThis.process?.env.APP_ENV === "development") { + const fs = require("fs"); + if ( + !fs.existsSync(appRootPath.path + "/.env") && + !fs.existsSync(appRootPath.path + "/.env.local") + ) { + const fromEnvPath = appRootPath.path + "/.env.local.default"; + const toEnvPath = appRootPath.path + "/.env.local"; fs.copyFileSync(fromEnvPath, toEnvPath, fs.constants.COPYFILE_EXCL); } } - + dotenv.config({ path: appRootPath.path, - silent: true + silent: true, }); /** @@ -21,15 +24,15 @@ dotenv.config({ */ const server = { - local : process.env.LOCAL === 'true', - hostName:process.env.SERVER_HOST, - port:process.env.PORT ?? 3030, - paginate:{ - default:10, - max:100 + local: process.env.LOCAL === "true", + hostName: process.env.SERVER_HOST, + port: process.env.PORT ?? 3030, + paginate: { + default: 10, + max: 100, }, - publicPath:process.env.PUBLIC_PATH, - version:process.env.SERVER_VERSION ?? '' + publicPath: process.env.PUBLIC_PATH, + version: process.env.SERVER_VERSION ?? "", }; /** @@ -37,77 +40,85 @@ const server = { */ const metaverseServer = { - listen_host: process.env.LISTEN_HOST ??'0.0.0.0', + listen_host: process.env.LISTEN_HOST ?? "0.0.0.0", listen_port: process.env.LISTEN_PORT ?? 9400, - metaverseInfoAdditionFile: process.env.METAVERSE_INFO_File ?? '', + metaverseInfoAdditionFile: process.env.METAVERSE_INFO_File ?? "", }; /** * Authentication */ -const authentication= { - entity: null, - service: null, - secret: process.env.AUTH_SECRET ?? 'testing', - authStrategies: ['jwt','local'], +const authentication = { + entity: "user", + service: "users", + secret: process.env.AUTH_SECRET ?? "testing", + authStrategies: ["jwt", "local"], jwtOptions: { - expiresIn: '60 days' + expiresIn: "60 days", }, local: { - usernameField: 'email', - passwordField: 'password' + usernameField: "email", + passwordField: "password", }, bearerToken: { - numBytes: 16 + numBytes: 16, }, oauth: { - redirect: '/', + redirect: "/", auth0: { - key: '', - secret: '', - subdomain: '', - scope: ['profile','openid','email'] - } - } + key: "", + secret: "", + subdomain: "", + scope: ["profile", "openid", "email"], + }, + }, }; - /** * Metaverse */ const metaverse = { - metaverseName: process.env.METAVERSE_NAME ?? '', - metaverseNickName: process.env.METAVERSE_NICK_NAME ?? '', - metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? '', // if empty, set to self - defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? '', // if empty, set to self - dashboardUrl: process.env.DASHBOARD_URL + metaverseName: process.env.METAVERSE_NAME ?? "", + metaverseNickName: process.env.METAVERSE_NICK_NAME ?? "", + metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? "", // if empty, set to self + defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? "", // if empty, set to self + dashboardUrl: process.env.DASHBOARD_URL, }; -if (IsNullOrEmpty(metaverse.metaverseServerUrl) || IsNullOrEmpty(metaverse.defaultIceServerUrl)) { - getMyExternalIPAddress().then((ipAddress)=>{ - if(IsNullOrEmpty(metaverse.metaverseServerUrl)){ +if ( + IsNullOrEmpty(metaverse.metaverseServerUrl) || + IsNullOrEmpty(metaverse.defaultIceServerUrl) +) { + getMyExternalIPAddress().then((ipAddress) => { + if (IsNullOrEmpty(metaverse.metaverseServerUrl)) { const newUrl = `http://${ipAddress}:${metaverseServer.listen_port.toString()}/`; metaverse.metaverseServerUrl = newUrl; } - if(IsNullOrEmpty(metaverse.defaultIceServerUrl)){ + if (IsNullOrEmpty(metaverse.defaultIceServerUrl)) { metaverse.defaultIceServerUrl = ipAddress; } }); } - - +const dbCollections = { + domains: "domains", + accounts: "accounts", + places: "places", + tokens: "tokens", +}; /** * Full config */ + const config = { deployStage: process.env.DEPLOY_STAGE, authentication, server, metaverse, - metaverseServer + metaverseServer, + dbCollections, }; -export default config; \ No newline at end of file +export default config; diff --git a/Goobieverse/src/authentication.ts b/Goobieverse/src/authentication.ts index e5031c25..01cfd482 100644 --- a/Goobieverse/src/authentication.ts +++ b/Goobieverse/src/authentication.ts @@ -1,23 +1,47 @@ -import { ServiceAddons } from '@feathersjs/feathers'; -import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'; -import { LocalStrategy } from '@feathersjs/authentication-local'; -import { expressOauth } from '@feathersjs/authentication-oauth'; +import { Params, ServiceAddons } from "@feathersjs/feathers"; +import { AuthenticationService, JWTStrategy } from "@feathersjs/authentication"; +import { LocalStrategy } from "@feathersjs/authentication-local"; +import { expressOauth } from "@feathersjs/authentication-oauth"; -import { Application } from './declarations'; +import { Application } from "./declarations"; -declare module './declarations' { +declare module "./declarations" { interface ServiceTypes { - 'authentication': AuthenticationService & ServiceAddons; + authentication: AuthenticationService & ServiceAddons; } } +// class MyLocalStrategy extends LocalStrategy { +// async findEntity(username: any, params: Params) { +// try { +// const entity = await super.findEntity(username, params); -export default function(app: Application): void { +// return entity; +// } catch (error) { +// throw new Error("Entity not found"); +// } +// } + +// async comparePassword(entity: any, password: any) { +// try { +// const result = await super.comparePassword(entity, password); + +// return result; +// } catch (error) { +// throw new Error("Invalid password"); +// } +// } +// } + +export default function (app: Application): void { const authentication = new AuthenticationService(app); + // const authService = new AuthenticationService(app); + + authentication.register("jwt", new JWTStrategy()); + authentication.register("local", new LocalStrategy()); - authentication.register('jwt', new JWTStrategy()); - authentication.register('local', new LocalStrategy()); + // authService.register("local", new MyLocalStrategy()); + // app.use("/authentication", authService); - app.use('/authentication', authentication); + app.use("/authentication", authentication); app.configure(expressOauth()); } - \ No newline at end of file diff --git a/Goobieverse/src/hooks/userData.ts b/Goobieverse/src/hooks/userData.ts new file mode 100644 index 00000000..c01cca97 --- /dev/null +++ b/Goobieverse/src/hooks/userData.ts @@ -0,0 +1,7 @@ +import { Hook, HookContext } from "@feathersjs/feathers"; + +export async function myHook(context: any): Promise { + // console.log(context, "myHook"); + + return context; +} diff --git a/Goobieverse/src/interfaces/AccountModel.ts b/Goobieverse/src/interfaces/AccountModel.ts new file mode 100644 index 00000000..81fbf1d4 --- /dev/null +++ b/Goobieverse/src/interfaces/AccountModel.ts @@ -0,0 +1,42 @@ +export interface AccountModel { + id: string; + username: string; + email: string; + accountSettings: string; // JSON of client settings + imagesHero: string; + imagesThumbnail: string; + imagesTiny: string; + password: string; + + locationConnected: boolean; + locationPath: string; // "/floatX,floatY,floatZ/floatX,floatY,floatZ,floatW" + locationPlaceId: string; // uuid of place + locationDomainId: string; // uuid of domain located in + locationNetworkAddress: string; + locationNetworkPort: number; + locationNodeId: string; // sessionId + availability: string[]; // contains 'none', 'friends', 'connections', 'all' + + connections: string[]; + friends: string[]; + locker: any; // JSON blob stored for user from server + profileDetail: any; // JSON blob stored for user from server + + // User authentication + passwordHash: string; + passwordSalt: string; + sessionPublicKey: string; // PEM public key generated for this session + accountEmailVerified: boolean; // default true if not present + + // Old stuff + xmppPassword: string; + discourseApiKey: string; + walletId: string; + + // Admin stuff + // ALWAYS USE functions in Roles class to manipulate this list of roles + roles: string[]; // account roles (like 'admin') + IPAddrOfCreator: string; // IP address that created this account + whenCreated: Date; // date of account creation + timeOfLastHeartbeat: Date; // when we last heard from this user +} diff --git a/Goobieverse/src/services/friends/friends.class.ts b/Goobieverse/src/services/friends/friends.class.ts new file mode 100644 index 00000000..02907d99 --- /dev/null +++ b/Goobieverse/src/services/friends/friends.class.ts @@ -0,0 +1,47 @@ +import { Db } from "mongodb"; +import { Service, MongoDBServiceOptions } from "feathers-mongodb"; +import { Application } from "../../declarations"; +import { Id, Params } from "@feathersjs/feathers"; +import { AccountModel } from "../../interfaces/AccountModel"; +import { isValidObject, isValidArray } from "../../utils/Misc"; +import { Response } from "../../utils/response"; +import { messages } from "../../utils/messages"; +const trim = require("trim"); + +export class Friends extends Service { + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options); + + const client: Promise = app.get("mongoClient"); + + client.then((db) => { + this.Model = db.collection("friends"); + }); + } + + async create(data: any, params?: Params): Promise { + try { + const username = trim(data.username); + if (username != "" && typeof username != "undefined") { + const UserData = await super + .create(data) + .then((result: any) => { + let finalData = {}; + finalData = result == null ? {} : result; + return finalData; + }) + .catch((error: any) => { + return error; + }); + if (isValidObject(UserData) && !UserData.type) { + return Response.success(UserData); + } + } else { + return Response.error(messages.common_messages_record_added_failed); + } + } catch (error: any) { + return Response.error(messages.common_messages_error); + } + } +} diff --git a/Goobieverse/src/services/friends/friends.hooks.ts b/Goobieverse/src/services/friends/friends.hooks.ts new file mode 100644 index 00000000..3ad6ca64 --- /dev/null +++ b/Goobieverse/src/services/friends/friends.hooks.ts @@ -0,0 +1,34 @@ +import { HooksObject } from "@feathersjs/feathers"; +import { myHook } from "../../hooks/userData"; + +export default { + before: { + all: [], + find: [], + get: [], + create: [myHook], + update: [], + patch: [], + remove: [], + }, + + after: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, + + error: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, +} as HooksObject; diff --git a/Goobieverse/src/services/friends/friends.service.ts b/Goobieverse/src/services/friends/friends.service.ts new file mode 100644 index 00000000..593c5573 --- /dev/null +++ b/Goobieverse/src/services/friends/friends.service.ts @@ -0,0 +1,26 @@ +// Initializes the `friends` service on path `/friends` +import { ServiceAddons } from "@feathersjs/feathers"; +import { Application } from "../../declarations"; +import { Friends } from "./friends.class"; +import hooks from "./friends.hooks"; + +// Add this service to the service type index +declare module "../../declarations" { + interface ServiceTypes { + friends: Friends & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get("paginate"), + }; + + // Initialize our service with any options it requires + app.use("/friends", new Friends(options, app)); + + // Get our initialized service so that we can register hooks + const service = app.service("friends"); + + service.hooks(hooks); +} diff --git a/Goobieverse/src/services/index.ts b/Goobieverse/src/services/index.ts index bdb42649..81996991 100644 --- a/Goobieverse/src/services/index.ts +++ b/Goobieverse/src/services/index.ts @@ -1,7 +1,10 @@ import { Application } from '../declarations'; +import users from './users/users.service'; +import friends from './friends/friends.service'; //import metaverseInfo from './metaverse_info/metaverse_info.service'; // Don't remove this comment. It's needed to format import lines nicely. export default function (app: Application): void { - //app.configure(metaverseInfo); + app.configure(users); + app.configure(friends); } diff --git a/Goobieverse/src/services/users/users.class.ts b/Goobieverse/src/services/users/users.class.ts new file mode 100644 index 00000000..10bdc7b1 --- /dev/null +++ b/Goobieverse/src/services/users/users.class.ts @@ -0,0 +1,105 @@ +import { Db } from "mongodb"; +import { Service, MongoDBServiceOptions } from "feathers-mongodb"; +import { Application } from "../../declarations"; +import { Id, Params } from "@feathersjs/feathers"; +import { AccountModel } from "../../interfaces/AccountModel"; +import { isValidObject, isValidArray } from "../../utils/Misc"; +import { Response } from "../../utils/response"; +import { messages } from "../../utils/messages"; +import { GenUUID } from "../../utils/Misc"; +const trim = require("trim"); + +export class Users extends Service { + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options); + + const client: Promise = app.get("mongoClient"); + + client.then((db) => { + this.Model = db.collection("users"); + }); + } + + async create(data: AccountModel, params?: Params): Promise { + try { + const id = GenUUID(); + const username = trim(data.username); + const email = trim(data.email); + const password = trim(data.password); + if ( + id != "" && + typeof id != "undefined" && + username != "" && + typeof username != "undefined" && + email != "" && + typeof email != "undefined" && + password != "" && + typeof password != "undefined" + ) { + const newData = { ...data, id: id }; + const UserData = await super + .create(newData) + .then((result: any) => { + let finalData = {}; + finalData = result == null ? {} : result; + return finalData; + }) + .catch((error: any) => { + return error; + }); + if (isValidObject(UserData) && !UserData.type) { + return Response.success(UserData); + } + } else { + return Response.error(messages.common_messages_record_added_failed); + } + } catch (error: any) { + return Response.error(messages.common_messages_error); + } + } + + async find(params?: Params): Promise { + try { + const UserData = await super + .find() + .then((result: any) => { + let finalData = {}; + finalData = result == null ? {} : result; + return finalData; + }) + .catch((error: any) => { + return error; + }); + if (isValidArray(UserData.data)) { + return Response.success(UserData.data); + } else { + return Response.error(messages.common_messages_records_not_available); + } + } catch (error: any) { + return Response.error(messages.common_messages_error); + } + } + + async get(id: string, params?: Params): Promise { + try { + const UserData = await super + .get(id) + .then((result: any) => { + let finalData = {}; + finalData = result == null ? {} : result; + return finalData; + }) + .catch((error: any) => { + return error; + }); + if (isValidObject(UserData) && !UserData.type) { + return Response.success(UserData); + } else { + return Response.error(messages.common_messages_record_not_available); + } + } catch (error: any) { + return Response.error(messages.common_messages_error); + } + } +} diff --git a/Goobieverse/src/services/users/users.hooks.ts b/Goobieverse/src/services/users/users.hooks.ts new file mode 100644 index 00000000..7a6247c2 --- /dev/null +++ b/Goobieverse/src/services/users/users.hooks.ts @@ -0,0 +1,40 @@ +import { HooksObject } from "@feathersjs/feathers"; +import { myHook } from "../../hooks/userData"; +import * as feathersAuthentication from "@feathersjs/authentication"; +import * as local from "@feathersjs/authentication-local"; +import bcrypt from "bcrypt"; + +const { authenticate } = feathersAuthentication.hooks; +const { hashPassword, protect } = local.hooks; + +export default { + before: { + all: [], + find: [], + get: [], + create: [hashPassword("password")], + update: [], + patch: [], + remove: [], + }, + + after: { + all: [protect("password")], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, + + error: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, +} as HooksObject; diff --git a/Goobieverse/src/services/users/users.service.ts b/Goobieverse/src/services/users/users.service.ts new file mode 100644 index 00000000..31fec244 --- /dev/null +++ b/Goobieverse/src/services/users/users.service.ts @@ -0,0 +1,26 @@ +// Initializes the `users` service on path `/users` +import { ServiceAddons } from "@feathersjs/feathers"; +import { Application } from "../../declarations"; +import { Users } from "./users.class"; +import hooks from "./users.hooks"; + +// Add this service to the service type index +declare module "../../declarations" { + interface ServiceTypes { + users: Users & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get("paginate"), + }; + + // Initialize our service with any options it requires + app.use("/users", new Users(options, app)); + + // Get our initialized service so that we can register hooks + const service = app.service("users"); + + service.hooks(hooks); +} diff --git a/Goobieverse/src/utils/Misc.ts b/Goobieverse/src/utils/Misc.ts index e355e4ec..150b4cad 100644 --- a/Goobieverse/src/utils/Misc.ts +++ b/Goobieverse/src/utils/Misc.ts @@ -1,34 +1,46 @@ -import fs from 'fs'; -import http from 'http'; -import https from 'https'; -import os from 'os'; +import fs from "fs"; +import http from "http"; +import https from "https"; +import os from "os"; +import { v4 as uuidv4 } from "uuid"; // Return 'true' if the passed value is null or empty -export function IsNullOrEmpty(pVal: any): boolean {return (typeof(pVal) === 'undefined')|| (pVal === null)|| (typeof(pVal) === 'string' && String(pVal).length === 0);} +export function IsNullOrEmpty(pVal: any): boolean { + return ( + typeof pVal === "undefined" || + pVal === null || + (typeof pVal === "string" && String(pVal).length === 0) + ); +} + +export function GenUUID(): string { + return uuidv4(); +} // Return 'true' if the passed value is not null or empty -export function IsNotNullOrEmpty(pVal: any): boolean {return !IsNullOrEmpty(pVal);} +export function IsNotNullOrEmpty(pVal: any): boolean { + return !IsNullOrEmpty(pVal); +} // Utility routine that reads in JSON content from either an URL or a filename. // Returns the parsed JSON object or 'undefined' if any errors. export async function readInJSON(pFilenameOrURL: string): Promise { - let configBody:string; - if (pFilenameOrURL.startsWith('http://')) { + let configBody: string; + if (pFilenameOrURL.startsWith("http://")) { configBody = await httpRequest(pFilenameOrURL); - } - else { - if (pFilenameOrURL.startsWith('https://')) { + } else { + if (pFilenameOrURL.startsWith("https://")) { configBody = await httpsRequest(pFilenameOrURL); - } - else { + } else { try { // We should technically sanitize this filename but if one can change the environment // or config file variables, the app is already poned. - configBody = fs.readFileSync(pFilenameOrURL, 'utf-8'); - } - catch (err) { - configBody = ''; - console.debug(`readInJSON: failed read of user config file ${pFilenameOrURL}: ${err}`); + configBody = fs.readFileSync(pFilenameOrURL, "utf-8"); + } catch (err) { + configBody = ""; + console.debug( + `readInJSON: failed read of user config file ${pFilenameOrURL}: ${err}` + ); } } } @@ -38,81 +50,93 @@ export async function readInJSON(pFilenameOrURL: string): Promise { return undefined; } - // Do a simple https GET and return the response as a string export async function httpsRequest(pUrl: string): Promise { - return new Promise( ( resolve, reject) => { - https.get(pUrl, (resp:any) => { - let data = ''; - resp.on('data', (chunk: string) => { - data += chunk; - }); - resp.on('end', () => { - resolve(data); + return new Promise((resolve, reject) => { + https + .get(pUrl, (resp: any) => { + let data = ""; + resp.on("data", (chunk: string) => { + data += chunk; + }); + resp.on("end", () => { + resolve(data); + }); + }) + .on("error", (err: any) => { + reject(err); }); - }).on('error', (err: any) => { - reject(err); - }); }); } // Do a simple http GET and return the response as a string export async function httpRequest(pUrl: string): Promise { - return new Promise( ( resolve, reject) => { - http.get(pUrl, (resp:any) => { - let data = ''; - resp.on('data', (chunk: string) => { - data += chunk; - }); - resp.on('end', () => { - resolve(data); + return new Promise((resolve, reject) => { + http + .get(pUrl, (resp: any) => { + let data = ""; + resp.on("data", (chunk: string) => { + data += chunk; + }); + resp.on("end", () => { + resolve(data); + }); + }) + .on("error", (err: any) => { + reject(err); }); - }).on('error', (err: any) => { - reject(err); - }); }); } - let myExternalAddr: string; export async function getMyExternalIPAddress(): Promise { if (IsNotNullOrEmpty(myExternalAddr)) { return Promise.resolve(myExternalAddr); } - return new Promise(( resolve, reject) => { - httpsRequest('https://api.ipify.org').then( resp => { - myExternalAddr = resp; - resolve(myExternalAddr); - }).catch ( err => { - // Can't get it that way for some reason. Ask our interface - const networkInterfaces = os.networkInterfaces(); - // { 'lo1': [ info, info ], 'eth0': [ info, info ]} where 'info' could be v4 and v6 addr infos + return new Promise((resolve, reject) => { + httpsRequest("https://api.ipify.org") + .then((resp) => { + myExternalAddr = resp; + resolve(myExternalAddr); + }) + .catch((err) => { + // Can't get it that way for some reason. Ask our interface + const networkInterfaces = os.networkInterfaces(); + // { 'lo1': [ info, info ], 'eth0': [ info, info ]} where 'info' could be v4 and v6 addr infos - let addressv4 = ''; - let addressv6 = ''; + let addressv4 = ""; + let addressv6 = ""; - Object.keys(networkInterfaces).forEach(dev => { - networkInterfaces[dev]?.filter(details => { - if (details.family === 'IPv4' && details.internal === false) { - addressv4 = details.address; - } - if (details.family === 'IPv6' && details.internal === false) { - addressv6 = details.address; - } + Object.keys(networkInterfaces).forEach((dev) => { + networkInterfaces[dev]?.filter((details) => { + if (details.family === "IPv4" && details.internal === false) { + addressv4 = details.address; + } + if (details.family === "IPv6" && details.internal === false) { + addressv6 = details.address; + } + }); }); + let address = ""; + if (IsNullOrEmpty(addressv4)) { + address = addressv6; + } else { + address = addressv6; + } + + if (IsNullOrEmpty(address)) { + reject("No address found"); + } + myExternalAddr = address.toString(); + resolve(myExternalAddr); }); - let address = ''; - if (IsNullOrEmpty(addressv4)) { - address = addressv6; - }else{ - address = addressv6; - } - - if(IsNullOrEmpty(address)) { - reject('No address found'); - } - myExternalAddr = address.toString(); - resolve(myExternalAddr); - }); }); -} \ No newline at end of file +} + +export const isValidArray = (arr: []) => { + return arr && Array.isArray(arr) && arr.length > 0; +}; + +export const isValidObject = (obj: object) => { + return obj && Object.keys(obj).length > 0; +}; diff --git a/Goobieverse/src/utils/messages.ts b/Goobieverse/src/utils/messages.ts new file mode 100644 index 00000000..6e79b98a --- /dev/null +++ b/Goobieverse/src/utils/messages.ts @@ -0,0 +1,47 @@ +export const messages = { + common_messages_error: "Something went wrong please try again later.", + common_messages_record_available: "Record is available.", + common_messages_record_not_available: "Record is not available.", + common_messages_records_available: "Records are available.", + common_messages_records_not_available: "Records are not available.", + login_success: "Login Successfully.", + common_messages_record_added: "Record added successfully.", + common_messages_record_added_failed: "Failed to added record.", + common_messages_record_updated: "Record updated successfully.", + common_messages_record_deleted: "Record deleted successfully.", + common_messages_record_update_failed: "Failed to Record Update", + common_messages_record_deleted_failed: "Failed to Delete Record", + common_messages_user_invitation_failed: "Failed to send User Invitation", + common_messages_no_invitation_available: "No Invitation available", + common_messages_invitation_accepted: "Invitation approved Successfully.", + common_messages_invitation_rejected: "Invitation rejected Successfully.", + common_messages_user_invitation_success: + "Invitation Sent to User Successfully.", + common_messages_record_not_found: "record not found", + common_messages_user_not_found: "User not active or found", + in_correct_email_psw_error: + " The email/password you entered does not match. Please enter the correct email/password", + required_parameters_null_or_missing: + "Required parameters value null or missing.", + email_already_exist: "This Email Id already exists in our records.", + something_went_wrong: "Something went wrong.", + otp_sent_successfully: "OTP sent successfully.", + your_not_Authorize: "Your not Authorize to perform this action", + report_generated_success: "Report generated successfully.", + item_from_other_store: "Please add items from same store as in cart.", + db_error_message: + "Sorry, we can not proceed further, due to some technical issue", + access_for_organization_to_device_error: + "Your organization does not have access to this device", + access_for_organization_to_device_success: + "Your organization have created access to this device", + no_such_email_found: "No such type of Email found in our data", + valid_email_address_error: "Please Enter valid Email Address", + otp_send_success: "OTP sent successfully", + otp_correct_success: "OTP entered was correct", + otp_correct_error: "OTP entered was incorrect", + password_updated_success: "Your password has been updated successfully", + password_updated_error: "Failed to update your password", + token_period_is_expired: "Your token has been expired", + please_select_weightBridge: "Please select a Weight Bridge!!!", +}; diff --git a/Goobieverse/src/utils/response.ts b/Goobieverse/src/utils/response.ts new file mode 100644 index 00000000..cd71989f --- /dev/null +++ b/Goobieverse/src/utils/response.ts @@ -0,0 +1,8 @@ +export const Response = { + success: (data: any) => { + return { status: "success", data: data }; + }, + error: (message: string) => { + return { status: "failure", message: message }; + }, +}; diff --git a/Goobieverse/test/services/friends.test.ts b/Goobieverse/test/services/friends.test.ts new file mode 100644 index 00000000..fd36e7bd --- /dev/null +++ b/Goobieverse/test/services/friends.test.ts @@ -0,0 +1,8 @@ +import app from '../../src/app'; + +describe('\'friends\' service', () => { + it('registered the service', () => { + const service = app.service('friends'); + expect(service).toBeTruthy(); + }); +}); diff --git a/Goobieverse/test/services/users.test.ts b/Goobieverse/test/services/users.test.ts new file mode 100644 index 00000000..20d1ebb3 --- /dev/null +++ b/Goobieverse/test/services/users.test.ts @@ -0,0 +1,8 @@ +import app from '../../src/app'; + +describe('\'users\' service', () => { + it('registered the service', () => { + const service = app.service('users'); + expect(service).toBeTruthy(); + }); +}); From 72ba8ab9d6c98006d892a71f83c3ff5cfcb2cf29 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Thu, 30 Dec 2021 15:47:48 +0530 Subject: [PATCH 003/128] implemented get profiles endpoint --- Goobieverse/src/appconfig.ts | 21 +- Goobieverse/src/hooks/checkAccessToAccount.ts | 143 ++++++++ Goobieverse/src/hooks/requestFail.ts | 12 + Goobieverse/src/hooks/requestSuccess.ts | 11 + Goobieverse/src/interfaces/AccountModel.ts | 42 +++ Goobieverse/src/interfaces/DomainModel.ts | 38 ++ Goobieverse/src/interfaces/MetaverseInfo.ts | 1 - Goobieverse/src/interfaces/PlaceModel.ts | 29 ++ Goobieverse/src/routes/publicRoutes.ts | 3 +- Goobieverse/src/services/index.ts | 4 +- .../src/services/profiles/profiles.class.ts | 79 +++++ .../src/services/profiles/profiles.hooks.ts | 36 ++ .../src/services/profiles/profiles.service.ts | 26 ++ Goobieverse/src/utils/Perm.ts | 39 +++ Goobieverse/src/utils/Utils.ts | 331 ++++++++++++++++++ Goobieverse/src/utils/response.ts | 17 + Goobieverse/src/utils/sets/Availability.ts | 28 ++ Goobieverse/src/utils/sets/Maturity.ts | 35 ++ Goobieverse/src/utils/sets/Roles.ts | 27 ++ Goobieverse/src/utils/sets/Visibility.ts | 36 ++ Goobieverse/src/utils/vTypes.ts | 64 ++++ Goobieverse/test/services/profiles.test.ts | 8 + Goobieverse/test/services/users.test.ts | 8 + 23 files changed, 1033 insertions(+), 5 deletions(-) create mode 100644 Goobieverse/src/hooks/checkAccessToAccount.ts create mode 100644 Goobieverse/src/hooks/requestFail.ts create mode 100644 Goobieverse/src/hooks/requestSuccess.ts create mode 100755 Goobieverse/src/interfaces/AccountModel.ts create mode 100755 Goobieverse/src/interfaces/DomainModel.ts create mode 100755 Goobieverse/src/interfaces/PlaceModel.ts create mode 100644 Goobieverse/src/services/profiles/profiles.class.ts create mode 100644 Goobieverse/src/services/profiles/profiles.hooks.ts create mode 100644 Goobieverse/src/services/profiles/profiles.service.ts create mode 100644 Goobieverse/src/utils/Perm.ts create mode 100644 Goobieverse/src/utils/Utils.ts create mode 100644 Goobieverse/src/utils/response.ts create mode 100644 Goobieverse/src/utils/sets/Availability.ts create mode 100644 Goobieverse/src/utils/sets/Maturity.ts create mode 100644 Goobieverse/src/utils/sets/Roles.ts create mode 100644 Goobieverse/src/utils/sets/Visibility.ts create mode 100755 Goobieverse/src/utils/vTypes.ts create mode 100644 Goobieverse/test/services/profiles.test.ts create mode 100644 Goobieverse/test/services/users.test.ts diff --git a/Goobieverse/src/appconfig.ts b/Goobieverse/src/appconfig.ts index bf90a4c6..427ef772 100644 --- a/Goobieverse/src/appconfig.ts +++ b/Goobieverse/src/appconfig.ts @@ -40,6 +40,17 @@ const metaverseServer = { listen_host: process.env.LISTEN_HOST ??'0.0.0.0', listen_port: process.env.LISTEN_PORT ?? 9400, metaverseInfoAdditionFile: process.env.METAVERSE_INFO_File ?? '', + session_timeout_minutes: 5, + heartbeat_seconds_until_offline: 5 * 60, // seconds until non-heartbeating user is offline + domain_seconds_until_offline: 10 * 60, // seconds until non-heartbeating domain is offline + domain_seconds_check_if_online: 2 * 60, // how often to check if a domain is online + handshake_request_expiration_minutes: 1, // minutes that a handshake friend request is active + connection_request_expiration_minutes: 60 * 24 * 4, // 4 days + friend_request_expiration_minutes: 60 * 24 * 4, // 4 days + + place_current_timeout_minutes: 5, // minutes until current place info is stale + place_inactive_timeout_minutes: 60, // minutes until place is considered inactive + place_check_last_activity_seconds: (3*60)-5, // seconds between checks for Place lastActivity updates }; /** @@ -82,6 +93,7 @@ const metaverse = { metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? '', // if empty, set to self defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? '', // if empty, set to self dashboardUrl: process.env.DASHBOARD_URL + }; if (IsNullOrEmpty(metaverse.metaverseServerUrl) || IsNullOrEmpty(metaverse.defaultIceServerUrl)) { @@ -97,6 +109,12 @@ if (IsNullOrEmpty(metaverse.metaverseServerUrl) || IsNullOrEmpty(metaverse.defau } +const dbCollections = { + domains : 'domains', + accounts : 'accounts', + places : 'places', + tokens : 'tokens' +}; /** @@ -107,7 +125,8 @@ const config = { authentication, server, metaverse, - metaverseServer + metaverseServer, + dbCollections }; export default config; \ No newline at end of file diff --git a/Goobieverse/src/hooks/checkAccessToAccount.ts b/Goobieverse/src/hooks/checkAccessToAccount.ts new file mode 100644 index 00000000..64644f62 --- /dev/null +++ b/Goobieverse/src/hooks/checkAccessToAccount.ts @@ -0,0 +1,143 @@ +import { AccountModel } from './../interfaces/AccountModel'; +import { HookContext } from '@feathersjs/feathers'; +import { IsNotNullOrEmpty } from '../utils/Misc'; +import { Perm } from '../utils/Perm'; +import config from '../appconfig'; +import {HTTPStatusCode} from '../utils/response'; +import {Availability} from '../utils/sets/Availability'; +import { SArray } from '../utils/vTypes'; + +export default (pRequiredAccess: Perm[]) => { + return async (context: HookContext): Promise => { + const db = await context.app.get('mongoClient'); + + const accounts = await db.collection(config.dbCollections.accounts).find({id:context.id}).toArray(); + let canAccess = false; + + if (IsNotNullOrEmpty(accounts)) { + const pTargetEntity = accounts[0]; + for (const perm of pRequiredAccess) { + switch (perm) { + case Perm.ALL: + canAccess = true; + break; + case Perm.PUBLIC: + // The target entity is publicly visible + // Mostly AccountEntities that must have an 'availability' field + if (pTargetEntity.hasOwnProperty('availability')) { + if ((pTargetEntity as AccountModel).availability.includes(Availability.ALL)) { + canAccess = true; + } + } + break; + /*case Perm.DOMAIN: + // requestor is a domain and it's account is the domain's sponsoring account + if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.DOMAIN)) { + if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { + canAccess = pAuthToken.accountId === (pTargetEntity as any).sponsorAccountId; + } + else { + // Super special case where domain doesn't have a sponsor but has an api_key. + // In this case, the API_KEY is put in the accountId field of the DOMAIN scoped AuthToken + if (pTargetEntity.hasOwnProperty('apiKey')) { + canAccess = pAuthToken.accountId === (pTargetEntity as any).apiKey; + } + } + } + break; + case Perm.OWNER: + // The requestor wants to be the same account as the target entity + if (pAuthToken && pTargetEntity.hasOwnProperty('id')) { + canAccess = pAuthToken.accountId === (pTargetEntity as AccountEntity).id; + } + if (!canAccess && pTargetEntity.hasOwnProperty('accountId')) { + canAccess = pAuthToken.accountId === (pTargetEntity as any).accountId; + } + break; + case Perm.FRIEND: + // The requestor is a 'friend' of the target entity + if (pAuthToken && pTargetEntity.hasOwnProperty('friends')) { + const targetFriends: string[] = (pTargetEntity as AccountEntity).friends; + if (targetFriends) { + requestingAccount = requestingAccount ?? await Accounts.getAccountWithId(pAuthToken.accountId); + canAccess = SArray.hasNoCase(targetFriends, requestingAccount.username); + } + } + break; + case Perm.CONNECTION: + // The requestor is a 'connection' of the target entity + if (pAuthToken && pTargetEntity.hasOwnProperty('connections')) { + const targetConnections: string[] = (pTargetEntity as AccountEntity).connections; + if (targetConnections) { + requestingAccount = requestingAccount ?? await Accounts.getAccountWithId(pAuthToken.accountId); + canAccess = SArray.hasNoCase(targetConnections, requestingAccount.username); + } + } + break; + case Perm.ADMIN: + if (pAuthToken && Tokens.isSpecialAdminToken(pAuthToken)) { + Logger.cdebug('field-setting', `checkAccessToEntity: isSpecialAdminToken`); + canAccess = true; + } + else { + // If the authToken is an account, has access if admin + if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.OWNER)) { + Logger.cdebug('field-setting', `checkAccessToEntity: admin. auth.AccountId=${pAuthToken.accountId}`); + requestingAccount = requestingAccount ?? await Accounts.getAccountWithId(pAuthToken.accountId); + canAccess = Accounts.isAdmin(requestingAccount); + } + } + break; + case Perm.SPONSOR: + // Requestor is a regular account and is the sponsor of the domain + if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.OWNER)) { + if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { + Logger.cdebug('field-setting', `checkAccessToEntity: authToken is domain. auth.AccountId=${pAuthToken.accountId}, sponsor=${(pTargetEntity as any).sponsorAccountId}`); + canAccess = pAuthToken.accountId === (pTargetEntity as DomainEntity).sponsorAccountId; + } + } + break; + case Perm.MANAGER: + // See if requesting account is in the list of managers of this entity + if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.OWNER)) { + if (pTargetEntity.hasOwnProperty('managers')) { + requestingAccount = requestingAccount ?? await Accounts.getAccountWithId(pAuthToken.accountId); + if (requestingAccount) { + const managers: string[] = (pTargetEntity as DomainEntity).managers; + // Logger.debug(`Perm.MANAGER: managers=${JSON.stringify(managers)}, target=${requestingAccount.username}`); + if (managers && managers.includes(requestingAccount.username.toLowerCase())) { + canAccess = true; + } + } + } + } + break; + case Perm.DOMAINACCESS: + // Target entity has a domain reference and verify the requestor is able to reference that domain + if (pAuthToken && pTargetEntity.hasOwnProperty('domainId')) { + const aDomain = await Domains.getDomainWithId((pTargetEntity as any).domainId); + if (aDomain) { + canAccess = aDomain.sponsorAccountId === pAuthToken.accountId; + } + } + break;*/ + default: + canAccess = false; + break; + } + // If some permission allows access, we are done + + } + }else{ + context.statusCode = HTTPStatusCode.NotFound; + throw new Error('Target account not found'); + } + + if (!canAccess){ + context.statusCode = HTTPStatusCode.Unauthorized; + throw new Error('Unauthorized'); + } + + return context; + }; +}; \ No newline at end of file diff --git a/Goobieverse/src/hooks/requestFail.ts b/Goobieverse/src/hooks/requestFail.ts new file mode 100644 index 00000000..258c34d0 --- /dev/null +++ b/Goobieverse/src/hooks/requestFail.ts @@ -0,0 +1,12 @@ +import { HookContext } from '@feathersjs/feathers'; +import { IsNotNullOrEmpty } from '../utils/Misc'; +import { Perm } from '../utils/Perm'; +import { Response } from '../utils/response'; + +export default () => { + return async (context: HookContext): Promise => { + + context.result = Response.error(context?.error?.message); + return context; + }; +}; \ No newline at end of file diff --git a/Goobieverse/src/hooks/requestSuccess.ts b/Goobieverse/src/hooks/requestSuccess.ts new file mode 100644 index 00000000..0cc54059 --- /dev/null +++ b/Goobieverse/src/hooks/requestSuccess.ts @@ -0,0 +1,11 @@ +import { HookContext } from '@feathersjs/feathers'; +import { IsNotNullOrEmpty } from '../utils/Misc'; +import { Perm } from '../utils/Perm'; +import { Response } from '../utils/response'; + +export default () => { + return async (context: HookContext): Promise => { + context.result = Response.success(context.result); + return context; + }; +}; \ No newline at end of file diff --git a/Goobieverse/src/interfaces/AccountModel.ts b/Goobieverse/src/interfaces/AccountModel.ts new file mode 100755 index 00000000..9313fa99 --- /dev/null +++ b/Goobieverse/src/interfaces/AccountModel.ts @@ -0,0 +1,42 @@ + +export interface AccountModel { + id: string, + username: string, + email: string, + accountSettings: string, // JSON of client settings + imagesHero: string, + imagesThumbnail: string, + imagesTiny: string, + + locationConnected: boolean + locationPath: string, // "/floatX,floatY,floatZ/floatX,floatY,floatZ,floatW" + locationPlaceId: string, // uuid of place + locationDomainId: string, // uuid of domain located in + locationNetworkAddress: string, + locationNetworkPort: number, + locationNodeId: string, // sessionId + availability: string[], // contains 'none', 'friends', 'connections', 'all' + + connections: string[], + friends: string[], + locker: any, // JSON blob stored for user from server + profileDetail: any, // JSON blob stored for user from server + + // User authentication + passwordHash: string, + passwordSalt: string, + sessionPublicKey: string, // PEM public key generated for this session + accountEmailVerified: boolean, // default true if not present + + // Old stuff + xmppPassword: string, + discourseApiKey: string, + walletId: string, + + // Admin stuff + // ALWAYS USE functions in Roles class to manipulate this list of roles + roles: string[], // account roles (like 'admin') + IPAddrOfCreator: string, // IP address that created this account + whenCreated: Date, // date of account creation + timeOfLastHeartbeat: Date // when we last heard from this user +} \ No newline at end of file diff --git a/Goobieverse/src/interfaces/DomainModel.ts b/Goobieverse/src/interfaces/DomainModel.ts new file mode 100755 index 00000000..8720af5b --- /dev/null +++ b/Goobieverse/src/interfaces/DomainModel.ts @@ -0,0 +1,38 @@ +export interface DomainModel{ + id: string, // globally unique domain identifier + name: string, // domain name/label + visibility: string, // visibility of this entry in general domain lists + publicKey: string, // DomainServers's public key in multi-line PEM format + apiKey: string, // Access key if a temp domain + sponsorAccountId: string, // The account that gave this domain an access key + iceServerAddr: string,// IP address of ICE server being used by this domain + + // Information that comes in via heartbeat + version: string, // DomainServer's build version (like "K3") + protocol: string, // Protocol version + networkAddr: string, // reported network address + networkPort: string, // reported network address + networkingMode: string, // one of "full", "ip", "disabled" + restricted: boolean, // 'true' if restricted to users with accounts + numUsers: number, // total number of logged in users + anonUsers: number, // number of anonymous users + hostnames: string[], // User segmentation + + // More information that's metadata that's passed in PUT domain + capacity: number, // Total possible users + description: string, // Short description of domain + contactInfo: string, // domain contact information + thumbnail: string, // thumbnail image of domain + images: string[], // collection of images for the domain + maturity: string, // Maturity rating + restriction: string, // Access restrictions ("open") + managers: string[], // Usernames of people who are domain admins + tags: string[], // Categories for describing the domain + + // admin stuff + iPAddrOfFirstContact: string, // IP address that registered this domain + whenCreated: Date, // What the variable name says + active: boolean, // domain is heartbeating + timeOfLastHeartbeat: Date, // time of last heartbeat + lastSenderKey: string, // a key identifying the sender +} \ No newline at end of file diff --git a/Goobieverse/src/interfaces/MetaverseInfo.ts b/Goobieverse/src/interfaces/MetaverseInfo.ts index 741123f2..4552ccb1 100644 --- a/Goobieverse/src/interfaces/MetaverseInfo.ts +++ b/Goobieverse/src/interfaces/MetaverseInfo.ts @@ -1,4 +1,3 @@ - export interface MetaverseInfoModel{ metaverse_name:string, metaverse_nick_name:string, diff --git a/Goobieverse/src/interfaces/PlaceModel.ts b/Goobieverse/src/interfaces/PlaceModel.ts new file mode 100755 index 00000000..2793916e --- /dev/null +++ b/Goobieverse/src/interfaces/PlaceModel.ts @@ -0,0 +1,29 @@ +export interface PlaceModel{ + id: string, // globally unique place identifier + name: string, // Human friendly name of the place + displayName: string, // Human friendly name of the place + description: string, // Human friendly description of the place + visibility: string, // visibility of this Place in general Place lists + maturity: string, // maturity level of the place (see Sets/Maturity.ts) + tags: string[], // tags defining the string content + domainId: string, // domain the place is in + managers: string[], // Usernames of people who are domain admins + path: string, // address within the domain: "optional-domain/x,y,z/x,y,z,x" + thumbnail: string, // thumbnail for place + images: string[], // images for the place + + // A Place can have a beacon that updates current state and information + // If current information is not supplied, attendance defaults to domain's + currentAttendance: number // current attendance at the Place + currentImages: string[] // images at the session + currentInfo: string // JSON information about the session + currentLastUpdateTime: Date // time that the last session information was updated + currentAPIKeyTokenId: string // API key for updating the session information + + // admin stuff + iPAddrOfFirstContact: string, // IP address that registered this place + whenCreated: Date, // What the variable name says + // 'lastActivity' is computed by Places.initPlaces and used for aliveness checks + lastActivity: Date, // newest of currentLastUpdateTime and Domain.timeOfLastHeartbeat +} + diff --git a/Goobieverse/src/routes/publicRoutes.ts b/Goobieverse/src/routes/publicRoutes.ts index 4a1df329..6d2f24f5 100644 --- a/Goobieverse/src/routes/publicRoutes.ts +++ b/Goobieverse/src/routes/publicRoutes.ts @@ -1,8 +1,9 @@ import express from 'express'; -import { PublicRoutesController} from '../controllers/PublicRoutesController'; +import { PublicRoutesController } from '../controllers/PublicRoutesController'; const publicRoutes = express.Router(); publicRoutes.get('/metaverse_info',PublicRoutesController().metaverseInfo); export { publicRoutes}; + \ No newline at end of file diff --git a/Goobieverse/src/services/index.ts b/Goobieverse/src/services/index.ts index bdb42649..478ff2ed 100644 --- a/Goobieverse/src/services/index.ts +++ b/Goobieverse/src/services/index.ts @@ -1,7 +1,7 @@ import { Application } from '../declarations'; -//import metaverseInfo from './metaverse_info/metaverse_info.service'; +import profiles from './profiles/profiles.service'; // Don't remove this comment. It's needed to format import lines nicely. export default function (app: Application): void { - //app.configure(metaverseInfo); + app.configure(profiles); } diff --git a/Goobieverse/src/services/profiles/profiles.class.ts b/Goobieverse/src/services/profiles/profiles.class.ts new file mode 100644 index 00000000..8f68bf17 --- /dev/null +++ b/Goobieverse/src/services/profiles/profiles.class.ts @@ -0,0 +1,79 @@ +import { DomainModel } from './../../interfaces/DomainModel'; +import { AccountModel } from '../../interfaces/AccountModel'; +import config from '../../appconfig'; +import { Availability } from '../../utils/sets/Availability'; +import { Db } from 'mongodb'; +import { Params, Id } from '@feathersjs/feathers'; +import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; +import { Application } from '../../declarations'; +import { buildAccountProfile } from '../../utils/Utils'; +import { IsNotNullOrEmpty } from '../../utils/Misc'; + +export class Profiles extends Service { + //eslint-disable-next-line @typescript-eslint/no-unused-vars + app: Application; + constructor(options: Partial, app: Application) { + super(options); + this.app = app; + + const client: Promise = app.get('mongoClient'); + client.then((database) => { + this.Model = database.collection(config.dbCollections.accounts); + }); + } + + async find(params?: Params): Promise { + const db = await this.app.get('mongoClient'); + const perPage = parseInt(params?.query?.per_page) || 10; + const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; + + const accounts = await super.find({ + query: { + $or: [{ availability: undefined }, { availability: Availability.ALL }], + $skip: skip, + $limit: perPage, + }, + }); + + const domainIds = (accounts as Array) + ?.map((item) => item.locationDomainId) + .filter( + (value, index, self) => + self.indexOf(value) === index && value !== undefined + ); + + const domains: Array = await db + .collection(config.dbCollections.domains) + .find({ query:{id: { $in: domainIds }}}) + .toArray(); + + const profiles: Array = []; + + (accounts as Array)?.forEach(async (element) => { + let domainModel: DomainModel | undefined; + for (const domain of domains) { + if (domain && domain.id === element.locationDomainId) { + domainModel = domain; + break; + } + } + profiles.push(await buildAccountProfile(element, domainModel)); + }); + return Promise.resolve({ profiles }); + } + + async get(id: Id, params: Params): Promise { + const db = await this.app.get('mongoClient'); + const accounts = await super.find({query:{id:id}}); + if(IsNotNullOrEmpty(accounts)){ + const account = (accounts as Array)[0]; + const domains: Array = await db.collection(config.dbCollections.domains).find({ id: { $eq: account.locationDomainId } }).toArray(); + let domainModel: any; + if(IsNotNullOrEmpty(domains)){domainModel = domains[0];} + const profile = await buildAccountProfile(account, domainModel); + return Promise.resolve({ profile }); + }else{ + return Promise.resolve({}); + } + } +} diff --git a/Goobieverse/src/services/profiles/profiles.hooks.ts b/Goobieverse/src/services/profiles/profiles.hooks.ts new file mode 100644 index 00000000..fd8a4416 --- /dev/null +++ b/Goobieverse/src/services/profiles/profiles.hooks.ts @@ -0,0 +1,36 @@ +import { disallow } from 'feathers-hooks-common'; +import checkAccessToAccount from '../../hooks/checkAccessToAccount'; +import requestFail from '../../hooks/requestFail'; +import requestSuccess from '../../hooks/requestSuccess'; +import {Perm} from '../../utils/Perm'; +export default { + before: { + all: [], + find: [], + get: [checkAccessToAccount([Perm.PUBLIC,Perm.OWNER,Perm.ADMIN])], + create: [disallow()], + update: [disallow()], + patch: [disallow()], + remove: [disallow()] + }, + + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, + + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } +}; diff --git a/Goobieverse/src/services/profiles/profiles.service.ts b/Goobieverse/src/services/profiles/profiles.service.ts new file mode 100644 index 00000000..7ef3a9db --- /dev/null +++ b/Goobieverse/src/services/profiles/profiles.service.ts @@ -0,0 +1,26 @@ +// Initializes the `profiles` service on path `/profiles` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { Profiles } from './profiles.class'; +import hooks from './profiles.hooks'; + +// Add this service to the service type index +declare module '../../declarations' { + interface ServiceTypes { + 'profiles': Profiles & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + // paginate: app.get('paginate') + }; + + // Initialize our service with any options it requires + app.use('/profiles', new Profiles(options, app)); + + // Get our initialized service so that we can register hooks + const service = app.service('profiles'); + + service.hooks(hooks); +} diff --git a/Goobieverse/src/utils/Perm.ts b/Goobieverse/src/utils/Perm.ts new file mode 100644 index 00000000..bff36d70 --- /dev/null +++ b/Goobieverse/src/utils/Perm.ts @@ -0,0 +1,39 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// Permission codes: +// 'all': any one +// 'domain': the requesting authToken is for a domain and the sponsor account matches +// 'owner': the requesting account is the owner of the target account +// 'friend': the requesting account is a friend of the target account +// 'connection': the requesting account is a connection of the target account +// 'admin': the requesting account has 'admin' privilages +// 'sponsor': the requesting account is the sponsor of the traget domain +// 'domainaccess': the target entity has a domain and requesting account must be sponsor +export class Perm { + public static NONE = 'none'; + public static ALL = 'all'; + public static PUBLIC = 'public'; // target account is publicly visible + public static DOMAIN = 'domain'; // check against .sponsorId + public static OWNER = 'owner'; // check against .id or .accountId + public static FRIEND = 'friend'; // check member of .friends + public static CONNECTION = 'connection';// check member of .connections + public static ADMIN = 'admin'; // check if isAdmin + public static SPONSOR = 'sponsor'; // check against .sponsorAccountId + public static MANAGER = 'manager'; // check against .managers + public static DOMAINACCESS = 'domainaccess'; // check that entity's domain has access +} + diff --git a/Goobieverse/src/utils/Utils.ts b/Goobieverse/src/utils/Utils.ts new file mode 100644 index 00000000..892bd923 --- /dev/null +++ b/Goobieverse/src/utils/Utils.ts @@ -0,0 +1,331 @@ +import { DomainModel } from '../interfaces/DomainModel'; +import { PlaceModel } from '../interfaces/PlaceModel'; +import { Visibility } from './sets/Visibility'; +import { Maturity } from './sets/Maturity'; +import { SArray,VKeyedCollection } from './vTypes'; +import { Roles } from './sets/Roles'; +import { AccountModel } from '../interfaces/AccountModel'; +import { IsNotNullOrEmpty,IsNullOrEmpty } from './Misc'; +import config from '../appconfig'; + + +// The legacy interface returns public keys as a stripped PEM key. +// "stripped" in that the bounding "BEGIN" and "END" lines have been removed. +// This routine returns a stripped key string from a properly PEM formatted public key string. +export function createSimplifiedPublicKey(pPubKey: string): string { + let keyLines: string[] = []; + if (pPubKey) { + keyLines = pPubKey.split('\n'); + keyLines.shift(); // Remove the "BEGIN" first line + while (keyLines.length > 1 + && ( keyLines[keyLines.length-1].length < 1 || keyLines[keyLines.length-1].includes('END PUBLIC KEY') ) ) { + keyLines.pop(); // Remove the "END" last line + } + } + return keyLines.join(''); // Combine all lines into one long string +} + + +// The returned location info has many options depending on whether +// the account has set location and/or has an associated domain. +// Return a structure that represents the target account's domain + +export async function buildLocationInfo(pAcct: AccountModel,aDomain?: DomainModel): Promise { + let ret: any = {}; + if (pAcct.locationDomainId) { + if (IsNotNullOrEmpty(aDomain) && aDomain) { + ret = { + root: { + domain: await buildDomainInfo(aDomain), + }, + path: pAcct.locationPath, + }; + } else { + // The domain doesn't have an ID + ret = { + root: { + domain: { + network_address: pAcct.locationNetworkAddress, + network_port: pAcct.locationNetworkPort, + }, + }, + }; + } + } + ret.node_id = pAcct.locationNodeId; + ret.online = isOnline(pAcct); + return ret; +} + +// A smaller, top-level domain info block +export async function buildDomainInfo(pDomain: DomainModel): Promise { + return { + id: pDomain.id, + domainId: pDomain.id, + name: pDomain.name, + visibility: pDomain.visibility ?? Visibility.OPEN, + capacity: pDomain.capacity, + sponsorAccountId: pDomain.sponsorAccountId, + label: pDomain.name, + network_address: pDomain.networkAddr, + network_port: pDomain.networkPort, + ice_server_address: pDomain.iceServerAddr, + version: pDomain.version, + protocol_version: pDomain.protocol, + active: pDomain.active ?? false, + time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), + num_users: pDomain.numUsers, + }; +} + +// Return a structure with the usual domain information. +export async function buildDomainInfoV1(pDomain: DomainModel): Promise { + return { + domainId: pDomain.id, + id: pDomain.id, // legacy + name: pDomain.name, + visibility: pDomain.visibility ?? Visibility.OPEN, + world_name: pDomain.name, // legacy + label: pDomain.name, // legacy + public_key: pDomain.publicKey + ? createSimplifiedPublicKey(pDomain.publicKey) + : undefined, + owner_places: await buildPlacesForDomain(pDomain), + sponsor_account_id: pDomain.sponsorAccountId, + ice_server_address: pDomain.iceServerAddr, + version: pDomain.version, + protocol_version: pDomain.protocol, + network_address: pDomain.networkAddr, + network_port: pDomain.networkPort, + automatic_networking: pDomain.networkingMode, + restricted: pDomain.restricted, + num_users: pDomain.numUsers, + anon_users: pDomain.anonUsers, + total_users: pDomain.numUsers, + capacity: pDomain.capacity, + description: pDomain.description, + maturity: pDomain.maturity ?? Maturity.UNRATED, + restriction: pDomain.restriction, + managers: pDomain.managers, + tags: pDomain.tags, + meta: { + capacity: pDomain.capacity, + contact_info: pDomain.contactInfo, + description: pDomain.description, + images: pDomain.images, + managers: pDomain.managers, + restriction: pDomain.restriction, + tags: pDomain.tags, + thumbnail: pDomain.thumbnail, + world_name: pDomain.name, + }, + users: { + num_anon_users: pDomain.anonUsers, + num_users: pDomain.numUsers, + user_hostnames: pDomain.hostnames, + }, + time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), + last_sender_key: pDomain.lastSenderKey, + addr_of_first_contact: pDomain.iPAddrOfFirstContact, + when_domain_entry_created: pDomain.whenCreated?.toISOString(), + when_domain_entry_created_s: pDomain.whenCreated?.getTime().toString(), + }; +} + +// Return the limited "user" info.. used by /api/v1/users +export async function buildUserInfo(pAccount: AccountModel): Promise { + return { + accountId: pAccount.id, + id: pAccount.id, + username: pAccount.username, + images: await buildImageInfo(pAccount), + location: await buildLocationInfo(pAccount), + }; +} + +export async function buildImageInfo(pAccount: AccountModel): Promise { + const ret:VKeyedCollection = {}; + + if (pAccount.imagesTiny) ret.tiny = pAccount.imagesTiny; + if (pAccount.imagesHero) ret.hero = pAccount.imagesHero; + if (pAccount.imagesThumbnail) ret.thumbnail = pAccount.imagesThumbnail; + return ret; +} + +// Return the block of account information. +// Used by several of the requests to return the complete account information. +export async function buildAccountInfo( + pAccount: AccountModel +): Promise { + return { + accountId: pAccount.id, + id: pAccount.id, + username: pAccount.username, + email: pAccount.email, + administrator: isAdmin(pAccount), + enabled: isEnabled(pAccount), + roles: pAccount.roles, + availability: pAccount.availability, + public_key: createSimplifiedPublicKey(pAccount.sessionPublicKey), + images: { + hero: pAccount.imagesHero, + tiny: pAccount.imagesTiny, + thumbnail: pAccount.imagesThumbnail, + }, + profile_detail: pAccount.profileDetail, + location: await buildLocationInfo(pAccount), + friends: pAccount.friends, + connections: pAccount.connections, + when_account_created: pAccount.whenCreated?.toISOString(), + when_account_created_s: pAccount.whenCreated?.getTime().toString(), + time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat + ?.getTime() + .toString(), + }; +} + +// getter property that is 'true' if the user is a grid administrator +function isAdmin(pAcct: AccountModel): boolean { + return SArray.has(pAcct.roles, Roles.ADMIN); +} +// Any logic to test of account is active +// Currently checks if account email is verified or is legacy +// account (no 'accountEmailVerified' variable) +function isEnabled(pAcct: AccountModel): boolean { + return pAcct.accountEmailVerified ?? true; +} + +// Return the block of account information used as the account 'profile'. +// Anyone can fetch a profile (if 'availability' is 'any') so not all info is returned +export async function buildAccountProfile( + pAccount: AccountModel, + aDomain?: DomainModel +): Promise { + return { + accountId: pAccount.id, + id: pAccount.id, + username: pAccount.username, + images: { + hero: pAccount.imagesHero, + tiny: pAccount.imagesTiny, + thumbnail: pAccount.imagesThumbnail, + }, + profile_detail: pAccount.profileDetail, + location: await buildLocationInfo(pAccount,aDomain), + when_account_created: pAccount.whenCreated?.toISOString(), + when_account_created_s: pAccount.whenCreated?.getTime().toString(), + time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat + ?.getTime() + .toString(), + }; +} + +// Return an object with the formatted place information +// Pass the PlaceModel and the place's domain if known. +export async function buildPlaceInfo( + pPlace: PlaceModel, + pDomain?: DomainModel +): Promise { + const ret = await buildPlaceInfoSmall(pPlace, pDomain); + + // if the place points to a domain, add that information also + if (IsNotNullOrEmpty(pDomain) && pDomain) { + ret.domain = await buildDomainInfo(pDomain); + } + return ret; +} +// Return the basic information block for a Place +export async function buildPlaceInfoSmall( + pPlace: PlaceModel, + pDomain?: DomainModel +): Promise { + const ret = { + placeId: pPlace.id, + id: pPlace.id, + name: pPlace.name, + displayName: pPlace.displayName, + visibility: pPlace.visibility ?? Visibility.OPEN, + address: getAddressString(pPlace,pDomain), + path: pPlace.path, + description: pPlace.description, + maturity: pPlace.maturity ?? Maturity.UNRATED, + tags: pPlace.tags, + managers: await getManagers(pPlace,pDomain), + thumbnail: pPlace.thumbnail, + images: pPlace.images, + current_attendance: pPlace.currentAttendance ?? 0, + current_images: pPlace.currentImages, + current_info: pPlace.currentInfo, + current_last_update_time: pPlace.currentLastUpdateTime?.toISOString(), + current_last_update_time_s: pPlace.currentLastUpdateTime + ?.getTime() + .toString(), + last_activity_update: pPlace.lastActivity?.toISOString(), + last_activity_update_s: pPlace.lastActivity?.getTime().toString(), + }; + return ret; +} + + +function getAddressString(pPlace: PlaceModel,aDomain?: DomainModel): string { + // Compute and return the string for the Places's address. + // The address is of the form "optional-domain/x,y,z/x,y,z,w". + // If the domain is missing, the domain-server's network address is added + let addr = pPlace.path ?? '/0,0,0/0,0,0,1'; + + // If no domain/address specified in path, build addr using reported domain IP/port + const pieces = addr.split('/'); + if (pieces[0].length === 0) { + if (IsNotNullOrEmpty(aDomain) && aDomain) { + if (IsNotNullOrEmpty(aDomain.networkAddr)) { + let domainAddr = aDomain.networkAddr; + if (IsNotNullOrEmpty(aDomain.networkPort)) { + domainAddr = aDomain.networkAddr + ':' + aDomain.networkPort; + } + addr = domainAddr + addr; + } + } + } + return addr; +} + +async function getManagers(pPlace: PlaceModel,aDomain?: DomainModel): Promise { + if(IsNullOrEmpty(pPlace.managers)) { + pPlace.managers = []; + //uncomment after complete Accounts Places api + /* + if (aDomain) { + const aAccount = await Accounts.getAccountWithId(aDomain.sponsorAccountId); + if (aAccount) { + pPlace.managers = [ aAccount.username ]; + } + } + await Places.updateEntityFields(pPlace, { 'managers': pPlace.managers }) + */ + } + return pPlace.managers; +} + +// Return an array of Places names that are associated with the passed domain +export async function buildPlacesForDomain(pDomain: DomainModel): Promise { + const ret: any[] = []; + //uncomment after complete Places api + /* for await (const aPlace of Places.enumerateAsync(new GenericFilter({ domainId: pDomain.id }))) { + ret.push(await buildPlaceInfoSmall(aPlace, pDomain)); + }*/ + return ret; +} + +function isOnline(pAcct: AccountModel): boolean { + if (pAcct && pAcct.timeOfLastHeartbeat) { + return ( + Date.now().valueOf() - pAcct.timeOfLastHeartbeat.valueOf() < + config.metaverseServer.heartbeat_seconds_until_offline * 1000 + ); + } + return false; +} diff --git a/Goobieverse/src/utils/response.ts b/Goobieverse/src/utils/response.ts new file mode 100644 index 00000000..58ebc212 --- /dev/null +++ b/Goobieverse/src/utils/response.ts @@ -0,0 +1,17 @@ +export const Response = { + success : (data: any,additionalFields?:any) => { + return {status: 'success',data: data,...additionalFields}; + }, + error:(message: string,additionalFields?:any) => { + return { status: 'failure', message: message,...additionalFields}; + } +}; + +export enum HTTPStatusCode { + OK = 200, + Found = 302, + BadRequest = 400, + Unauthorized = 401, + Forbidden = 403, + NotFound = 404, +} \ No newline at end of file diff --git a/Goobieverse/src/utils/sets/Availability.ts b/Goobieverse/src/utils/sets/Availability.ts new file mode 100644 index 00000000..94ede550 --- /dev/null +++ b/Goobieverse/src/utils/sets/Availability.ts @@ -0,0 +1,28 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +export class Availability { + public static NONE = 'none'; // no one can see me + public static FRIENDS = 'friends'; // available to friends + public static CONNECTIONS= 'connections'; // available to connections + public static ALL = 'all'; // available to all + + // See if the passed availability code is a known availability token + static async KnownAvailability(pAvailability: string): Promise { + return [ Availability.NONE,Availability.FRIENDS,Availability.CONNECTIONS,Availability.ALL].includes(pAvailability); + } +} + diff --git a/Goobieverse/src/utils/sets/Maturity.ts b/Goobieverse/src/utils/sets/Maturity.ts new file mode 100644 index 00000000..8668f40e --- /dev/null +++ b/Goobieverse/src/utils/sets/Maturity.ts @@ -0,0 +1,35 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +export class Maturity { + public static UNRATED = 'unrated'; + public static EVERYONE = 'everyone'; + public static TEEN = 'teen'; + public static MATURE = 'mature'; + public static ADULT = 'adult'; + + static MaturityCategories = [ Maturity.UNRATED, + Maturity.EVERYONE, + Maturity.TEEN, + Maturity.MATURE, + Maturity.ADULT + ]; + + static KnownMaturity(pMaturity: string): boolean { + return this.MaturityCategories.includes(pMaturity); + } +} + diff --git a/Goobieverse/src/utils/sets/Roles.ts b/Goobieverse/src/utils/sets/Roles.ts new file mode 100644 index 00000000..fb662496 --- /dev/null +++ b/Goobieverse/src/utils/sets/Roles.ts @@ -0,0 +1,27 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// Class to manage the manipulations on roles that accounts can have +export class Roles { + // at the moment, the only role is 'admin' + public static ADMIN = 'admin'; // someone who has metaverse-server admin + public static USER = 'user'; // a 'user' or 'person' + + // See if the passed role code is a known role token + static async KnownRole(pScope: string): Promise { + return [ Roles.ADMIN, Roles.USER ].includes(pScope); + } +} diff --git a/Goobieverse/src/utils/sets/Visibility.ts b/Goobieverse/src/utils/sets/Visibility.ts new file mode 100644 index 00000000..9e8debea --- /dev/null +++ b/Goobieverse/src/utils/sets/Visibility.ts @@ -0,0 +1,36 @@ +// Copyright 2021 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +export class Visibility { + public static OPEN = 'open'; + public static FRIENDS = 'friends'; + public static CONNECTIONS = 'connections'; + public static GROUP = 'group'; + public static PRIVATE = 'private'; + + static VisibilityCategories = [ + Visibility.OPEN, + Visibility.FRIENDS, + Visibility.CONNECTIONS, + Visibility.GROUP, + Visibility.PRIVATE + ]; + + static KnownVisibility(pVisibility: string): boolean { + return this.VisibilityCategories.includes(pVisibility); + } +} + diff --git a/Goobieverse/src/utils/vTypes.ts b/Goobieverse/src/utils/vTypes.ts new file mode 100755 index 00000000..5e51a7d3 --- /dev/null +++ b/Goobieverse/src/utils/vTypes.ts @@ -0,0 +1,64 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +'use strict'; + +import { IsNullOrEmpty, IsNotNullOrEmpty } from './Misc'; + +// An object that is used as a keyed collection of objects. +// The key is always a string +export interface VKeyedCollection { + [ key: string]: any +} + +export interface VKeyValue { + [ key: string]: string +} + +// String array. +// Several structures are an array of strings (TokenScope, AccountRoles, ...). +export class SArray { + static has(pArray: string[], pCheck: string): boolean { + return IsNullOrEmpty(pArray) ? false : pArray.includes(pCheck); + } + + static hasNoCase(pArray: string[], pCheck: string): boolean { + const pCheckLower = pCheck.toLowerCase(); + if (IsNotNullOrEmpty(pArray)) { + for (const ent of pArray) { + if (ent.toLowerCase() === pCheckLower) { + return true; + } + } + } + return false; + } + + static add(pArray: string[], pAdd: string): boolean { + let added = false; + if (typeof(pAdd) === 'string') { + if (! pArray.includes(pAdd)) { + pArray.push(pAdd); + added = true; + } + } + return added; + } + + static remove(pArray: string[], pRemove: string): void { + const idx = pArray.indexOf(pRemove); + if (idx >= 0) { + pArray.splice(idx, 1); + } + } +} \ No newline at end of file diff --git a/Goobieverse/test/services/profiles.test.ts b/Goobieverse/test/services/profiles.test.ts new file mode 100644 index 00000000..9525af9f --- /dev/null +++ b/Goobieverse/test/services/profiles.test.ts @@ -0,0 +1,8 @@ +import app from '../../src/app'; + +describe('\'profiles\' service', () => { + it('registered the service', () => { + const service = app.service('profiles'); + expect(service).toBeTruthy(); + }); +}); diff --git a/Goobieverse/test/services/users.test.ts b/Goobieverse/test/services/users.test.ts new file mode 100644 index 00000000..20d1ebb3 --- /dev/null +++ b/Goobieverse/test/services/users.test.ts @@ -0,0 +1,8 @@ +import app from '../../src/app'; + +describe('\'users\' service', () => { + it('registered the service', () => { + const service = app.service('users'); + expect(service).toBeTruthy(); + }); +}); From 927921bd25a98468027ed6da0ec0f91ad8f32d83 Mon Sep 17 00:00:00 2001 From: khilanlalani1503 Date: Thu, 30 Dec 2021 16:12:05 +0530 Subject: [PATCH 004/128] changes in schema from users to accounts --- Goobieverse/src/services/friends/friends.hooks.ts | 3 +-- Goobieverse/src/services/users/users.class.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Goobieverse/src/services/friends/friends.hooks.ts b/Goobieverse/src/services/friends/friends.hooks.ts index 3ad6ca64..c335ebaa 100644 --- a/Goobieverse/src/services/friends/friends.hooks.ts +++ b/Goobieverse/src/services/friends/friends.hooks.ts @@ -1,12 +1,11 @@ import { HooksObject } from "@feathersjs/feathers"; -import { myHook } from "../../hooks/userData"; export default { before: { all: [], find: [], get: [], - create: [myHook], + create: [], update: [], patch: [], remove: [], diff --git a/Goobieverse/src/services/users/users.class.ts b/Goobieverse/src/services/users/users.class.ts index 10bdc7b1..a97a8e88 100644 --- a/Goobieverse/src/services/users/users.class.ts +++ b/Goobieverse/src/services/users/users.class.ts @@ -17,7 +17,7 @@ export class Users extends Service { const client: Promise = app.get("mongoClient"); client.then((db) => { - this.Model = db.collection("users"); + this.Model = db.collection("accounts"); }); } From eb00928843844fe587a0f64a4b05c90d11ec6590 Mon Sep 17 00:00:00 2001 From: khilanlalani1503 Date: Thu, 30 Dec 2021 18:43:02 +0530 Subject: [PATCH 005/128] authentication 50% and eslint in it --- Goobieverse/package.json | 1 + Goobieverse/src/appconfig.ts | 67 ++++++++++--------- Goobieverse/src/authentication.ts | 47 +++---------- Goobieverse/src/hooks/userData.ts | 2 +- Goobieverse/src/interfaces/AccountModel.ts | 4 +- .../src/services/friends/friends.class.ts | 25 +++---- .../src/services/friends/friends.hooks.ts | 2 +- .../src/services/friends/friends.service.ts | 16 ++--- Goobieverse/src/services/users/users.class.ts | 41 ++++++------ Goobieverse/src/services/users/users.hooks.ts | 21 +++--- .../src/services/users/users.service.ts | 16 ++--- Goobieverse/src/utils/Misc.ts | 52 +++++++------- Goobieverse/src/utils/messages.ts | 51 ++------------ Goobieverse/src/utils/response.ts | 4 +- 14 files changed, 147 insertions(+), 202 deletions(-) diff --git a/Goobieverse/package.json b/Goobieverse/package.json index 9c7a9928..bddc130b 100644 --- a/Goobieverse/package.json +++ b/Goobieverse/package.json @@ -76,6 +76,7 @@ "@types/mongodb": "^4.0.7", "@types/morgan": "^1.9.3", "@types/serve-favicon": "^2.5.3", + "@types/trim": "^0.1.1", "@typescript-eslint/eslint-plugin": "^5.8.0", "@typescript-eslint/parser": "^5.8.0", "axios": "^0.24.0", diff --git a/Goobieverse/src/appconfig.ts b/Goobieverse/src/appconfig.ts index e1681f90..721c232f 100644 --- a/Goobieverse/src/appconfig.ts +++ b/Goobieverse/src/appconfig.ts @@ -1,15 +1,16 @@ -import dotenv from "dotenv-flow"; -import appRootPath from "app-root-path"; -import { IsNullOrEmpty, getMyExternalIPAddress } from "./utils/Misc"; +import dotenv from 'dotenv-flow'; +import appRootPath from 'app-root-path'; +import { IsNullOrEmpty, getMyExternalIPAddress } from './utils/Misc'; +import fs from 'fs'; -if (globalThis.process?.env.APP_ENV === "development") { - const fs = require("fs"); +if (globalThis.process?.env.APP_ENV === 'development') { + // const fs = require('fs'); if ( - !fs.existsSync(appRootPath.path + "/.env") && - !fs.existsSync(appRootPath.path + "/.env.local") + !fs.existsSync(appRootPath.path + '/.env') && + !fs.existsSync(appRootPath.path + '/.env.local') ) { - const fromEnvPath = appRootPath.path + "/.env.local.default"; - const toEnvPath = appRootPath.path + "/.env.local"; + const fromEnvPath = appRootPath.path + '/.env.local.default'; + const toEnvPath = appRootPath.path + '/.env.local'; fs.copyFileSync(fromEnvPath, toEnvPath, fs.constants.COPYFILE_EXCL); } } @@ -24,7 +25,7 @@ dotenv.config({ */ const server = { - local: process.env.LOCAL === "true", + local: process.env.LOCAL === 'true', hostName: process.env.SERVER_HOST, port: process.env.PORT ?? 3030, paginate: { @@ -32,7 +33,7 @@ const server = { max: 100, }, publicPath: process.env.PUBLIC_PATH, - version: process.env.SERVER_VERSION ?? "", + version: process.env.SERVER_VERSION ?? '', }; /** @@ -40,36 +41,36 @@ const server = { */ const metaverseServer = { - listen_host: process.env.LISTEN_HOST ?? "0.0.0.0", + listen_host: process.env.LISTEN_HOST ?? '0.0.0.0', listen_port: process.env.LISTEN_PORT ?? 9400, - metaverseInfoAdditionFile: process.env.METAVERSE_INFO_File ?? "", + metaverseInfoAdditionFile: process.env.METAVERSE_INFO_File ?? '', }; /** * Authentication */ const authentication = { - entity: "user", - service: "users", - secret: process.env.AUTH_SECRET ?? "testing", - authStrategies: ["jwt", "local"], + entity: 'user', + service: 'users', + secret: process.env.AUTH_SECRET ?? 'testing', + authStrategies: ['jwt', 'local'], jwtOptions: { - expiresIn: "60 days", + expiresIn: '60 days', }, local: { - usernameField: "email", - passwordField: "password", + usernameField: 'username', + passwordField: 'password', }, bearerToken: { numBytes: 16, }, oauth: { - redirect: "/", + redirect: '/', auth0: { - key: "", - secret: "", - subdomain: "", - scope: ["profile", "openid", "email"], + key: '', + secret: '', + subdomain: '', + scope: ['profile', 'openid', 'email'], }, }, }; @@ -79,10 +80,10 @@ const authentication = { */ const metaverse = { - metaverseName: process.env.METAVERSE_NAME ?? "", - metaverseNickName: process.env.METAVERSE_NICK_NAME ?? "", - metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? "", // if empty, set to self - defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? "", // if empty, set to self + metaverseName: process.env.METAVERSE_NAME ?? '', + metaverseNickName: process.env.METAVERSE_NICK_NAME ?? '', + metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? '', // if empty, set to self + defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? '', // if empty, set to self dashboardUrl: process.env.DASHBOARD_URL, }; @@ -102,10 +103,10 @@ if ( } const dbCollections = { - domains: "domains", - accounts: "accounts", - places: "places", - tokens: "tokens", + domains: 'domains', + accounts: 'accounts', + places: 'places', + tokens: 'tokens', }; /** diff --git a/Goobieverse/src/authentication.ts b/Goobieverse/src/authentication.ts index 01cfd482..d95d01c8 100644 --- a/Goobieverse/src/authentication.ts +++ b/Goobieverse/src/authentication.ts @@ -1,47 +1,22 @@ -import { Params, ServiceAddons } from "@feathersjs/feathers"; -import { AuthenticationService, JWTStrategy } from "@feathersjs/authentication"; -import { LocalStrategy } from "@feathersjs/authentication-local"; -import { expressOauth } from "@feathersjs/authentication-oauth"; +import { ServiceAddons } from '@feathersjs/feathers'; +import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'; +import { LocalStrategy } from '@feathersjs/authentication-local'; +import { expressOauth } from '@feathersjs/authentication-oauth'; -import { Application } from "./declarations"; +import { Application } from './declarations'; -declare module "./declarations" { +declare module './declarations' { interface ServiceTypes { - authentication: AuthenticationService & ServiceAddons; + 'authentication': AuthenticationService & ServiceAddons; } } -// class MyLocalStrategy extends LocalStrategy { -// async findEntity(username: any, params: Params) { -// try { -// const entity = await super.findEntity(username, params); -// return entity; -// } catch (error) { -// throw new Error("Entity not found"); -// } -// } - -// async comparePassword(entity: any, password: any) { -// try { -// const result = await super.comparePassword(entity, password); - -// return result; -// } catch (error) { -// throw new Error("Invalid password"); -// } -// } -// } - -export default function (app: Application): void { +export default function(app: Application): void { const authentication = new AuthenticationService(app); - // const authService = new AuthenticationService(app); - - authentication.register("jwt", new JWTStrategy()); - authentication.register("local", new LocalStrategy()); - // authService.register("local", new MyLocalStrategy()); - // app.use("/authentication", authService); + authentication.register('jwt', new JWTStrategy()); + authentication.register('local', new LocalStrategy()); - app.use("/authentication", authentication); + app.use('/authentication', authentication); app.configure(expressOauth()); } diff --git a/Goobieverse/src/hooks/userData.ts b/Goobieverse/src/hooks/userData.ts index c01cca97..96348da2 100644 --- a/Goobieverse/src/hooks/userData.ts +++ b/Goobieverse/src/hooks/userData.ts @@ -1,4 +1,4 @@ -import { Hook, HookContext } from "@feathersjs/feathers"; +import { Hook, HookContext } from '@feathersjs/feathers'; export async function myHook(context: any): Promise { // console.log(context, "myHook"); diff --git a/Goobieverse/src/interfaces/AccountModel.ts b/Goobieverse/src/interfaces/AccountModel.ts index 81fbf1d4..ae4bff71 100644 --- a/Goobieverse/src/interfaces/AccountModel.ts +++ b/Goobieverse/src/interfaces/AccountModel.ts @@ -23,8 +23,8 @@ export interface AccountModel { profileDetail: any; // JSON blob stored for user from server // User authentication - passwordHash: string; - passwordSalt: string; + // passwordHash: string; + // passwordSalt: string; sessionPublicKey: string; // PEM public key generated for this session accountEmailVerified: boolean; // default true if not present diff --git a/Goobieverse/src/services/friends/friends.class.ts b/Goobieverse/src/services/friends/friends.class.ts index 02907d99..31efdb35 100644 --- a/Goobieverse/src/services/friends/friends.class.ts +++ b/Goobieverse/src/services/friends/friends.class.ts @@ -1,29 +1,30 @@ -import { Db } from "mongodb"; -import { Service, MongoDBServiceOptions } from "feathers-mongodb"; -import { Application } from "../../declarations"; -import { Id, Params } from "@feathersjs/feathers"; -import { AccountModel } from "../../interfaces/AccountModel"; -import { isValidObject, isValidArray } from "../../utils/Misc"; -import { Response } from "../../utils/response"; -import { messages } from "../../utils/messages"; -const trim = require("trim"); +import { Db } from 'mongodb'; +import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; +import { Application } from '../../declarations'; +import { Id, Params } from '@feathersjs/feathers'; +import { AccountModel } from '../../interfaces/AccountModel'; +import { isValidObject, isValidArray } from '../../utils/Misc'; +import { Response } from '../../utils/response'; +import { messages } from '../../utils/messages'; +import trim from 'trim'; +// const trim = require("trim"); export class Friends extends Service { //eslint-disable-next-line @typescript-eslint/no-unused-vars constructor(options: Partial, app: Application) { super(options); - const client: Promise = app.get("mongoClient"); + const client: Promise = app.get('mongoClient'); client.then((db) => { - this.Model = db.collection("friends"); + this.Model = db.collection('friends'); }); } async create(data: any, params?: Params): Promise { try { const username = trim(data.username); - if (username != "" && typeof username != "undefined") { + if (username != '' && typeof username != 'undefined') { const UserData = await super .create(data) .then((result: any) => { diff --git a/Goobieverse/src/services/friends/friends.hooks.ts b/Goobieverse/src/services/friends/friends.hooks.ts index c335ebaa..949f69a4 100644 --- a/Goobieverse/src/services/friends/friends.hooks.ts +++ b/Goobieverse/src/services/friends/friends.hooks.ts @@ -1,4 +1,4 @@ -import { HooksObject } from "@feathersjs/feathers"; +import { HooksObject } from '@feathersjs/feathers'; export default { before: { diff --git a/Goobieverse/src/services/friends/friends.service.ts b/Goobieverse/src/services/friends/friends.service.ts index 593c5573..10ef7f6f 100644 --- a/Goobieverse/src/services/friends/friends.service.ts +++ b/Goobieverse/src/services/friends/friends.service.ts @@ -1,11 +1,11 @@ // Initializes the `friends` service on path `/friends` -import { ServiceAddons } from "@feathersjs/feathers"; -import { Application } from "../../declarations"; -import { Friends } from "./friends.class"; -import hooks from "./friends.hooks"; +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { Friends } from './friends.class'; +import hooks from './friends.hooks'; // Add this service to the service type index -declare module "../../declarations" { +declare module '../../declarations' { interface ServiceTypes { friends: Friends & ServiceAddons; } @@ -13,14 +13,14 @@ declare module "../../declarations" { export default function (app: Application): void { const options = { - paginate: app.get("paginate"), + paginate: app.get('paginate'), }; // Initialize our service with any options it requires - app.use("/friends", new Friends(options, app)); + app.use('/friends', new Friends(options, app)); // Get our initialized service so that we can register hooks - const service = app.service("friends"); + const service = app.service('friends'); service.hooks(hooks); } diff --git a/Goobieverse/src/services/users/users.class.ts b/Goobieverse/src/services/users/users.class.ts index a97a8e88..764f68bb 100644 --- a/Goobieverse/src/services/users/users.class.ts +++ b/Goobieverse/src/services/users/users.class.ts @@ -1,23 +1,24 @@ -import { Db } from "mongodb"; -import { Service, MongoDBServiceOptions } from "feathers-mongodb"; -import { Application } from "../../declarations"; -import { Id, Params } from "@feathersjs/feathers"; -import { AccountModel } from "../../interfaces/AccountModel"; -import { isValidObject, isValidArray } from "../../utils/Misc"; -import { Response } from "../../utils/response"; -import { messages } from "../../utils/messages"; -import { GenUUID } from "../../utils/Misc"; -const trim = require("trim"); +import { Db } from 'mongodb'; +import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; +import { Application } from '../../declarations'; +import { Id, Params } from '@feathersjs/feathers'; +import { AccountModel } from '../../interfaces/AccountModel'; +import { isValidObject, isValidArray } from '../../utils/Misc'; +import { Response } from '../../utils/response'; +import { messages } from '../../utils/messages'; +import { GenUUID } from '../../utils/Misc'; +import trim from 'trim'; +// const trim = require('trim'); export class Users extends Service { //eslint-disable-next-line @typescript-eslint/no-unused-vars constructor(options: Partial, app: Application) { super(options); - const client: Promise = app.get("mongoClient"); + const client: Promise = app.get('mongoClient'); client.then((db) => { - this.Model = db.collection("accounts"); + this.Model = db.collection('accounts'); }); } @@ -28,14 +29,14 @@ export class Users extends Service { const email = trim(data.email); const password = trim(data.password); if ( - id != "" && - typeof id != "undefined" && - username != "" && - typeof username != "undefined" && - email != "" && - typeof email != "undefined" && - password != "" && - typeof password != "undefined" + id != '' && + typeof id != 'undefined' && + username != '' && + typeof username != 'undefined' && + email != '' && + typeof email != 'undefined' && + password != '' && + typeof password != 'undefined' ) { const newData = { ...data, id: id }; const UserData = await super diff --git a/Goobieverse/src/services/users/users.hooks.ts b/Goobieverse/src/services/users/users.hooks.ts index 7a6247c2..b370ee79 100644 --- a/Goobieverse/src/services/users/users.hooks.ts +++ b/Goobieverse/src/services/users/users.hooks.ts @@ -1,8 +1,7 @@ -import { HooksObject } from "@feathersjs/feathers"; -import { myHook } from "../../hooks/userData"; -import * as feathersAuthentication from "@feathersjs/authentication"; -import * as local from "@feathersjs/authentication-local"; -import bcrypt from "bcrypt"; +import { HooksObject } from '@feathersjs/feathers'; +import { myHook } from '../../hooks/userData'; +import * as feathersAuthentication from '@feathersjs/authentication'; +import * as local from '@feathersjs/authentication-local'; const { authenticate } = feathersAuthentication.hooks; const { hashPassword, protect } = local.hooks; @@ -11,15 +10,21 @@ export default { before: { all: [], find: [], - get: [], - create: [hashPassword("password")], + get: [ + (context: any) => { + // delete context.params.user + console.log(context.params.user, 'context'); + return context; + }, + ], + create: [hashPassword('password')], update: [], patch: [], remove: [], }, after: { - all: [protect("password")], + all: [protect('password')], find: [], get: [], create: [], diff --git a/Goobieverse/src/services/users/users.service.ts b/Goobieverse/src/services/users/users.service.ts index 31fec244..a2bfc325 100644 --- a/Goobieverse/src/services/users/users.service.ts +++ b/Goobieverse/src/services/users/users.service.ts @@ -1,11 +1,11 @@ // Initializes the `users` service on path `/users` -import { ServiceAddons } from "@feathersjs/feathers"; -import { Application } from "../../declarations"; -import { Users } from "./users.class"; -import hooks from "./users.hooks"; +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { Users } from './users.class'; +import hooks from './users.hooks'; // Add this service to the service type index -declare module "../../declarations" { +declare module '../../declarations' { interface ServiceTypes { users: Users & ServiceAddons; } @@ -13,14 +13,14 @@ declare module "../../declarations" { export default function (app: Application): void { const options = { - paginate: app.get("paginate"), + paginate: app.get('paginate'), }; // Initialize our service with any options it requires - app.use("/users", new Users(options, app)); + app.use('/users', new Users(options, app)); // Get our initialized service so that we can register hooks - const service = app.service("users"); + const service = app.service('users'); service.hooks(hooks); } diff --git a/Goobieverse/src/utils/Misc.ts b/Goobieverse/src/utils/Misc.ts index 150b4cad..3edc7fd2 100644 --- a/Goobieverse/src/utils/Misc.ts +++ b/Goobieverse/src/utils/Misc.ts @@ -1,15 +1,15 @@ -import fs from "fs"; -import http from "http"; -import https from "https"; -import os from "os"; -import { v4 as uuidv4 } from "uuid"; +import fs from 'fs'; +import http from 'http'; +import https from 'https'; +import os from 'os'; +import { v4 as uuidv4 } from 'uuid'; // Return 'true' if the passed value is null or empty export function IsNullOrEmpty(pVal: any): boolean { return ( - typeof pVal === "undefined" || + typeof pVal === 'undefined' || pVal === null || - (typeof pVal === "string" && String(pVal).length === 0) + (typeof pVal === 'string' && String(pVal).length === 0) ); } @@ -26,18 +26,18 @@ export function IsNotNullOrEmpty(pVal: any): boolean { // Returns the parsed JSON object or 'undefined' if any errors. export async function readInJSON(pFilenameOrURL: string): Promise { let configBody: string; - if (pFilenameOrURL.startsWith("http://")) { + if (pFilenameOrURL.startsWith('http://')) { configBody = await httpRequest(pFilenameOrURL); } else { - if (pFilenameOrURL.startsWith("https://")) { + if (pFilenameOrURL.startsWith('https://')) { configBody = await httpsRequest(pFilenameOrURL); } else { try { // We should technically sanitize this filename but if one can change the environment // or config file variables, the app is already poned. - configBody = fs.readFileSync(pFilenameOrURL, "utf-8"); + configBody = fs.readFileSync(pFilenameOrURL, 'utf-8'); } catch (err) { - configBody = ""; + configBody = ''; console.debug( `readInJSON: failed read of user config file ${pFilenameOrURL}: ${err}` ); @@ -55,15 +55,15 @@ export async function httpsRequest(pUrl: string): Promise { return new Promise((resolve, reject) => { https .get(pUrl, (resp: any) => { - let data = ""; - resp.on("data", (chunk: string) => { + let data = ''; + resp.on('data', (chunk: string) => { data += chunk; }); - resp.on("end", () => { + resp.on('end', () => { resolve(data); }); }) - .on("error", (err: any) => { + .on('error', (err: any) => { reject(err); }); }); @@ -74,15 +74,15 @@ export async function httpRequest(pUrl: string): Promise { return new Promise((resolve, reject) => { http .get(pUrl, (resp: any) => { - let data = ""; - resp.on("data", (chunk: string) => { + let data = ''; + resp.on('data', (chunk: string) => { data += chunk; }); - resp.on("end", () => { + resp.on('end', () => { resolve(data); }); }) - .on("error", (err: any) => { + .on('error', (err: any) => { reject(err); }); }); @@ -94,7 +94,7 @@ export async function getMyExternalIPAddress(): Promise { return Promise.resolve(myExternalAddr); } return new Promise((resolve, reject) => { - httpsRequest("https://api.ipify.org") + httpsRequest('https://api.ipify.org') .then((resp) => { myExternalAddr = resp; resolve(myExternalAddr); @@ -104,20 +104,20 @@ export async function getMyExternalIPAddress(): Promise { const networkInterfaces = os.networkInterfaces(); // { 'lo1': [ info, info ], 'eth0': [ info, info ]} where 'info' could be v4 and v6 addr infos - let addressv4 = ""; - let addressv6 = ""; + let addressv4 = ''; + let addressv6 = ''; Object.keys(networkInterfaces).forEach((dev) => { networkInterfaces[dev]?.filter((details) => { - if (details.family === "IPv4" && details.internal === false) { + if (details.family === 'IPv4' && details.internal === false) { addressv4 = details.address; } - if (details.family === "IPv6" && details.internal === false) { + if (details.family === 'IPv6' && details.internal === false) { addressv6 = details.address; } }); }); - let address = ""; + let address = ''; if (IsNullOrEmpty(addressv4)) { address = addressv6; } else { @@ -125,7 +125,7 @@ export async function getMyExternalIPAddress(): Promise { } if (IsNullOrEmpty(address)) { - reject("No address found"); + reject('No address found'); } myExternalAddr = address.toString(); resolve(myExternalAddr); diff --git a/Goobieverse/src/utils/messages.ts b/Goobieverse/src/utils/messages.ts index 6e79b98a..8aa24979 100644 --- a/Goobieverse/src/utils/messages.ts +++ b/Goobieverse/src/utils/messages.ts @@ -1,47 +1,8 @@ export const messages = { - common_messages_error: "Something went wrong please try again later.", - common_messages_record_available: "Record is available.", - common_messages_record_not_available: "Record is not available.", - common_messages_records_available: "Records are available.", - common_messages_records_not_available: "Records are not available.", - login_success: "Login Successfully.", - common_messages_record_added: "Record added successfully.", - common_messages_record_added_failed: "Failed to added record.", - common_messages_record_updated: "Record updated successfully.", - common_messages_record_deleted: "Record deleted successfully.", - common_messages_record_update_failed: "Failed to Record Update", - common_messages_record_deleted_failed: "Failed to Delete Record", - common_messages_user_invitation_failed: "Failed to send User Invitation", - common_messages_no_invitation_available: "No Invitation available", - common_messages_invitation_accepted: "Invitation approved Successfully.", - common_messages_invitation_rejected: "Invitation rejected Successfully.", - common_messages_user_invitation_success: - "Invitation Sent to User Successfully.", - common_messages_record_not_found: "record not found", - common_messages_user_not_found: "User not active or found", - in_correct_email_psw_error: - " The email/password you entered does not match. Please enter the correct email/password", - required_parameters_null_or_missing: - "Required parameters value null or missing.", - email_already_exist: "This Email Id already exists in our records.", - something_went_wrong: "Something went wrong.", - otp_sent_successfully: "OTP sent successfully.", - your_not_Authorize: "Your not Authorize to perform this action", - report_generated_success: "Report generated successfully.", - item_from_other_store: "Please add items from same store as in cart.", - db_error_message: - "Sorry, we can not proceed further, due to some technical issue", - access_for_organization_to_device_error: - "Your organization does not have access to this device", - access_for_organization_to_device_success: - "Your organization have created access to this device", - no_such_email_found: "No such type of Email found in our data", - valid_email_address_error: "Please Enter valid Email Address", - otp_send_success: "OTP sent successfully", - otp_correct_success: "OTP entered was correct", - otp_correct_error: "OTP entered was incorrect", - password_updated_success: "Your password has been updated successfully", - password_updated_error: "Failed to update your password", - token_period_is_expired: "Your token has been expired", - please_select_weightBridge: "Please select a Weight Bridge!!!", + common_messages_error: 'Something went wrong please try again later.', + common_messages_record_available: 'Record is available.', + common_messages_record_not_available: 'Record is not available.', + common_messages_records_available: 'Records are available.', + common_messages_records_not_available: 'Records are not available.', + common_messages_record_added_failed:'Failed to add record!' }; diff --git a/Goobieverse/src/utils/response.ts b/Goobieverse/src/utils/response.ts index cd71989f..fd6753cf 100644 --- a/Goobieverse/src/utils/response.ts +++ b/Goobieverse/src/utils/response.ts @@ -1,8 +1,8 @@ export const Response = { success: (data: any) => { - return { status: "success", data: data }; + return { status: 'success', data: data }; }, error: (message: string) => { - return { status: "failure", message: message }; + return { status: 'failure', message: message }; }, }; From 3bb7e8d5c41765d38013d5fbc4094ea690d14b99 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Thu, 30 Dec 2021 19:13:11 +0530 Subject: [PATCH 006/128] added auth --- Goobieverse/src/appconfig.ts | 2 +- Goobieverse/src/authentication.ts | 1 - Goobieverse/src/services/auth/auth.class.ts | 13 +++++++ Goobieverse/src/services/auth/auth.hooks.ts | 37 +++++++++++++++++++ Goobieverse/src/services/auth/auth.service.ts | 26 +++++++++++++ Goobieverse/src/services/index.ts | 4 +- 6 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 Goobieverse/src/services/auth/auth.class.ts create mode 100644 Goobieverse/src/services/auth/auth.hooks.ts create mode 100644 Goobieverse/src/services/auth/auth.service.ts diff --git a/Goobieverse/src/appconfig.ts b/Goobieverse/src/appconfig.ts index 721c232f..ce1b835f 100644 --- a/Goobieverse/src/appconfig.ts +++ b/Goobieverse/src/appconfig.ts @@ -51,7 +51,7 @@ const metaverseServer = { */ const authentication = { entity: 'user', - service: 'users', + service: 'auth', secret: process.env.AUTH_SECRET ?? 'testing', authStrategies: ['jwt', 'local'], jwtOptions: { diff --git a/Goobieverse/src/authentication.ts b/Goobieverse/src/authentication.ts index d95d01c8..76809616 100644 --- a/Goobieverse/src/authentication.ts +++ b/Goobieverse/src/authentication.ts @@ -10,7 +10,6 @@ declare module './declarations' { 'authentication': AuthenticationService & ServiceAddons; } } - export default function(app: Application): void { const authentication = new AuthenticationService(app); diff --git a/Goobieverse/src/services/auth/auth.class.ts b/Goobieverse/src/services/auth/auth.class.ts new file mode 100644 index 00000000..d78dfc67 --- /dev/null +++ b/Goobieverse/src/services/auth/auth.class.ts @@ -0,0 +1,13 @@ +import { Db } from 'mongodb'; +import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; +import { Application } from '../../declarations'; + +export class Auth extends Service { + constructor(options: Partial, app: Application) { + super(options); + const client: Promise = app.get('mongoClient'); + client.then((db) => { + this.Model = db.collection('accounts'); + }); + } +} diff --git a/Goobieverse/src/services/auth/auth.hooks.ts b/Goobieverse/src/services/auth/auth.hooks.ts new file mode 100644 index 00000000..e23a0196 --- /dev/null +++ b/Goobieverse/src/services/auth/auth.hooks.ts @@ -0,0 +1,37 @@ +import { HooksObject } from '@feathersjs/feathers'; +import * as feathersAuthentication from '@feathersjs/authentication'; +import * as local from '@feathersjs/authentication-local'; +const { authenticate } = feathersAuthentication.hooks; +const { hashPassword, protect } = local.hooks; + +export default { + before: { + all: [], + find: [ authenticate('jwt') ], + get: [ authenticate('jwt') ], + create: [ hashPassword('password') ], + update: [ hashPassword('password'), authenticate('jwt') ], + patch: [ hashPassword('password'), authenticate('jwt') ], + remove: [ authenticate('jwt') ] + }, + + after: { + all: [protect('password')], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, + + error: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, +} as HooksObject; diff --git a/Goobieverse/src/services/auth/auth.service.ts b/Goobieverse/src/services/auth/auth.service.ts new file mode 100644 index 00000000..3c8618a1 --- /dev/null +++ b/Goobieverse/src/services/auth/auth.service.ts @@ -0,0 +1,26 @@ +// Initializes the `users` service on path `/users` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { Auth } from './auth.class'; +import hooks from './auth.hooks'; + +// Add this service to the service type index +declare module '../../declarations' { + interface ServiceTypes { + auth: Auth & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get('paginate'), + }; + + // Initialize our service with any options it requires + app.use('/auth', new Auth(options, app)); + + // Get our initialized service so that we can register hooks + const service = app.service('auth'); + + service.hooks(hooks); +} diff --git a/Goobieverse/src/services/index.ts b/Goobieverse/src/services/index.ts index 81996991..303b366c 100644 --- a/Goobieverse/src/services/index.ts +++ b/Goobieverse/src/services/index.ts @@ -1,10 +1,10 @@ import { Application } from '../declarations'; import users from './users/users.service'; import friends from './friends/friends.service'; -//import metaverseInfo from './metaverse_info/metaverse_info.service'; -// Don't remove this comment. It's needed to format import lines nicely. +import auth from './auth/auth.service'; export default function (app: Application): void { + app.configure(auth); app.configure(users); app.configure(friends); } From 9e93745bd2da1590f3dd225ff9b7466fe6ce99b6 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Thu, 30 Dec 2021 19:22:14 +0530 Subject: [PATCH 007/128] change hooks --- Goobieverse/src/services/auth/auth.hooks.ts | 9 +++++---- Goobieverse/src/services/users/users.hooks.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Goobieverse/src/services/auth/auth.hooks.ts b/Goobieverse/src/services/auth/auth.hooks.ts index e23a0196..661db2a9 100644 --- a/Goobieverse/src/services/auth/auth.hooks.ts +++ b/Goobieverse/src/services/auth/auth.hooks.ts @@ -1,6 +1,7 @@ import { HooksObject } from '@feathersjs/feathers'; import * as feathersAuthentication from '@feathersjs/authentication'; import * as local from '@feathersjs/authentication-local'; +import { disallow } from 'feathers-hooks-common'; const { authenticate } = feathersAuthentication.hooks; const { hashPassword, protect } = local.hooks; @@ -9,10 +10,10 @@ export default { all: [], find: [ authenticate('jwt') ], get: [ authenticate('jwt') ], - create: [ hashPassword('password') ], - update: [ hashPassword('password'), authenticate('jwt') ], - patch: [ hashPassword('password'), authenticate('jwt') ], - remove: [ authenticate('jwt') ] + create: [disallow('external')], + update: [disallow('external')], + patch: [disallow('external')], + remove: [ disallow('external')] }, after: { diff --git a/Goobieverse/src/services/users/users.hooks.ts b/Goobieverse/src/services/users/users.hooks.ts index b370ee79..a7abc28c 100644 --- a/Goobieverse/src/services/users/users.hooks.ts +++ b/Goobieverse/src/services/users/users.hooks.ts @@ -18,7 +18,7 @@ export default { }, ], create: [hashPassword('password')], - update: [], + update: [hashPassword('password')], patch: [], remove: [], }, From a938ea54a79505a1a4c9b993108e66b7ef80c3c4 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Fri, 31 Dec 2021 12:30:54 +0530 Subject: [PATCH 008/128] added DB common class --- Goobieverse/src/dbservice/DatabaseService.ts | 45 +++++++++++++++++++ .../src/dbservice/DatabaseServiceOptions.ts | 4 ++ Goobieverse/src/hooks/checkAccessToAccount.ts | 2 +- .../src/services/profiles/profiles.class.ts | 33 +++++--------- 4 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 Goobieverse/src/dbservice/DatabaseService.ts create mode 100644 Goobieverse/src/dbservice/DatabaseServiceOptions.ts diff --git a/Goobieverse/src/dbservice/DatabaseService.ts b/Goobieverse/src/dbservice/DatabaseService.ts new file mode 100644 index 00000000..ddf674e9 --- /dev/null +++ b/Goobieverse/src/dbservice/DatabaseService.ts @@ -0,0 +1,45 @@ +import { Service } from 'feathers-mongodb'; +import { Application } from '../declarations'; +import { DatabaseServiceOptions } from './DatabaseServiceOptions'; +import { Db, Collection, Document, FindCursor, WithId, DeleteResult } from 'mongodb'; +import { IsNullOrEmpty } from '../utils/Misc'; + + +export class DatabaseService extends Service { + app: Application; + db?:Db; + + constructor(options: Partial, app: Application) { + super(options); + this.app = app; + this.loadDatabase(); + } + + async loadDatabase() { + this.db = await this.app.get('mongoClient'); + } + + async getDatabase(): Promise { + if(IsNullOrEmpty(this.db)){ + await this.loadDatabase(); + } + return this.db!; + } + + async getService(tableName:string):Promise>{ + return await (await this.getDatabase()).collection(tableName); + } + + async findData(tableName: string, filter?:Document ): Promise>>{ + if(IsNullOrEmpty(filter)){ + return await (await (this.getService(tableName))).find(filter!); + }else{ + return await (await (this.getService(tableName))).find(); + } + } + + async findDataAsArray(tableName: string, filter?:Document): Promise { + return (await this.findData(tableName,filter)).toArray(); + } + +} \ No newline at end of file diff --git a/Goobieverse/src/dbservice/DatabaseServiceOptions.ts b/Goobieverse/src/dbservice/DatabaseServiceOptions.ts new file mode 100644 index 00000000..99a05d9b --- /dev/null +++ b/Goobieverse/src/dbservice/DatabaseServiceOptions.ts @@ -0,0 +1,4 @@ +import { MongoDBServiceOptions } from 'feathers-mongodb'; +import { Collection } from 'mongodb'; + +export interface DatabaseServiceOptions extends MongoDBServiceOptions {} \ No newline at end of file diff --git a/Goobieverse/src/hooks/checkAccessToAccount.ts b/Goobieverse/src/hooks/checkAccessToAccount.ts index 64644f62..e2574cda 100644 --- a/Goobieverse/src/hooks/checkAccessToAccount.ts +++ b/Goobieverse/src/hooks/checkAccessToAccount.ts @@ -30,7 +30,7 @@ export default (pRequiredAccess: Perm[]) => { } } break; - /*case Perm.DOMAIN: + /* case Perm.DOMAIN: // requestor is a domain and it's account is the domain's sponsoring account if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.DOMAIN)) { if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { diff --git a/Goobieverse/src/services/profiles/profiles.class.ts b/Goobieverse/src/services/profiles/profiles.class.ts index 8f68bf17..18701da4 100644 --- a/Goobieverse/src/services/profiles/profiles.class.ts +++ b/Goobieverse/src/services/profiles/profiles.class.ts @@ -1,33 +1,26 @@ +import { DatabaseService } from './../../dbservice/DatabaseService'; import { DomainModel } from './../../interfaces/DomainModel'; import { AccountModel } from '../../interfaces/AccountModel'; import config from '../../appconfig'; import { Availability } from '../../utils/sets/Availability'; -import { Db } from 'mongodb'; import { Params, Id } from '@feathersjs/feathers'; import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; import { Application } from '../../declarations'; import { buildAccountProfile } from '../../utils/Utils'; import { IsNotNullOrEmpty } from '../../utils/Misc'; -export class Profiles extends Service { - //eslint-disable-next-line @typescript-eslint/no-unused-vars - app: Application; - constructor(options: Partial, app: Application) { - super(options); - this.app = app; +export class Profiles extends DatabaseService { - const client: Promise = app.get('mongoClient'); - client.then((database) => { - this.Model = database.collection(config.dbCollections.accounts); - }); + constructor(options: Partial, app: Application) { + super(options,app); } async find(params?: Params): Promise { - const db = await this.app.get('mongoClient'); + const perPage = parseInt(params?.query?.per_page) || 10; const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; - const accounts = await super.find({ + const accounts = await this.findDataAsArray(config.dbCollections.accounts,{ query: { $or: [{ availability: undefined }, { availability: Availability.ALL }], $skip: skip, @@ -41,11 +34,8 @@ export class Profiles extends Service { (value, index, self) => self.indexOf(value) === index && value !== undefined ); - - const domains: Array = await db - .collection(config.dbCollections.domains) - .find({ query:{id: { $in: domainIds }}}) - .toArray(); + + const domains: DomainModel[] = await this.findDataAsArray(config.dbCollections.accounts,{ query:{id: { $in: domainIds }}}); const profiles: Array = []; @@ -59,15 +49,14 @@ export class Profiles extends Service { } profiles.push(await buildAccountProfile(element, domainModel)); }); - return Promise.resolve({ profiles }); + return Promise.resolve({ profiles }); } async get(id: Id, params: Params): Promise { - const db = await this.app.get('mongoClient'); - const accounts = await super.find({query:{id:id}}); + const accounts = await this.findDataAsArray(config.dbCollections.accounts,{query:{id:id}}); if(IsNotNullOrEmpty(accounts)){ const account = (accounts as Array)[0]; - const domains: Array = await db.collection(config.dbCollections.domains).find({ id: { $eq: account.locationDomainId } }).toArray(); + const domains: Array = await this.findDataAsArray(config.dbCollections.domains,{ id: { $eq: account.locationDomainId } }); let domainModel: any; if(IsNotNullOrEmpty(domains)){domainModel = domains[0];} const profile = await buildAccountProfile(account, domainModel); From b26bccf5456501a2cf46ea0e0bbf3aa395320293 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Fri, 31 Dec 2021 14:51:43 +0530 Subject: [PATCH 009/128] added responsebuilder --- Goobieverse/src/dbservice/DatabaseService.ts | 28 +- Goobieverse/src/hooks/checkAccessToAccount.ts | 7 +- .../src/responsebuilder/accountsBuilder.ts | 79 +++++ .../src/responsebuilder/domainsBuilder.ts | 80 +++++ .../src/responsebuilder/placesBuilder.ts | 130 ++++++++ .../src/responsebuilder/tokensBuilder.ts | 0 .../src/services/profiles/profiles.class.ts | 6 +- Goobieverse/src/utils/Utils.ts | 296 +----------------- 8 files changed, 319 insertions(+), 307 deletions(-) create mode 100644 Goobieverse/src/responsebuilder/accountsBuilder.ts create mode 100644 Goobieverse/src/responsebuilder/domainsBuilder.ts create mode 100644 Goobieverse/src/responsebuilder/placesBuilder.ts create mode 100644 Goobieverse/src/responsebuilder/tokensBuilder.ts diff --git a/Goobieverse/src/dbservice/DatabaseService.ts b/Goobieverse/src/dbservice/DatabaseService.ts index ddf674e9..45092d82 100644 --- a/Goobieverse/src/dbservice/DatabaseService.ts +++ b/Goobieverse/src/dbservice/DatabaseService.ts @@ -1,22 +1,28 @@ import { Service } from 'feathers-mongodb'; import { Application } from '../declarations'; +import { HookContext } from '@feathersjs/feathers'; import { DatabaseServiceOptions } from './DatabaseServiceOptions'; -import { Db, Collection, Document, FindCursor, WithId, DeleteResult } from 'mongodb'; -import { IsNullOrEmpty } from '../utils/Misc'; +import { Db, Collection, Document, FindCursor, WithId,Filter } from 'mongodb'; +import { IsNotNullOrEmpty, IsNullOrEmpty } from '../utils/Misc'; export class DatabaseService extends Service { - app: Application; + app?: Application; db?:Db; - - constructor(options: Partial, app: Application) { + context?:HookContext; + constructor(options: Partial,app?: Application,context?: HookContext) { super(options); this.app = app; + this.context = context; this.loadDatabase(); } async loadDatabase() { - this.db = await this.app.get('mongoClient'); + if(IsNotNullOrEmpty(this.app) && this.app){ + this.db = await this.app.get('mongoClient'); + }else if(IsNotNullOrEmpty(this.context) && this.context){ + this.db = await this.context.app.get('mongoClient'); + } } async getDatabase(): Promise { @@ -30,15 +36,17 @@ export class DatabaseService extends Service { return await (await this.getDatabase()).collection(tableName); } - async findData(tableName: string, filter?:Document ): Promise>>{ - if(IsNullOrEmpty(filter)){ + async findData(tableName: string, filter?:Filter ): Promise>>{ + + if(IsNotNullOrEmpty(filter)){ + console.log(filter); return await (await (this.getService(tableName))).find(filter!); - }else{ + } else { return await (await (this.getService(tableName))).find(); } } - async findDataAsArray(tableName: string, filter?:Document): Promise { + async findDataAsArray(tableName: string, filter?:Filter): Promise { return (await this.findData(tableName,filter)).toArray(); } diff --git a/Goobieverse/src/hooks/checkAccessToAccount.ts b/Goobieverse/src/hooks/checkAccessToAccount.ts index e2574cda..3b7475cb 100644 --- a/Goobieverse/src/hooks/checkAccessToAccount.ts +++ b/Goobieverse/src/hooks/checkAccessToAccount.ts @@ -1,4 +1,5 @@ import { AccountModel } from './../interfaces/AccountModel'; +import {Application} from '@feathersjs/feathers'; import { HookContext } from '@feathersjs/feathers'; import { IsNotNullOrEmpty } from '../utils/Misc'; import { Perm } from '../utils/Perm'; @@ -6,12 +7,14 @@ import config from '../appconfig'; import {HTTPStatusCode} from '../utils/response'; import {Availability} from '../utils/sets/Availability'; import { SArray } from '../utils/vTypes'; +import { DatabaseService } from '../dbservice/DatabaseService'; export default (pRequiredAccess: Perm[]) => { return async (context: HookContext): Promise => { - const db = await context.app.get('mongoClient'); + const dbService = new DatabaseService({},undefined,context); - const accounts = await db.collection(config.dbCollections.accounts).find({id:context.id}).toArray(); + const accounts = await dbService.findDataAsArray(config.dbCollections.accounts,{id:context.id}); + console.log(accounts); let canAccess = false; if (IsNotNullOrEmpty(accounts)) { diff --git a/Goobieverse/src/responsebuilder/accountsBuilder.ts b/Goobieverse/src/responsebuilder/accountsBuilder.ts new file mode 100644 index 00000000..d88ebf29 --- /dev/null +++ b/Goobieverse/src/responsebuilder/accountsBuilder.ts @@ -0,0 +1,79 @@ +import { DomainModel } from '../interfaces/DomainModel'; +import { AccountModel } from '../interfaces/AccountModel'; +import { buildLocationInfo } from './placesBuilder'; +import { VKeyedCollection } from '../utils/vTypes'; +import { isAdmin, isEnabled, createSimplifiedPublicKey } from '../utils/Utils'; +// Return the limited "user" info.. used by /api/v1/users +export async function buildUserInfo(pAccount: AccountModel): Promise { + return { + accountId: pAccount.id, + id: pAccount.id, + username: pAccount.username, + images: await buildImageInfo(pAccount), + location: await buildLocationInfo(pAccount), + }; +} + +export async function buildImageInfo(pAccount: AccountModel): Promise { + const ret: VKeyedCollection = {}; + + if (pAccount.imagesTiny) ret.tiny = pAccount.imagesTiny; + if (pAccount.imagesHero) ret.hero = pAccount.imagesHero; + if (pAccount.imagesThumbnail) ret.thumbnail = pAccount.imagesThumbnail; + return ret; +} + +// Return the block of account information. +// Used by several of the requests to return the complete account information. +export async function buildAccountInfo( + pAccount: AccountModel +): Promise { + return { + accountId: pAccount.id, + id: pAccount.id, + username: pAccount.username, + email: pAccount.email, + administrator: isAdmin(pAccount), + enabled: isEnabled(pAccount), + roles: pAccount.roles, + availability: pAccount.availability, + public_key: createSimplifiedPublicKey(pAccount.sessionPublicKey), + images: { + hero: pAccount.imagesHero, + tiny: pAccount.imagesTiny, + thumbnail: pAccount.imagesThumbnail, + }, + profile_detail: pAccount.profileDetail, + location: await buildLocationInfo(pAccount), + friends: pAccount.friends, + connections: pAccount.connections, + when_account_created: pAccount.whenCreated?.toISOString(), + when_account_created_s: pAccount.whenCreated?.getTime().toString(), + time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat?.getTime().toString(), + }; +} + +// Return the block of account information used as the account 'profile'. +// Anyone can fetch a profile (if 'availability' is 'any') so not all info is returned +export async function buildAccountProfile( + pAccount: AccountModel, + aDomain?: DomainModel +): Promise { + return { + accountId: pAccount.id, + id: pAccount.id, + username: pAccount.username, + images: { + hero: pAccount.imagesHero, + tiny: pAccount.imagesTiny, + thumbnail: pAccount.imagesThumbnail, + }, + profile_detail: pAccount.profileDetail, + location: await buildLocationInfo(pAccount, aDomain), + when_account_created: pAccount.whenCreated?.toISOString(), + when_account_created_s: pAccount.whenCreated?.getTime().toString(), + time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat?.getTime().toString(), + }; +} \ No newline at end of file diff --git a/Goobieverse/src/responsebuilder/domainsBuilder.ts b/Goobieverse/src/responsebuilder/domainsBuilder.ts new file mode 100644 index 00000000..a4f89b90 --- /dev/null +++ b/Goobieverse/src/responsebuilder/domainsBuilder.ts @@ -0,0 +1,80 @@ +import { Visibility } from '../utils/sets/Visibility'; +import { DomainModel } from '../interfaces/DomainModel'; +import { createSimplifiedPublicKey } from '../utils/Utils'; +import { buildPlacesForDomain } from './placesBuilder'; +import { Maturity } from '../utils/sets/Maturity'; +// A smaller, top-level domain info block +export async function buildDomainInfo(pDomain: DomainModel): Promise { + return { + id: pDomain.id, + domainId: pDomain.id, + name: pDomain.name, + visibility: pDomain.visibility ?? Visibility.OPEN, + capacity: pDomain.capacity, + sponsorAccountId: pDomain.sponsorAccountId, + label: pDomain.name, + network_address: pDomain.networkAddr, + network_port: pDomain.networkPort, + ice_server_address: pDomain.iceServerAddr, + version: pDomain.version, + protocol_version: pDomain.protocol, + active: pDomain.active ?? false, + time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), + num_users: pDomain.numUsers, + }; +} + +// Return a structure with the usual domain information. +export async function buildDomainInfoV1(pDomain: DomainModel): Promise { + return { + domainId: pDomain.id, + id: pDomain.id, // legacy + name: pDomain.name, + visibility: pDomain.visibility ?? Visibility.OPEN, + world_name: pDomain.name, // legacy + label: pDomain.name, // legacy + public_key: pDomain.publicKey? createSimplifiedPublicKey(pDomain.publicKey): undefined, + owner_places: await buildPlacesForDomain(pDomain), + sponsor_account_id: pDomain.sponsorAccountId, + ice_server_address: pDomain.iceServerAddr, + version: pDomain.version, + protocol_version: pDomain.protocol, + network_address: pDomain.networkAddr, + network_port: pDomain.networkPort, + automatic_networking: pDomain.networkingMode, + restricted: pDomain.restricted, + num_users: pDomain.numUsers, + anon_users: pDomain.anonUsers, + total_users: pDomain.numUsers, + capacity: pDomain.capacity, + description: pDomain.description, + maturity: pDomain.maturity ?? Maturity.UNRATED, + restriction: pDomain.restriction, + managers: pDomain.managers, + tags: pDomain.tags, + meta: { + capacity: pDomain.capacity, + contact_info: pDomain.contactInfo, + description: pDomain.description, + images: pDomain.images, + managers: pDomain.managers, + restriction: pDomain.restriction, + tags: pDomain.tags, + thumbnail: pDomain.thumbnail, + world_name: pDomain.name, + }, + users: { + num_anon_users: pDomain.anonUsers, + num_users: pDomain.numUsers, + user_hostnames: pDomain.hostnames, + }, + time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), + last_sender_key: pDomain.lastSenderKey, + addr_of_first_contact: pDomain.iPAddrOfFirstContact, + when_domain_entry_created: pDomain.whenCreated?.toISOString(), + when_domain_entry_created_s: pDomain.whenCreated?.getTime().toString(), + }; +} + \ No newline at end of file diff --git a/Goobieverse/src/responsebuilder/placesBuilder.ts b/Goobieverse/src/responsebuilder/placesBuilder.ts new file mode 100644 index 00000000..9fc18e58 --- /dev/null +++ b/Goobieverse/src/responsebuilder/placesBuilder.ts @@ -0,0 +1,130 @@ +import { AccountModel } from './../interfaces/AccountModel'; +import { IsNotNullOrEmpty,IsNullOrEmpty } from '../utils/Misc'; +import { buildDomainInfo } from './domainsBuilder'; +import { DomainModel } from '../interfaces/DomainModel'; +import { isOnline } from '../utils/Utils'; +import { PlaceModel } from '../interfaces/PlaceModel'; +import { Visibility } from '../utils/sets/Visibility'; +import { Maturity } from '../utils/sets/Maturity'; +// The returned location info has many options depending on whether +// the account has set location and/or has an associated domain. +// Return a structure that represents the target account's domain + +export async function buildLocationInfo(pAcct: AccountModel,aDomain?: DomainModel): Promise { + let ret: any = {}; + if (pAcct.locationDomainId) { + if (IsNotNullOrEmpty(aDomain) && aDomain) { + ret = { + root: { + domain: await buildDomainInfo(aDomain), + }, + path: pAcct.locationPath, + }; + } else { + // The domain doesn't have an ID + ret = { + root: { + domain: { + network_address: pAcct.locationNetworkAddress, + network_port: pAcct.locationNetworkPort, + }, + }, + }; + } + } + ret.node_id = pAcct.locationNodeId; + ret.online = isOnline(pAcct); + return ret; +} + +// Return an object with the formatted place information +// Pass the PlaceModel and the place's domain if known. +export async function buildPlaceInfo(pPlace: PlaceModel,pDomain?: DomainModel): Promise { + const ret = await buildPlaceInfoSmall(pPlace, pDomain); + + // if the place points to a domain, add that information also + if (IsNotNullOrEmpty(pDomain) && pDomain) { + ret.domain = await buildDomainInfo(pDomain); + } + return ret; +} + + +function getAddressString(pPlace: PlaceModel,aDomain?: DomainModel): string { +// Compute and return the string for the Places's address. +// The address is of the form "optional-domain/x,y,z/x,y,z,w". +// If the domain is missing, the domain-server's network address is added + let addr = pPlace.path ?? '/0,0,0/0,0,0,1'; + + // If no domain/address specified in path, build addr using reported domain IP/port + const pieces = addr.split('/'); + if (pieces[0].length === 0) { + if (IsNotNullOrEmpty(aDomain) && aDomain) { + if (IsNotNullOrEmpty(aDomain.networkAddr)) { + let domainAddr = aDomain.networkAddr; + if (IsNotNullOrEmpty(aDomain.networkPort)) { + domainAddr = aDomain.networkAddr + ':' + aDomain.networkPort; + } + addr = domainAddr + addr; + } + } + } + return addr; +} + + +// Return the basic information block for a Place +export async function buildPlaceInfoSmall(pPlace: PlaceModel,pDomain?: DomainModel): Promise { + const ret = { + placeId: pPlace.id, + id: pPlace.id, + name: pPlace.name, + displayName: pPlace.displayName, + visibility: pPlace.visibility ?? Visibility.OPEN, + address: getAddressString(pPlace,pDomain), + path: pPlace.path, + description: pPlace.description, + maturity: pPlace.maturity ?? Maturity.UNRATED, + tags: pPlace.tags, + managers: await getManagers(pPlace,pDomain), + thumbnail: pPlace.thumbnail, + images: pPlace.images, + current_attendance: pPlace.currentAttendance ?? 0, + current_images: pPlace.currentImages, + current_info: pPlace.currentInfo, + current_last_update_time: pPlace.currentLastUpdateTime?.toISOString(), + current_last_update_time_s: pPlace.currentLastUpdateTime?.getTime().toString(), + last_activity_update: pPlace.lastActivity?.toISOString(), + last_activity_update_s: pPlace.lastActivity?.getTime().toString(), + }; + return ret; +} + + +async function getManagers(pPlace: PlaceModel,aDomain?: DomainModel): Promise { + if(IsNullOrEmpty(pPlace.managers)) { + pPlace.managers = []; + //uncomment after complete Accounts Places api + /* + if (aDomain) { + const aAccount = await Accounts.getAccountWithId(aDomain.sponsorAccountId); + if (aAccount) { + pPlace.managers = [ aAccount.username ]; + } + } + await Places.updateEntityFields(pPlace, { 'managers': pPlace.managers }) + */ + } + return pPlace.managers; +} + + +// Return an array of Places names that are associated with the passed domain +export async function buildPlacesForDomain(pDomain: DomainModel): Promise { + const ret: any[] = []; + //uncomment after complete Places api + /* for await (const aPlace of Places.enumerateAsync(new GenericFilter({ domainId: pDomain.id }))) { + ret.push(await buildPlaceInfoSmall(aPlace, pDomain)); + }*/ + return ret; +} \ No newline at end of file diff --git a/Goobieverse/src/responsebuilder/tokensBuilder.ts b/Goobieverse/src/responsebuilder/tokensBuilder.ts new file mode 100644 index 00000000..e69de29b diff --git a/Goobieverse/src/services/profiles/profiles.class.ts b/Goobieverse/src/services/profiles/profiles.class.ts index 18701da4..098dac12 100644 --- a/Goobieverse/src/services/profiles/profiles.class.ts +++ b/Goobieverse/src/services/profiles/profiles.class.ts @@ -4,9 +4,9 @@ import { AccountModel } from '../../interfaces/AccountModel'; import config from '../../appconfig'; import { Availability } from '../../utils/sets/Availability'; import { Params, Id } from '@feathersjs/feathers'; -import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; +import { MongoDBServiceOptions } from 'feathers-mongodb'; import { Application } from '../../declarations'; -import { buildAccountProfile } from '../../utils/Utils'; +import { buildAccountProfile } from '../../responsebuilder/accountsBuilder'; import { IsNotNullOrEmpty } from '../../utils/Misc'; export class Profiles extends DatabaseService { @@ -22,7 +22,7 @@ export class Profiles extends DatabaseService { const accounts = await this.findDataAsArray(config.dbCollections.accounts,{ query: { - $or: [{ availability: undefined }, { availability: Availability.ALL }], + $or: [{availability:undefined },{availability:Availability.ALL}], $skip: skip, $limit: perPage, }, diff --git a/Goobieverse/src/utils/Utils.ts b/Goobieverse/src/utils/Utils.ts index 892bd923..95a35e5f 100644 --- a/Goobieverse/src/utils/Utils.ts +++ b/Goobieverse/src/utils/Utils.ts @@ -1,11 +1,6 @@ -import { DomainModel } from '../interfaces/DomainModel'; -import { PlaceModel } from '../interfaces/PlaceModel'; -import { Visibility } from './sets/Visibility'; -import { Maturity } from './sets/Maturity'; -import { SArray,VKeyedCollection } from './vTypes'; +import { SArray } from './vTypes'; import { Roles } from './sets/Roles'; import { AccountModel } from '../interfaces/AccountModel'; -import { IsNotNullOrEmpty,IsNullOrEmpty } from './Misc'; import config from '../appconfig'; @@ -25,302 +20,19 @@ export function createSimplifiedPublicKey(pPubKey: string): string { return keyLines.join(''); // Combine all lines into one long string } - -// The returned location info has many options depending on whether -// the account has set location and/or has an associated domain. -// Return a structure that represents the target account's domain - -export async function buildLocationInfo(pAcct: AccountModel,aDomain?: DomainModel): Promise { - let ret: any = {}; - if (pAcct.locationDomainId) { - if (IsNotNullOrEmpty(aDomain) && aDomain) { - ret = { - root: { - domain: await buildDomainInfo(aDomain), - }, - path: pAcct.locationPath, - }; - } else { - // The domain doesn't have an ID - ret = { - root: { - domain: { - network_address: pAcct.locationNetworkAddress, - network_port: pAcct.locationNetworkPort, - }, - }, - }; - } - } - ret.node_id = pAcct.locationNodeId; - ret.online = isOnline(pAcct); - return ret; -} - -// A smaller, top-level domain info block -export async function buildDomainInfo(pDomain: DomainModel): Promise { - return { - id: pDomain.id, - domainId: pDomain.id, - name: pDomain.name, - visibility: pDomain.visibility ?? Visibility.OPEN, - capacity: pDomain.capacity, - sponsorAccountId: pDomain.sponsorAccountId, - label: pDomain.name, - network_address: pDomain.networkAddr, - network_port: pDomain.networkPort, - ice_server_address: pDomain.iceServerAddr, - version: pDomain.version, - protocol_version: pDomain.protocol, - active: pDomain.active ?? false, - time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), - num_users: pDomain.numUsers, - }; -} - -// Return a structure with the usual domain information. -export async function buildDomainInfoV1(pDomain: DomainModel): Promise { - return { - domainId: pDomain.id, - id: pDomain.id, // legacy - name: pDomain.name, - visibility: pDomain.visibility ?? Visibility.OPEN, - world_name: pDomain.name, // legacy - label: pDomain.name, // legacy - public_key: pDomain.publicKey - ? createSimplifiedPublicKey(pDomain.publicKey) - : undefined, - owner_places: await buildPlacesForDomain(pDomain), - sponsor_account_id: pDomain.sponsorAccountId, - ice_server_address: pDomain.iceServerAddr, - version: pDomain.version, - protocol_version: pDomain.protocol, - network_address: pDomain.networkAddr, - network_port: pDomain.networkPort, - automatic_networking: pDomain.networkingMode, - restricted: pDomain.restricted, - num_users: pDomain.numUsers, - anon_users: pDomain.anonUsers, - total_users: pDomain.numUsers, - capacity: pDomain.capacity, - description: pDomain.description, - maturity: pDomain.maturity ?? Maturity.UNRATED, - restriction: pDomain.restriction, - managers: pDomain.managers, - tags: pDomain.tags, - meta: { - capacity: pDomain.capacity, - contact_info: pDomain.contactInfo, - description: pDomain.description, - images: pDomain.images, - managers: pDomain.managers, - restriction: pDomain.restriction, - tags: pDomain.tags, - thumbnail: pDomain.thumbnail, - world_name: pDomain.name, - }, - users: { - num_anon_users: pDomain.anonUsers, - num_users: pDomain.numUsers, - user_hostnames: pDomain.hostnames, - }, - time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), - last_sender_key: pDomain.lastSenderKey, - addr_of_first_contact: pDomain.iPAddrOfFirstContact, - when_domain_entry_created: pDomain.whenCreated?.toISOString(), - when_domain_entry_created_s: pDomain.whenCreated?.getTime().toString(), - }; -} - -// Return the limited "user" info.. used by /api/v1/users -export async function buildUserInfo(pAccount: AccountModel): Promise { - return { - accountId: pAccount.id, - id: pAccount.id, - username: pAccount.username, - images: await buildImageInfo(pAccount), - location: await buildLocationInfo(pAccount), - }; -} - -export async function buildImageInfo(pAccount: AccountModel): Promise { - const ret:VKeyedCollection = {}; - - if (pAccount.imagesTiny) ret.tiny = pAccount.imagesTiny; - if (pAccount.imagesHero) ret.hero = pAccount.imagesHero; - if (pAccount.imagesThumbnail) ret.thumbnail = pAccount.imagesThumbnail; - return ret; -} - -// Return the block of account information. -// Used by several of the requests to return the complete account information. -export async function buildAccountInfo( - pAccount: AccountModel -): Promise { - return { - accountId: pAccount.id, - id: pAccount.id, - username: pAccount.username, - email: pAccount.email, - administrator: isAdmin(pAccount), - enabled: isEnabled(pAccount), - roles: pAccount.roles, - availability: pAccount.availability, - public_key: createSimplifiedPublicKey(pAccount.sessionPublicKey), - images: { - hero: pAccount.imagesHero, - tiny: pAccount.imagesTiny, - thumbnail: pAccount.imagesThumbnail, - }, - profile_detail: pAccount.profileDetail, - location: await buildLocationInfo(pAccount), - friends: pAccount.friends, - connections: pAccount.connections, - when_account_created: pAccount.whenCreated?.toISOString(), - when_account_created_s: pAccount.whenCreated?.getTime().toString(), - time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat - ?.getTime() - .toString(), - }; -} - // getter property that is 'true' if the user is a grid administrator -function isAdmin(pAcct: AccountModel): boolean { +export function isAdmin(pAcct: AccountModel): boolean { return SArray.has(pAcct.roles, Roles.ADMIN); } // Any logic to test of account is active // Currently checks if account email is verified or is legacy // account (no 'accountEmailVerified' variable) -function isEnabled(pAcct: AccountModel): boolean { +export function isEnabled(pAcct: AccountModel): boolean { return pAcct.accountEmailVerified ?? true; } -// Return the block of account information used as the account 'profile'. -// Anyone can fetch a profile (if 'availability' is 'any') so not all info is returned -export async function buildAccountProfile( - pAccount: AccountModel, - aDomain?: DomainModel -): Promise { - return { - accountId: pAccount.id, - id: pAccount.id, - username: pAccount.username, - images: { - hero: pAccount.imagesHero, - tiny: pAccount.imagesTiny, - thumbnail: pAccount.imagesThumbnail, - }, - profile_detail: pAccount.profileDetail, - location: await buildLocationInfo(pAccount,aDomain), - when_account_created: pAccount.whenCreated?.toISOString(), - when_account_created_s: pAccount.whenCreated?.getTime().toString(), - time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat - ?.getTime() - .toString(), - }; -} - -// Return an object with the formatted place information -// Pass the PlaceModel and the place's domain if known. -export async function buildPlaceInfo( - pPlace: PlaceModel, - pDomain?: DomainModel -): Promise { - const ret = await buildPlaceInfoSmall(pPlace, pDomain); - - // if the place points to a domain, add that information also - if (IsNotNullOrEmpty(pDomain) && pDomain) { - ret.domain = await buildDomainInfo(pDomain); - } - return ret; -} -// Return the basic information block for a Place -export async function buildPlaceInfoSmall( - pPlace: PlaceModel, - pDomain?: DomainModel -): Promise { - const ret = { - placeId: pPlace.id, - id: pPlace.id, - name: pPlace.name, - displayName: pPlace.displayName, - visibility: pPlace.visibility ?? Visibility.OPEN, - address: getAddressString(pPlace,pDomain), - path: pPlace.path, - description: pPlace.description, - maturity: pPlace.maturity ?? Maturity.UNRATED, - tags: pPlace.tags, - managers: await getManagers(pPlace,pDomain), - thumbnail: pPlace.thumbnail, - images: pPlace.images, - current_attendance: pPlace.currentAttendance ?? 0, - current_images: pPlace.currentImages, - current_info: pPlace.currentInfo, - current_last_update_time: pPlace.currentLastUpdateTime?.toISOString(), - current_last_update_time_s: pPlace.currentLastUpdateTime - ?.getTime() - .toString(), - last_activity_update: pPlace.lastActivity?.toISOString(), - last_activity_update_s: pPlace.lastActivity?.getTime().toString(), - }; - return ret; -} - - -function getAddressString(pPlace: PlaceModel,aDomain?: DomainModel): string { - // Compute and return the string for the Places's address. - // The address is of the form "optional-domain/x,y,z/x,y,z,w". - // If the domain is missing, the domain-server's network address is added - let addr = pPlace.path ?? '/0,0,0/0,0,0,1'; - - // If no domain/address specified in path, build addr using reported domain IP/port - const pieces = addr.split('/'); - if (pieces[0].length === 0) { - if (IsNotNullOrEmpty(aDomain) && aDomain) { - if (IsNotNullOrEmpty(aDomain.networkAddr)) { - let domainAddr = aDomain.networkAddr; - if (IsNotNullOrEmpty(aDomain.networkPort)) { - domainAddr = aDomain.networkAddr + ':' + aDomain.networkPort; - } - addr = domainAddr + addr; - } - } - } - return addr; -} - -async function getManagers(pPlace: PlaceModel,aDomain?: DomainModel): Promise { - if(IsNullOrEmpty(pPlace.managers)) { - pPlace.managers = []; - //uncomment after complete Accounts Places api - /* - if (aDomain) { - const aAccount = await Accounts.getAccountWithId(aDomain.sponsorAccountId); - if (aAccount) { - pPlace.managers = [ aAccount.username ]; - } - } - await Places.updateEntityFields(pPlace, { 'managers': pPlace.managers }) - */ - } - return pPlace.managers; -} - -// Return an array of Places names that are associated with the passed domain -export async function buildPlacesForDomain(pDomain: DomainModel): Promise { - const ret: any[] = []; - //uncomment after complete Places api - /* for await (const aPlace of Places.enumerateAsync(new GenericFilter({ domainId: pDomain.id }))) { - ret.push(await buildPlaceInfoSmall(aPlace, pDomain)); - }*/ - return ret; -} -function isOnline(pAcct: AccountModel): boolean { +export function isOnline(pAcct: AccountModel): boolean { if (pAcct && pAcct.timeOfLastHeartbeat) { return ( Date.now().valueOf() - pAcct.timeOfLastHeartbeat.valueOf() < From e982694819ec6d4b5693951ab4542f23a157a0c3 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Fri, 31 Dec 2021 15:17:53 +0530 Subject: [PATCH 010/128] change database service --- Goobieverse/src/dbservice/DatabaseService.ts | 18 +++---- Goobieverse/src/hooks/checkAccessToAccount.ts | 18 +++++-- .../src/services/profiles/profiles.class.ts | 48 ++++++++++++++++--- .../src/services/profiles/profiles.service.ts | 2 +- 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/Goobieverse/src/dbservice/DatabaseService.ts b/Goobieverse/src/dbservice/DatabaseService.ts index 45092d82..6a2e5da4 100644 --- a/Goobieverse/src/dbservice/DatabaseService.ts +++ b/Goobieverse/src/dbservice/DatabaseService.ts @@ -1,6 +1,6 @@ import { Service } from 'feathers-mongodb'; import { Application } from '../declarations'; -import { HookContext } from '@feathersjs/feathers'; +import { HookContext, Paginated } from '@feathersjs/feathers'; import { DatabaseServiceOptions } from './DatabaseServiceOptions'; import { Db, Collection, Document, FindCursor, WithId,Filter } from 'mongodb'; import { IsNotNullOrEmpty, IsNullOrEmpty } from '../utils/Misc'; @@ -33,21 +33,17 @@ export class DatabaseService extends Service { } async getService(tableName:string):Promise>{ - return await (await this.getDatabase()).collection(tableName); + this.Model = await (await this.getDatabase()).collection(tableName); + return this.Model; } - async findData(tableName: string, filter?:Filter ): Promise>>{ - + async findData(tableName: string, filter?:Filter ): Promise | any[]>{ + await (this.getService(tableName)); if(IsNotNullOrEmpty(filter)){ console.log(filter); - return await (await (this.getService(tableName))).find(filter!); + return await super.find(filter!); } else { - return await (await (this.getService(tableName))).find(); + return await super.find(); } } - - async findDataAsArray(tableName: string, filter?:Filter): Promise { - return (await this.findData(tableName,filter)).toArray(); - } - } \ No newline at end of file diff --git a/Goobieverse/src/hooks/checkAccessToAccount.ts b/Goobieverse/src/hooks/checkAccessToAccount.ts index 3b7475cb..49896634 100644 --- a/Goobieverse/src/hooks/checkAccessToAccount.ts +++ b/Goobieverse/src/hooks/checkAccessToAccount.ts @@ -1,5 +1,5 @@ import { AccountModel } from './../interfaces/AccountModel'; -import {Application} from '@feathersjs/feathers'; +import { Application, Paginated } from '@feathersjs/feathers'; import { HookContext } from '@feathersjs/feathers'; import { IsNotNullOrEmpty } from '../utils/Misc'; import { Perm } from '../utils/Perm'; @@ -13,12 +13,22 @@ export default (pRequiredAccess: Perm[]) => { return async (context: HookContext): Promise => { const dbService = new DatabaseService({},undefined,context); - const accounts = await dbService.findDataAsArray(config.dbCollections.accounts,{id:context.id}); + const accounts = await dbService.findData(config.dbCollections.accounts,{query:{id:context.id}}); console.log(accounts); let canAccess = false; if (IsNotNullOrEmpty(accounts)) { - const pTargetEntity = accounts[0]; + + + let pTargetEntity:AccountModel; + + if(accounts instanceof Array){ + pTargetEntity = (accounts as Array)[0]; + }else{ + pTargetEntity = accounts.data[0]; + } + + for (const perm of pRequiredAccess) { switch (perm) { case Perm.ALL: @@ -28,7 +38,7 @@ export default (pRequiredAccess: Perm[]) => { // The target entity is publicly visible // Mostly AccountEntities that must have an 'availability' field if (pTargetEntity.hasOwnProperty('availability')) { - if ((pTargetEntity as AccountModel).availability.includes(Availability.ALL)) { + if ((pTargetEntity).availability.includes(Availability.ALL)) { canAccess = true; } } diff --git a/Goobieverse/src/services/profiles/profiles.class.ts b/Goobieverse/src/services/profiles/profiles.class.ts index 098dac12..6a36727d 100644 --- a/Goobieverse/src/services/profiles/profiles.class.ts +++ b/Goobieverse/src/services/profiles/profiles.class.ts @@ -20,14 +20,23 @@ export class Profiles extends DatabaseService { const perPage = parseInt(params?.query?.per_page) || 10; const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; - const accounts = await this.findDataAsArray(config.dbCollections.accounts,{ + const accountData = await this.findData(config.dbCollections.accounts,{ query: { - $or: [{availability:undefined },{availability:Availability.ALL}], + $or: [{availability:undefined},{availability: Availability.ALL }], $skip: skip, $limit: perPage, - }, + }, }); + let accounts:AccountModel[] = []; + + if(accountData instanceof Array){ + accounts = accountData as Array; + }else{ + accounts = accountData.data as Array; + } + + const domainIds = (accounts as Array) ?.map((item) => item.locationDomainId) .filter( @@ -35,7 +44,15 @@ export class Profiles extends DatabaseService { self.indexOf(value) === index && value !== undefined ); - const domains: DomainModel[] = await this.findDataAsArray(config.dbCollections.accounts,{ query:{id: { $in: domainIds }}}); + const domainData= await this.findData(config.dbCollections.domains,{ query:{id: { $in: domainIds }}}); + + let domains:DomainModel[] = []; + + if(domainData instanceof Array){ + domains = domainData as Array; + }else{ + domains = domainData.data as Array; + } const profiles: Array = []; @@ -53,10 +70,29 @@ export class Profiles extends DatabaseService { } async get(id: Id, params: Params): Promise { - const accounts = await this.findDataAsArray(config.dbCollections.accounts,{query:{id:id}}); + const accountData = await this.findData(config.dbCollections.accounts,{query:{id:id}}); + + let accounts:AccountModel[] = []; + + if(accountData instanceof Array){ + accounts = accountData as Array; + }else{ + accounts = accountData.data as Array; + } + if(IsNotNullOrEmpty(accounts)){ const account = (accounts as Array)[0]; - const domains: Array = await this.findDataAsArray(config.dbCollections.domains,{ id: { $eq: account.locationDomainId } }); + + const domainData = await this.findData(config.dbCollections.domains,{ id: { $eq: account.locationDomainId } }); + + let domains:DomainModel[] = []; + + if(domainData instanceof Array){ + domains = domainData as Array; + }else{ + domains = domainData.data as Array; + } + let domainModel: any; if(IsNotNullOrEmpty(domains)){domainModel = domains[0];} const profile = await buildAccountProfile(account, domainModel); diff --git a/Goobieverse/src/services/profiles/profiles.service.ts b/Goobieverse/src/services/profiles/profiles.service.ts index 7ef3a9db..c1a4c30b 100644 --- a/Goobieverse/src/services/profiles/profiles.service.ts +++ b/Goobieverse/src/services/profiles/profiles.service.ts @@ -13,7 +13,7 @@ declare module '../../declarations' { export default function (app: Application): void { const options = { - // paginate: app.get('paginate') + paginate: app.get('paginate') }; // Initialize our service with any options it requires From fb7d642b1db10fb3778e6c62b71986289db3695f Mon Sep 17 00:00:00 2001 From: khilanlalani1503 Date: Fri, 31 Dec 2021 15:23:00 +0530 Subject: [PATCH 011/128] user api changes --- Goobieverse/src/dbservice/DatabaseService.ts | 12 +- Goobieverse/src/services/users/users.class.ts | 226 +++++++++++------- Goobieverse/src/services/users/users.hooks.ts | 16 +- Goobieverse/src/utils/messages.ts | 3 +- 4 files changed, 156 insertions(+), 101 deletions(-) diff --git a/Goobieverse/src/dbservice/DatabaseService.ts b/Goobieverse/src/dbservice/DatabaseService.ts index 45092d82..9318476b 100644 --- a/Goobieverse/src/dbservice/DatabaseService.ts +++ b/Goobieverse/src/dbservice/DatabaseService.ts @@ -17,6 +17,7 @@ export class DatabaseService extends Service { this.loadDatabase(); } + async loadDatabase() { if(IsNotNullOrEmpty(this.app) && this.app){ this.db = await this.app.get('mongoClient'); @@ -36,10 +37,10 @@ export class DatabaseService extends Service { return await (await this.getDatabase()).collection(tableName); } - async findData(tableName: string, filter?:Filter ): Promise>>{ - + + async findData(tableName: string, filter?:Filter ): Promise>>{ if(IsNotNullOrEmpty(filter)){ - console.log(filter); + console.log(filter,'filter'); return await (await (this.getService(tableName))).find(filter!); } else { return await (await (this.getService(tableName))).find(); @@ -50,4 +51,9 @@ export class DatabaseService extends Service { return (await this.findData(tableName,filter)).toArray(); } + async CreateData(tableName: string, data: any): Promise { + console.log(this.getService(tableName),'all'); + return (await this.findData(tableName)).toArray(); + } + } \ No newline at end of file diff --git a/Goobieverse/src/services/users/users.class.ts b/Goobieverse/src/services/users/users.class.ts index 764f68bb..b6845494 100644 --- a/Goobieverse/src/services/users/users.class.ts +++ b/Goobieverse/src/services/users/users.class.ts @@ -1,106 +1,156 @@ -import { Db } from 'mongodb'; +import { DatabaseService } from './../../dbservice/DatabaseService'; import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; import { Application } from '../../declarations'; +import config from '../../appconfig'; import { Id, Params } from '@feathersjs/feathers'; import { AccountModel } from '../../interfaces/AccountModel'; -import { isValidObject, isValidArray } from '../../utils/Misc'; import { Response } from '../../utils/response'; -import { messages } from '../../utils/messages'; -import { GenUUID } from '../../utils/Misc'; -import trim from 'trim'; -// const trim = require('trim'); - -export class Users extends Service { +import { GenUUID, IsNotNullOrEmpty } from '../../utils/Misc'; +import { Roles } from '../../utils/sets/Roles'; +import { IsNullOrEmpty } from '../../utils/Misc'; +export class Users extends DatabaseService { //eslint-disable-next-line @typescript-eslint/no-unused-vars constructor(options: Partial, app: Application) { - super(options); - - const client: Promise = app.get('mongoClient'); - - client.then((db) => { - this.Model = db.collection('accounts'); - }); + super(options,app); } async create(data: AccountModel, params?: Params): Promise { - try { - const id = GenUUID(); - const username = trim(data.username); - const email = trim(data.email); - const password = trim(data.password); - if ( - id != '' && - typeof id != 'undefined' && - username != '' && - typeof username != 'undefined' && - email != '' && - typeof email != 'undefined' && - password != '' && - typeof password != 'undefined' - ) { - const newData = { ...data, id: id }; - const UserData = await super - .create(newData) - .then((result: any) => { - let finalData = {}; - finalData = result == null ? {} : result; - return finalData; - }) - .catch((error: any) => { - return error; - }); - if (isValidObject(UserData) && !UserData.type) { - return Response.success(UserData); + // try { + // const id = GenUUID(); + // const username = trim(data.username); + // const email = trim(data.email); + // const password = trim(data.password); + // const roles = [Roles.USER]; + // const friends : string[] = []; + // const connections : string[] = []; + // const whenCreated = new Date(); + // // const perPage = parseInt(params?.query?.per_page) || 10; + // // const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; + + // const accounts = await super.find({}); + // console.log(accounts, 'all accounts'); + // if ( + // id != '' && + // typeof id != 'undefined' && + // username != '' && + // typeof username != 'undefined' && + // email != '' && + // typeof email != 'undefined' && + // password != '' && + // typeof password != 'undefined' && + // typeof roles != 'undefined' + // ) { + // const newData = { ...data, id: id , roles:roles,friends:friends,connections:connections,whenCreated:whenCreated}; + // const UserData = await super + // .create(newData) + // .then((result: any) => { + // let finalData = {}; + // finalData = result == null ? {} : result; + // return finalData; + // }) + // .catch((error: any) => { + // return error; + // }); + // if (isValidObject(UserData) && !UserData.type) { + // return Response.success(UserData); + // } + // } else { + // return Response.error(messages.common_messages_record_added_failed); + // } + // } catch (error: any) { + // return Response.error(messages.common_messages_error); + // } + if (data) { + const username : string = data.username; + const email : string = data.email; + const password : string = data.password; + if (username) { + const accountsName: AccountModel[] = await this.findDataAsArray(config.dbCollections.accounts, { username: username } ); + console.log(accountsName, 'accountsName'); + const name = (accountsName as Array) + ?.map((item) => item.username); + console.log(name); + if (!name.includes(username)) { + const accountsEmail: AccountModel[] = await this.findDataAsArray(config.dbCollections.accounts, { email: email } ); + const emailAddress = (accountsEmail as Array) + ?.map((item) => item.email); + if (!emailAddress.includes(email)) { + console.log('in'); + const id = GenUUID(); + const roles = [Roles.USER]; + const friends : string[] = []; + const connections : string[] = []; + const whenCreated = new Date(); + + const newUserData = { + ...data, + id: id, + roles: roles, + whenCreated: whenCreated, + friends: friends, + connections:connections, + }; + + const accounts: AccountModel[] = await this.CreateData(config.dbCollections.accounts, { newUserData }); + console.log(accounts, 'accounts'); + // return Promise.resolve({ newUserData }); + // const AccountDetails = await this.CreateData(config.dbCollections.accounts, { query: {insertOne:{newUserData} } }); + // console.log(AccountDetails,'AccountDetails'); + }else{ + return Response.error('Email already exists'); + } + } else { + return Response.error('Account already exists'); } } else { - return Response.error(messages.common_messages_record_added_failed); + return Response.error('Badly formatted username'); } - } catch (error: any) { - return Response.error(messages.common_messages_error); + } else { + return Response.error('Badly formatted request'); } } - async find(params?: Params): Promise { - try { - const UserData = await super - .find() - .then((result: any) => { - let finalData = {}; - finalData = result == null ? {} : result; - return finalData; - }) - .catch((error: any) => { - return error; - }); - if (isValidArray(UserData.data)) { - return Response.success(UserData.data); - } else { - return Response.error(messages.common_messages_records_not_available); - } - } catch (error: any) { - return Response.error(messages.common_messages_error); - } - } + // async find(params?: Params): Promise { + // try { + // const UserData = await super + // .find() + // .then((result: any) => { + // let finalData = {}; + // finalData = result == null ? {} : result; + // return finalData; + // }) + // .catch((error: any) => { + // return error; + // }); + // if (isValidArray(UserData.data)) { + // return Response.success(UserData.data); + // } else { + // return Response.error(messages.common_messages_records_not_available); + // } + // } catch (error: any) { + // return Response.error(messages.common_messages_error); + // } + // } - async get(id: string, params?: Params): Promise { - try { - const UserData = await super - .get(id) - .then((result: any) => { - let finalData = {}; - finalData = result == null ? {} : result; - return finalData; - }) - .catch((error: any) => { - return error; - }); - if (isValidObject(UserData) && !UserData.type) { - return Response.success(UserData); - } else { - return Response.error(messages.common_messages_record_not_available); - } - } catch (error: any) { - return Response.error(messages.common_messages_error); - } - } + // async get(id: string, params?: Params): Promise { + // try { + // const UserData = await super + // .get(id) + // .then((result: any) => { + // let finalData = {}; + // finalData = result == null ? {} : result; + // return finalData; + // }) + // .catch((error: any) => { + // return error; + // }); + // if (isValidObject(UserData) && !UserData.type) { + // return Response.success(UserData); + // } else { + // return Response.error(messages.common_messages_record_not_available); + // } + // } catch (error: any) { + // return Response.error(messages.common_messages_error); + // } + // } } diff --git a/Goobieverse/src/services/users/users.hooks.ts b/Goobieverse/src/services/users/users.hooks.ts index a7abc28c..68f8c420 100644 --- a/Goobieverse/src/services/users/users.hooks.ts +++ b/Goobieverse/src/services/users/users.hooks.ts @@ -2,6 +2,10 @@ import { HooksObject } from '@feathersjs/feathers'; import { myHook } from '../../hooks/userData'; import * as feathersAuthentication from '@feathersjs/authentication'; import * as local from '@feathersjs/authentication-local'; +import requestFail from '../../hooks/requestFail'; +import requestSuccess from '../../hooks/requestSuccess'; +import { Perm } from '../../utils/Perm'; +import checkAccessToAccount from '../../hooks/checkAccessToAccount'; const { authenticate } = feathersAuthentication.hooks; const { hashPassword, protect } = local.hooks; @@ -10,13 +14,7 @@ export default { before: { all: [], find: [], - get: [ - (context: any) => { - // delete context.params.user - console.log(context.params.user, 'context'); - return context; - }, - ], + get: [], create: [hashPassword('password')], update: [hashPassword('password')], patch: [], @@ -24,7 +22,7 @@ export default { }, after: { - all: [protect('password')], + all: [protect('password'),requestSuccess()], find: [], get: [], create: [], @@ -34,7 +32,7 @@ export default { }, error: { - all: [], + all: [requestFail()], find: [], get: [], create: [], diff --git a/Goobieverse/src/utils/messages.ts b/Goobieverse/src/utils/messages.ts index 8aa24979..2fac7658 100644 --- a/Goobieverse/src/utils/messages.ts +++ b/Goobieverse/src/utils/messages.ts @@ -4,5 +4,6 @@ export const messages = { common_messages_record_not_available: 'Record is not available.', common_messages_records_available: 'Records are available.', common_messages_records_not_available: 'Records are not available.', - common_messages_record_added_failed:'Failed to add record!' + common_messages_record_added_failed: 'Failed to add record!', + }; From 2597fa4d01520174b24a2a16cabe10427786ff73 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Fri, 31 Dec 2021 15:37:08 +0530 Subject: [PATCH 012/128] added findDataToArray functionm into Database Service --- Goobieverse/src/dbservice/DatabaseService.ts | 11 ++++++++ Goobieverse/src/hooks/checkAccessToAccount.ts | 25 ++++++++----------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Goobieverse/src/dbservice/DatabaseService.ts b/Goobieverse/src/dbservice/DatabaseService.ts index 6a2e5da4..29e716bf 100644 --- a/Goobieverse/src/dbservice/DatabaseService.ts +++ b/Goobieverse/src/dbservice/DatabaseService.ts @@ -46,4 +46,15 @@ export class DatabaseService extends Service { return await super.find(); } } + + async findDataToArray(tableName: string, filter?:Filter ): Promise{ + await (this.getService(tableName)); + const data = await this.findData(tableName,filter); + if(data instanceof Array){ + return data; + }else{ + return data.data; + } + } + } \ No newline at end of file diff --git a/Goobieverse/src/hooks/checkAccessToAccount.ts b/Goobieverse/src/hooks/checkAccessToAccount.ts index 49896634..057ea941 100644 --- a/Goobieverse/src/hooks/checkAccessToAccount.ts +++ b/Goobieverse/src/hooks/checkAccessToAccount.ts @@ -9,6 +9,7 @@ import {Availability} from '../utils/sets/Availability'; import { SArray } from '../utils/vTypes'; import { DatabaseService } from '../dbservice/DatabaseService'; + export default (pRequiredAccess: Perm[]) => { return async (context: HookContext): Promise => { const dbService = new DatabaseService({},undefined,context); @@ -17,18 +18,14 @@ export default (pRequiredAccess: Perm[]) => { console.log(accounts); let canAccess = false; - if (IsNotNullOrEmpty(accounts)) { - - - let pTargetEntity:AccountModel; - - if(accounts instanceof Array){ - pTargetEntity = (accounts as Array)[0]; - }else{ - pTargetEntity = accounts.data[0]; - } - + let pTargetEntity:AccountModel | undefined; + if(accounts instanceof Array){ + pTargetEntity = (accounts as Array)[0]; + }else if(IsNotNullOrEmpty(accounts.data[0])){ + pTargetEntity = accounts.data[0]; + } + if (IsNotNullOrEmpty(pTargetEntity)) { for (const perm of pRequiredAccess) { switch (perm) { case Perm.ALL: @@ -37,13 +34,13 @@ export default (pRequiredAccess: Perm[]) => { case Perm.PUBLIC: // The target entity is publicly visible // Mostly AccountEntities that must have an 'availability' field - if (pTargetEntity.hasOwnProperty('availability')) { + if (pTargetEntity?.hasOwnProperty('availability')) { if ((pTargetEntity).availability.includes(Availability.ALL)) { canAccess = true; } } break; - /* case Perm.DOMAIN: + /* case Perm.DOMAIN: // requestor is a domain and it's account is the domain's sponsoring account if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.DOMAIN)) { if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { @@ -57,7 +54,7 @@ export default (pRequiredAccess: Perm[]) => { } } } - break; + break;*/ case Perm.OWNER: // The requestor wants to be the same account as the target entity if (pAuthToken && pTargetEntity.hasOwnProperty('id')) { From bb0069171e95d598f1c06580d13c3f84f8bc4f93 Mon Sep 17 00:00:00 2001 From: khilanlalani1503 Date: Sat, 1 Jan 2022 20:10:32 +0530 Subject: [PATCH 013/128] api for friends , connections post api and user register with email verification and user all api --- Goobieverse/package.json | 6 +- Goobieverse/src/appconfig.ts | 20 +- Goobieverse/src/dbservice/DatabaseService.ts | 24 +- Goobieverse/src/hooks/checkAccessToAccount.ts | 25 +-- Goobieverse/src/interfaces/RequestModal.ts | 22 ++ .../services/connections/connections.class.ts | 32 +++ .../services/connections/connections.hooks.ts | 38 ++++ .../connections/connections.service.ts | 27 +++ Goobieverse/src/services/email/email.class.ts | 16 ++ Goobieverse/src/services/email/email.hooks.ts | 33 +++ .../src/services/email/email.service.ts | 25 +++ .../src/services/friends/friends.class.ts | 78 ++++--- .../src/services/friends/friends.hooks.ts | 11 +- .../src/services/friends/friends.service.ts | 1 + Goobieverse/src/services/index.ts | 6 +- Goobieverse/src/services/users/users.class.ts | 211 ++++++++---------- Goobieverse/src/services/users/users.hooks.ts | 13 +- .../src/services/users/users.service.ts | 1 + Goobieverse/src/utils/mail.ts | 19 ++ Goobieverse/test/services/connections.test.ts | 8 + Goobieverse/test/services/email.test.ts | 8 + Goobieverse/types.d.ts | 5 + Goobieverse/verificationEmail.html | 18 ++ package-lock.json | 30 ++- package.json | 1 + 25 files changed, 492 insertions(+), 186 deletions(-) create mode 100644 Goobieverse/src/interfaces/RequestModal.ts create mode 100644 Goobieverse/src/services/connections/connections.class.ts create mode 100644 Goobieverse/src/services/connections/connections.hooks.ts create mode 100644 Goobieverse/src/services/connections/connections.service.ts create mode 100644 Goobieverse/src/services/email/email.class.ts create mode 100644 Goobieverse/src/services/email/email.hooks.ts create mode 100644 Goobieverse/src/services/email/email.service.ts create mode 100644 Goobieverse/src/utils/mail.ts create mode 100644 Goobieverse/test/services/connections.test.ts create mode 100644 Goobieverse/test/services/email.test.ts create mode 100644 Goobieverse/types.d.ts create mode 100644 Goobieverse/verificationEmail.html diff --git a/Goobieverse/package.json b/Goobieverse/package.json index bddc130b..2ae24693 100644 --- a/Goobieverse/package.json +++ b/Goobieverse/package.json @@ -25,7 +25,7 @@ "scripts": { "test": "npm run lint && npm run compile && npm run jest", "lint": "eslint src/. test/. --config .eslintrc.json --ext .ts --fix", - "dev": "cross-env APP_ENV=development ts-node-dev --no-notify src/", + "dev": "ts-eager src/index.ts cross-env APP_ENV=development", "start": "cross-env APP_ENV=prod npm run compile && node lib/", "jest": "jest --forceExit", "compile": "shx rm -rf lib/ && tsc", @@ -56,12 +56,15 @@ "cross-env": "^7.0.3", "dotenv-flow": "^3.2.0", "feathers-hooks-common": "^5.0.6", + "feathers-mailer": "^3.1.0", "feathers-mongodb": "^6.4.1", "helmet": "^4.6.0", "mongodb": "^4.2.2", "mongodb-core": "^3.2.7", + "nodemailer-smtp-transport": "^2.7.4", "serve-favicon": "^2.5.0", "trim": "^1.0.1", + "ts-eager": "^2.0.2", "uuidv4": "^6.2.12", "winston": "^3.3.3" }, @@ -75,6 +78,7 @@ "@types/jsonwebtoken": "^8.5.6", "@types/mongodb": "^4.0.7", "@types/morgan": "^1.9.3", + "@types/nodemailer-smtp-transport": "^2.7.5", "@types/serve-favicon": "^2.5.3", "@types/trim": "^0.1.1", "@typescript-eslint/eslint-plugin": "^5.8.0", diff --git a/Goobieverse/src/appconfig.ts b/Goobieverse/src/appconfig.ts index e672ec22..8df343f7 100644 --- a/Goobieverse/src/appconfig.ts +++ b/Goobieverse/src/appconfig.ts @@ -36,6 +36,16 @@ const server = { version: process.env.SERVER_VERSION ?? '', }; +const email = { + host: process.env.SMTP_HOST ?? 'smtp.gmail.com', + port: process.env.SMTP_PORT ?? '465', + secure: process.env.SMTP_SECURE ?? true, + auth: { + user: process.env.SMTP_USER ?? 'khilan.odan@gmail.com', + pass: process.env.SMTP_PASS ?? 'blackhawk143', + } +}; + /** * Metaverse Server */ @@ -51,10 +61,13 @@ const metaverseServer = { handshake_request_expiration_minutes: 1, // minutes that a handshake friend request is active connection_request_expiration_minutes: 60 * 24 * 4, // 4 days friend_request_expiration_minutes: 60 * 24 * 4, // 4 days - + base_admin_account:process.env.ADMIN_ACCOUNT ?? 'Goobieverse', place_current_timeout_minutes: 5, // minutes until current place info is stale place_inactive_timeout_minutes: 60, // minutes until place is considered inactive - place_check_last_activity_seconds: (3*60)-5, // seconds between checks for Place lastActivity updates + place_check_last_activity_seconds: (3 * 60) - 5, // seconds between checks for Place lastActivity updates + email_verification_timeout_minutes: process.env.EMAIL_VERIFICATION_TIME, + enable_account_email_verification: process.env.ENABLE_ACCOUNT_VERIFICATION ?? 'true', + email_verification_email_body: '../verificationEmail.html', }; /** @@ -133,7 +146,8 @@ const config = { server, metaverse, metaverseServer, - dbCollections + dbCollections, + email }; export default config; diff --git a/Goobieverse/src/dbservice/DatabaseService.ts b/Goobieverse/src/dbservice/DatabaseService.ts index c23ae5e8..186e44bb 100644 --- a/Goobieverse/src/dbservice/DatabaseService.ts +++ b/Goobieverse/src/dbservice/DatabaseService.ts @@ -1,9 +1,10 @@ import { Service } from 'feathers-mongodb'; import { Application } from '../declarations'; -import { HookContext, Paginated } from '@feathersjs/feathers'; +import { HookContext, Paginated, } from '@feathersjs/feathers'; import { DatabaseServiceOptions } from './DatabaseServiceOptions'; import { Db, Collection, Document, FindCursor, WithId,Filter } from 'mongodb'; import { IsNotNullOrEmpty, IsNullOrEmpty } from '../utils/Misc'; +import { AccountModel } from '../interfaces/AccountModel'; export class DatabaseService extends Service { @@ -48,13 +49,24 @@ export class DatabaseService extends Service { } } - async findDataAsArray(tableName: string, filter?:Filter): Promise { - return (await this.findData(tableName,filter)).toArray(); + async findDataToArray(tableName: string, filter?:Filter ): Promise{ + await (this.getService(tableName)); + const data = await this.findData(tableName,filter); + if(data instanceof Array){ + return data; + }else{ + return data.data; + } + } + + async CreateData(tableName: string, data:any): Promise { + await (this.getService(tableName)); + return await super.create(data); } - async CreateData(tableName: string, data: any): Promise { - console.log(this.getService(tableName),'all'); - return (await this.findData(tableName)).toArray(); + async UpdateDataById(tableName: string, data:any,id:any): Promise { + await (this.getService(tableName)); + return await super.update(id, data); } } \ No newline at end of file diff --git a/Goobieverse/src/hooks/checkAccessToAccount.ts b/Goobieverse/src/hooks/checkAccessToAccount.ts index 49896634..399a7966 100644 --- a/Goobieverse/src/hooks/checkAccessToAccount.ts +++ b/Goobieverse/src/hooks/checkAccessToAccount.ts @@ -9,6 +9,7 @@ import {Availability} from '../utils/sets/Availability'; import { SArray } from '../utils/vTypes'; import { DatabaseService } from '../dbservice/DatabaseService'; + export default (pRequiredAccess: Perm[]) => { return async (context: HookContext): Promise => { const dbService = new DatabaseService({},undefined,context); @@ -17,18 +18,14 @@ export default (pRequiredAccess: Perm[]) => { console.log(accounts); let canAccess = false; - if (IsNotNullOrEmpty(accounts)) { - - - let pTargetEntity:AccountModel; - - if(accounts instanceof Array){ - pTargetEntity = (accounts as Array)[0]; - }else{ - pTargetEntity = accounts.data[0]; - } - + let pTargetEntity:AccountModel | undefined; + if(accounts instanceof Array){ + pTargetEntity = (accounts as Array)[0]; + }else if(IsNotNullOrEmpty(accounts.data[0])){ + pTargetEntity = accounts.data[0]; + } + if (IsNotNullOrEmpty(pTargetEntity)) { for (const perm of pRequiredAccess) { switch (perm) { case Perm.ALL: @@ -37,13 +34,12 @@ export default (pRequiredAccess: Perm[]) => { case Perm.PUBLIC: // The target entity is publicly visible // Mostly AccountEntities that must have an 'availability' field - if (pTargetEntity.hasOwnProperty('availability')) { + if (pTargetEntity?.hasOwnProperty('availability')) { if ((pTargetEntity).availability.includes(Availability.ALL)) { canAccess = true; } } - break; - /* case Perm.DOMAIN: + break;/* case Perm.DOMAIN: // requestor is a domain and it's account is the domain's sponsoring account if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.DOMAIN)) { if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { @@ -58,6 +54,7 @@ export default (pRequiredAccess: Perm[]) => { } } break; + case Perm.OWNER: // The requestor wants to be the same account as the target entity if (pAuthToken && pTargetEntity.hasOwnProperty('id')) { diff --git a/Goobieverse/src/interfaces/RequestModal.ts b/Goobieverse/src/interfaces/RequestModal.ts new file mode 100644 index 00000000..441b08aa --- /dev/null +++ b/Goobieverse/src/interfaces/RequestModal.ts @@ -0,0 +1,22 @@ +export interface RequestEntity { + id: string; + requestType: string; + + // requestor and target + requestingAccountId: string; + targetAccountId: string; + + // administration + expirationTime: Date; + whenCreated: Date; + + // requestType == HANDSHAKE + requesterNodeId: string; + targetNodeId: string; + requesterAccepted: boolean; + targetAccepted: boolean; + + // requestType == VERIFYEMAIL + // 'requestingAccountId' is the account being verified + verificationCode: string; // the code we're waiting for +} \ No newline at end of file diff --git a/Goobieverse/src/services/connections/connections.class.ts b/Goobieverse/src/services/connections/connections.class.ts new file mode 100644 index 00000000..6aaf99bc --- /dev/null +++ b/Goobieverse/src/services/connections/connections.class.ts @@ -0,0 +1,32 @@ +import { MongoDBServiceOptions } from 'feathers-mongodb'; +import { DatabaseService } from './../../dbservice/DatabaseService'; +import { Application } from '../../declarations'; +import config from '../../appconfig'; +import { Response } from '../../utils/response'; +import { isValidObject } from '../../utils/Misc'; + +export class Connections extends DatabaseService { + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options, app); + this.app = app; + } + + async create(data: any, params?: any): Promise { + if (data && data.username) { + const ParticularUserData: any = await this.findData(config.dbCollections.accounts, { query: { id: params.user.id } }); + const newParticularUserData = ParticularUserData.data[0]; + newParticularUserData.connections.push(data.username); + const addUserData = await this.UpdateDataById(config.dbCollections.accounts,newParticularUserData, params.user.id); + if (isValidObject(addUserData)) { + return Promise.resolve({}); + } else { + return Response.error('cannot add connections this way'); + } + } else { + return Response.error('Badly formed request'); + } + } + + +} diff --git a/Goobieverse/src/services/connections/connections.hooks.ts b/Goobieverse/src/services/connections/connections.hooks.ts new file mode 100644 index 00000000..4e5135ae --- /dev/null +++ b/Goobieverse/src/services/connections/connections.hooks.ts @@ -0,0 +1,38 @@ +import { HooksObject } from '@feathersjs/feathers'; +import * as authentication from '@feathersjs/authentication'; +import requestFail from '../../hooks/requestFail'; +import requestSuccess from '../../hooks/requestSuccess'; + +const { authenticate } = authentication.hooks; + +export default { + before: { + all: [authenticate('jwt')], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, + + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, + + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } +} as HooksObject; diff --git a/Goobieverse/src/services/connections/connections.service.ts b/Goobieverse/src/services/connections/connections.service.ts new file mode 100644 index 00000000..486e7e82 --- /dev/null +++ b/Goobieverse/src/services/connections/connections.service.ts @@ -0,0 +1,27 @@ +// Initializes the `connections` service on path `/connections` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { Connections } from './connections.class'; +import hooks from './connections.hooks'; + +// Add this service to the service type index +declare module '../../declarations' { + interface ServiceTypes { + 'connections': Connections & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get('paginate'), + id:'id' + }; + + // Initialize our service with any options it requires + app.use('/connections', new Connections(options, app)); + + // Get our initialized service so that we can register hooks + const service = app.service('connections'); + + service.hooks(hooks); +} diff --git a/Goobieverse/src/services/email/email.class.ts b/Goobieverse/src/services/email/email.class.ts new file mode 100644 index 00000000..174de40d --- /dev/null +++ b/Goobieverse/src/services/email/email.class.ts @@ -0,0 +1,16 @@ +import { Db } from 'mongodb'; +import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; +import { Application } from '../../declarations'; + +export class Email extends Service { + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options); + + const client: Promise = app.get('mongoClient'); + + client.then(db => { + this.Model = db.collection('email'); + }); + } +} diff --git a/Goobieverse/src/services/email/email.hooks.ts b/Goobieverse/src/services/email/email.hooks.ts new file mode 100644 index 00000000..cfbd4fde --- /dev/null +++ b/Goobieverse/src/services/email/email.hooks.ts @@ -0,0 +1,33 @@ +import { HooksObject } from '@feathersjs/feathers'; + +export default { + before: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, + + after: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, + + error: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } +} as HooksObject; diff --git a/Goobieverse/src/services/email/email.service.ts b/Goobieverse/src/services/email/email.service.ts new file mode 100644 index 00000000..cb018f96 --- /dev/null +++ b/Goobieverse/src/services/email/email.service.ts @@ -0,0 +1,25 @@ +// Initializes the `email` service on path `/email` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { Email } from './email.class'; +import hooks from './email.hooks'; +import config from '../../appconfig'; +import smtpTransport from 'nodemailer-smtp-transport'; +import Mailer from 'feathers-mailer'; + + +// Add this service to the service type index +declare module '../../declarations' { + interface ServiceTypes { + 'email': Email & ServiceAddons; + } +} + +export default function (app: Application): void { + const event = Mailer(smtpTransport(config.email)); + app.use('email', event); + + const service = app.service('email'); + + service.hooks(hooks); +} diff --git a/Goobieverse/src/services/friends/friends.class.ts b/Goobieverse/src/services/friends/friends.class.ts index 31efdb35..39f8246e 100644 --- a/Goobieverse/src/services/friends/friends.class.ts +++ b/Goobieverse/src/services/friends/friends.class.ts @@ -1,48 +1,54 @@ -import { Db } from 'mongodb'; -import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; +import { MongoDBServiceOptions } from 'feathers-mongodb'; +import { DatabaseService } from './../../dbservice/DatabaseService'; import { Application } from '../../declarations'; -import { Id, Params } from '@feathersjs/feathers'; -import { AccountModel } from '../../interfaces/AccountModel'; -import { isValidObject, isValidArray } from '../../utils/Misc'; +import config from '../../appconfig'; import { Response } from '../../utils/response'; -import { messages } from '../../utils/messages'; -import trim from 'trim'; -// const trim = require("trim"); +import { Params } from '@feathersjs/feathers'; -export class Friends extends Service { + +export class Friends extends DatabaseService { //eslint-disable-next-line @typescript-eslint/no-unused-vars constructor(options: Partial, app: Application) { - super(options); - - const client: Promise = app.get('mongoClient'); - - client.then((db) => { - this.Model = db.collection('friends'); - }); + super(options, app); + this.app = app; } - async create(data: any, params?: Params): Promise { - try { - const username = trim(data.username); - if (username != '' && typeof username != 'undefined') { - const UserData = await super - .create(data) - .then((result: any) => { - let finalData = {}; - finalData = result == null ? {} : result; - return finalData; - }) - .catch((error: any) => { - return error; - }); - if (isValidObject(UserData) && !UserData.type) { - return Response.success(UserData); - } + async create(data: any, params?: any): Promise { + if (data && data.username) { + const ParticularUserData: any = await this.findData(config.dbCollections.accounts, { query: { id: params.user.id } }); + if (ParticularUserData.data[0].connections.includes(data.username)) { + const newParticularUserData = ParticularUserData.data[0]; + newParticularUserData.friends.push(data.username); + await this.UpdateDataById(config.dbCollections.accounts,newParticularUserData, params.user.id); } else { - return Response.error(messages.common_messages_record_added_failed); + return Response.error('cannot add friend who is not a connection'); } - } catch (error: any) { - return Response.error(messages.common_messages_error); + } else { + return Response.error('Badly formed request'); + } + } + + async find(params?: any): Promise { + if (params.user.friends) { + const friends = params.user.friends; + return Promise.resolve({ friends }); + } else { + throw new Error('No friend found'); } } + + async remove(id: string, params?: any): Promise { + if (params.user.friends) { + const ParticularUserData: any = await this.findData(config.dbCollections.accounts, { query: { id: params.user.id } }); + const friends = ParticularUserData.data[0].friends.filter(function (value:string) { + return value !== id; + }); + ParticularUserData.data[0].friends = friends; + const newParticularUserData = ParticularUserData.data[0]; + await this.UpdateDataById(config.dbCollections.accounts,newParticularUserData, params.user.id); + } else { + throw new Error('Not logged in'); + } + } + } diff --git a/Goobieverse/src/services/friends/friends.hooks.ts b/Goobieverse/src/services/friends/friends.hooks.ts index 949f69a4..e2333ff5 100644 --- a/Goobieverse/src/services/friends/friends.hooks.ts +++ b/Goobieverse/src/services/friends/friends.hooks.ts @@ -1,8 +1,13 @@ import { HooksObject } from '@feathersjs/feathers'; +import * as authentication from '@feathersjs/authentication'; +import requestFail from '../../hooks/requestFail'; +import requestSuccess from '../../hooks/requestSuccess'; + +const { authenticate } = authentication.hooks; export default { before: { - all: [], + all: [authenticate('jwt')], find: [], get: [], create: [], @@ -12,7 +17,7 @@ export default { }, after: { - all: [], + all: [requestSuccess()], find: [], get: [], create: [], @@ -22,7 +27,7 @@ export default { }, error: { - all: [], + all: [requestFail()], find: [], get: [], create: [], diff --git a/Goobieverse/src/services/friends/friends.service.ts b/Goobieverse/src/services/friends/friends.service.ts index 10ef7f6f..17e3180b 100644 --- a/Goobieverse/src/services/friends/friends.service.ts +++ b/Goobieverse/src/services/friends/friends.service.ts @@ -14,6 +14,7 @@ declare module '../../declarations' { export default function (app: Application): void { const options = { paginate: app.get('paginate'), + id:'id' }; // Initialize our service with any options it requires diff --git a/Goobieverse/src/services/index.ts b/Goobieverse/src/services/index.ts index 15da0617..eb2ef53e 100644 --- a/Goobieverse/src/services/index.ts +++ b/Goobieverse/src/services/index.ts @@ -1,13 +1,17 @@ import { Application } from '../declarations'; -import profiles from './profiles/profiles.service'; +import profiles from './profiles/profiles.service'; // Don't remove this comment. It's needed to format import lines nicely. import users from './users/users.service'; import friends from './friends/friends.service'; import auth from './auth/auth.service'; +import email from './email/email.service'; +import connections from './connections/connections.service'; export default function (app: Application): void { app.configure(auth); app.configure(users); app.configure(friends); app.configure(profiles); + app.configure(email); + app.configure(connections); } diff --git a/Goobieverse/src/services/users/users.class.ts b/Goobieverse/src/services/users/users.class.ts index b6845494..6aa8490a 100644 --- a/Goobieverse/src/services/users/users.class.ts +++ b/Goobieverse/src/services/users/users.class.ts @@ -1,156 +1,139 @@ import { DatabaseService } from './../../dbservice/DatabaseService'; -import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; +import { MongoDBServiceOptions } from 'feathers-mongodb'; import { Application } from '../../declarations'; import config from '../../appconfig'; -import { Id, Params } from '@feathersjs/feathers'; +import { Params } from '@feathersjs/feathers'; import { AccountModel } from '../../interfaces/AccountModel'; -import { Response } from '../../utils/response'; -import { GenUUID, IsNotNullOrEmpty } from '../../utils/Misc'; +import { GenUUID } from '../../utils/Misc'; import { Roles } from '../../utils/sets/Roles'; -import { IsNullOrEmpty } from '../../utils/Misc'; +import { IsNullOrEmpty, isValidObject } from '../../utils/Misc'; +import { SArray } from '../../utils/vTypes'; +import { sendEmail } from '../../utils/mail'; +import path from 'path'; +import fsPromises from 'fs/promises'; + export class Users extends DatabaseService { + app: Application; //eslint-disable-next-line @typescript-eslint/no-unused-vars constructor(options: Partial, app: Application) { - super(options,app); + super(options, app); + this.app = app; } async create(data: AccountModel, params?: Params): Promise { - // try { - // const id = GenUUID(); - // const username = trim(data.username); - // const email = trim(data.email); - // const password = trim(data.password); - // const roles = [Roles.USER]; - // const friends : string[] = []; - // const connections : string[] = []; - // const whenCreated = new Date(); - // // const perPage = parseInt(params?.query?.per_page) || 10; - // // const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; - - // const accounts = await super.find({}); - // console.log(accounts, 'all accounts'); - // if ( - // id != '' && - // typeof id != 'undefined' && - // username != '' && - // typeof username != 'undefined' && - // email != '' && - // typeof email != 'undefined' && - // password != '' && - // typeof password != 'undefined' && - // typeof roles != 'undefined' - // ) { - // const newData = { ...data, id: id , roles:roles,friends:friends,connections:connections,whenCreated:whenCreated}; - // const UserData = await super - // .create(newData) - // .then((result: any) => { - // let finalData = {}; - // finalData = result == null ? {} : result; - // return finalData; - // }) - // .catch((error: any) => { - // return error; - // }); - // if (isValidObject(UserData) && !UserData.type) { - // return Response.success(UserData); - // } - // } else { - // return Response.error(messages.common_messages_record_added_failed); - // } - // } catch (error: any) { - // return Response.error(messages.common_messages_error); - // } - if (data) { + if (data.username && data.email && data.password) { const username : string = data.username; const email : string = data.email; const password : string = data.password; if (username) { - const accountsName: AccountModel[] = await this.findDataAsArray(config.dbCollections.accounts, { username: username } ); - console.log(accountsName, 'accountsName'); + const accountsName: AccountModel[] = await this.findDataToArray(config.dbCollections.accounts, { query: { username: username } }); const name = (accountsName as Array) ?.map((item) => item.username); - console.log(name); if (!name.includes(username)) { - const accountsEmail: AccountModel[] = await this.findDataAsArray(config.dbCollections.accounts, { email: email } ); + + const accountsEmail: AccountModel[] = await this.findDataToArray(config.dbCollections.accounts, { query:{email: email }}); const emailAddress = (accountsEmail as Array) ?.map((item) => item.email); if (!emailAddress.includes(email)) { - console.log('in'); + const id = GenUUID(); const roles = [Roles.USER]; const friends : string[] = []; const connections : string[] = []; const whenCreated = new Date(); - - const newUserData = { + const accountIsActive = true; + const accountWaitingVerification = false; + const accounts = await this.CreateData(config.dbCollections.accounts, { ...data, id: id, roles: roles, whenCreated: whenCreated, friends: friends, - connections:connections, - }; - - const accounts: AccountModel[] = await this.CreateData(config.dbCollections.accounts, { newUserData }); - console.log(accounts, 'accounts'); - // return Promise.resolve({ newUserData }); - // const AccountDetails = await this.CreateData(config.dbCollections.accounts, { query: {insertOne:{newUserData} } }); - // console.log(AccountDetails,'AccountDetails'); - }else{ - return Response.error('Email already exists'); + connections: connections, + accountIsActive: accountIsActive, + accountWaitingVerification :accountWaitingVerification + }); + if (isValidObject(accounts)) { + const emailToValidate = data.email; + const emailRegexp = /^[a-zA-Z0-9.!#$%&'+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)$/; + if (emailRegexp.test(emailToValidate)) { + try { + const adminAccountName = config.metaverseServer['base_admin_account']; + if (accounts.username === adminAccountName) { + if (IsNullOrEmpty(accounts.roles)) accounts.roles = []; + SArray.add(accounts.roles, Roles.ADMIN); + } + + const verificationURL = config.metaverse['metaverseServerUrl'] + + `/api/v1/account/verify/email?a=${accounts.id}&v=${accounts.id}`; + const metaverseName = config.metaverse['metaverseName']; + const shortMetaverseName = config.metaverse['metaverseNickName']; + const verificationFile = path.join(__dirname, '../..', config.metaverseServer['email_verification_email_body']); + + let emailBody = await fsPromises.readFile(verificationFile, 'utf-8'); + emailBody = emailBody.replace('VERIFICATION_URL', verificationURL) + .replace('METAVERSE_NAME', metaverseName) + .replace('SHORT_METAVERSE_NAME', shortMetaverseName); + + const email = { + from: 'khilan.odan@gmail.com', + to: accounts.email, + subject: `${shortMetaverseName} account verification`, + html: emailBody, + }; + const sendEmailVerificationLink = await sendEmail(this.app, email).then( + function (result:any) { + let sendEmailVerificationStatus = {}; + sendEmailVerificationStatus = result == null ? {} : result; + return sendEmailVerificationStatus; + } + ); + return Promise.resolve({ + accountId: accounts.id, + username: accounts.username, + accountIsActive: accounts.accountIsActive, + accountWaitingVerification:accounts.accountWaitingVerification + }); + } catch (error: any) { + throw new Error('Exception adding user: ' + error); + } + } + else { + throw new Error('Send valid Email address'); + } + } else { + throw new Error('Could not create account'); + } + } else { + throw new Error('Email already exists'); } } else { - return Response.error('Account already exists'); + throw new Error('Account already exists'); } } else { - return Response.error('Badly formatted username'); + throw new Error('Badly formatted username'); } } else { - return Response.error('Badly formatted request'); + throw new Error('Badly formatted request'); } } - // async find(params?: Params): Promise { - // try { - // const UserData = await super - // .find() - // .then((result: any) => { - // let finalData = {}; - // finalData = result == null ? {} : result; - // return finalData; - // }) - // .catch((error: any) => { - // return error; - // }); - // if (isValidArray(UserData.data)) { - // return Response.success(UserData.data); - // } else { - // return Response.error(messages.common_messages_records_not_available); - // } - // } catch (error: any) { - // return Response.error(messages.common_messages_error); - // } - // } + async find(params?: Params): Promise { + + const perPage = parseInt(params?.query?.per_page) || 10; + const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; + + + const user = await this.findDataToArray(config.dbCollections.accounts, { + query: { + accountIsActive: true , + $select: [ 'username', 'accountId' ], + $skip: skip, + $limit: perPage + } + }); + + return Promise.resolve({ user }); + } - // async get(id: string, params?: Params): Promise { - // try { - // const UserData = await super - // .get(id) - // .then((result: any) => { - // let finalData = {}; - // finalData = result == null ? {} : result; - // return finalData; - // }) - // .catch((error: any) => { - // return error; - // }); - // if (isValidObject(UserData) && !UserData.type) { - // return Response.success(UserData); - // } else { - // return Response.error(messages.common_messages_record_not_available); - // } - // } catch (error: any) { - // return Response.error(messages.common_messages_error); - // } - // } } diff --git a/Goobieverse/src/services/users/users.hooks.ts b/Goobieverse/src/services/users/users.hooks.ts index 68f8c420..34a59886 100644 --- a/Goobieverse/src/services/users/users.hooks.ts +++ b/Goobieverse/src/services/users/users.hooks.ts @@ -1,19 +1,18 @@ import { HooksObject } from '@feathersjs/feathers'; -import { myHook } from '../../hooks/userData'; -import * as feathersAuthentication from '@feathersjs/authentication'; import * as local from '@feathersjs/authentication-local'; import requestFail from '../../hooks/requestFail'; import requestSuccess from '../../hooks/requestSuccess'; -import { Perm } from '../../utils/Perm'; -import checkAccessToAccount from '../../hooks/checkAccessToAccount'; +// import { Perm } from '../../utils/Perm'; +// import checkAccessToAccount from '../../hooks/checkAccessToAccount'; +import * as authentication from '@feathersjs/authentication'; -const { authenticate } = feathersAuthentication.hooks; +const { authenticate } = authentication.hooks; const { hashPassword, protect } = local.hooks; export default { before: { all: [], - find: [], + find: [authenticate('jwt')], get: [], create: [hashPassword('password')], update: [hashPassword('password')], @@ -22,7 +21,7 @@ export default { }, after: { - all: [protect('password'),requestSuccess()], + all: [requestSuccess()], find: [], get: [], create: [], diff --git a/Goobieverse/src/services/users/users.service.ts b/Goobieverse/src/services/users/users.service.ts index a2bfc325..820552c4 100644 --- a/Goobieverse/src/services/users/users.service.ts +++ b/Goobieverse/src/services/users/users.service.ts @@ -14,6 +14,7 @@ declare module '../../declarations' { export default function (app: Application): void { const options = { paginate: app.get('paginate'), + id:'id' }; // Initialize our service with any options it requires diff --git a/Goobieverse/src/utils/mail.ts b/Goobieverse/src/utils/mail.ts new file mode 100644 index 00000000..8c2cbda9 --- /dev/null +++ b/Goobieverse/src/utils/mail.ts @@ -0,0 +1,19 @@ +import { Application } from '../declarations'; +import { BadRequest } from '@feathersjs/errors'; + +export async function sendEmail(app: Application, email: any): Promise { + if (email.to) { + email.html = email.html.replace(/&/g, '&'); + try { + const abc = await app + .service('email') + .create(email) + .then(function (result) { + return result; + }); + return abc; + } catch (error: any) { + return Promise.reject(new BadRequest(error)); + } + } +} \ No newline at end of file diff --git a/Goobieverse/test/services/connections.test.ts b/Goobieverse/test/services/connections.test.ts new file mode 100644 index 00000000..1d840d40 --- /dev/null +++ b/Goobieverse/test/services/connections.test.ts @@ -0,0 +1,8 @@ +import app from '../../src/app'; + +describe('\'connections\' service', () => { + it('registered the service', () => { + const service = app.service('connections'); + expect(service).toBeTruthy(); + }); +}); diff --git a/Goobieverse/test/services/email.test.ts b/Goobieverse/test/services/email.test.ts new file mode 100644 index 00000000..ecaedcc4 --- /dev/null +++ b/Goobieverse/test/services/email.test.ts @@ -0,0 +1,8 @@ +import app from '../../src/app'; + +describe('\'email\' service', () => { + it('registered the service', () => { + const service = app.service('email'); + expect(service).toBeTruthy(); + }); +}); diff --git a/Goobieverse/types.d.ts b/Goobieverse/types.d.ts new file mode 100644 index 00000000..0a5888aa --- /dev/null +++ b/Goobieverse/types.d.ts @@ -0,0 +1,5 @@ + + +declare module 'feathers-mailer' { + export default function Mailer(transport: any, defaults?: any): any; +} diff --git a/Goobieverse/verificationEmail.html b/Goobieverse/verificationEmail.html new file mode 100644 index 00000000..0d98d273 --- /dev/null +++ b/Goobieverse/verificationEmail.html @@ -0,0 +1,18 @@ +
+

+ You have created an account in the METAVERSE_NAME metaverse. +

+ +

+ Please verify the account email by following this link: +

+

+

+ See you in the virtual world! +

+

+ -- SHORT_METAVERSE_NAME admin +

+
\ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7b686b0f..57c1c0f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "iamus-metaverse-server", - "version": "2.4.9", + "version": "2.4.10", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -604,6 +604,11 @@ "is-symbol": "^1.0.2" } }, + "esbuild": { + "version": "0.11.23", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.23.tgz", + "integrity": "sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1379,6 +1384,20 @@ } } }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -1504,6 +1523,15 @@ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" }, + "ts-eager": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ts-eager/-/ts-eager-2.0.2.tgz", + "integrity": "sha512-xzFPL2z7mgLs0brZXaIHTm91Pjl/Cuu9AMKprgSuK+kIS2LjiG8fqqg4eqz3tgBy9OIdupb9w55pr7ea3JBB+Q==", + "requires": { + "esbuild": "^0.11.20", + "source-map-support": "^0.5.19" + } + }, "tslib": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", diff --git a/package.json b/package.json index 83d6da50..14be28bc 100755 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "morgan": "~1.9.1", "multer": "^1.4.2", "nodemailer": "^6.6.0", + "ts-eager": "^2.0.2", "unique-names-generator": "^4.5.0", "uuid": "^8.3.2", "winston": "^3.3.3" From 61f3056dccd5521b7862bebd026ece0db411b356 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Sat, 1 Jan 2022 21:13:14 +0530 Subject: [PATCH 014/128] added patch, delete , delete multiple, get method added in database service and implemented patch & delete accounts --- Goobieverse/src/appconfig.ts | 3 +- Goobieverse/src/dbservice/DatabaseService.ts | 30 ++- .../src/dbservice/DatabaseServiceOptions.ts | 1 - Goobieverse/src/hooks/checkAccess.ts | 134 +++++++++++++ Goobieverse/src/hooks/checkAccessToAccount.ts | 153 --------------- Goobieverse/src/hooks/isHasAuthToken.ts | 7 + .../src/services/accounts/accounts.class.ts | 176 ++++++++++++++++++ .../src/services/accounts/accounts.hooks.ts | 42 +++++ .../src/services/accounts/accounts.service.ts | 30 +++ Goobieverse/src/services/index.ts | 5 +- .../src/services/profiles/profiles.class.ts | 43 +---- .../src/services/profiles/profiles.hooks.ts | 13 +- .../src/services/profiles/profiles.service.ts | 3 +- Goobieverse/src/utils/Utils.ts | 17 +- Goobieverse/src/utils/messages.ts | 7 +- 15 files changed, 463 insertions(+), 201 deletions(-) create mode 100644 Goobieverse/src/hooks/checkAccess.ts delete mode 100644 Goobieverse/src/hooks/checkAccessToAccount.ts create mode 100644 Goobieverse/src/hooks/isHasAuthToken.ts create mode 100644 Goobieverse/src/services/accounts/accounts.class.ts create mode 100644 Goobieverse/src/services/accounts/accounts.hooks.ts create mode 100644 Goobieverse/src/services/accounts/accounts.service.ts diff --git a/Goobieverse/src/appconfig.ts b/Goobieverse/src/appconfig.ts index e672ec22..c320c394 100644 --- a/Goobieverse/src/appconfig.ts +++ b/Goobieverse/src/appconfig.ts @@ -119,7 +119,8 @@ const dbCollections = { domains : 'domains', accounts : 'accounts', places : 'places', - tokens : 'tokens' + tokens : 'tokens', + requests:'requests' }; diff --git a/Goobieverse/src/dbservice/DatabaseService.ts b/Goobieverse/src/dbservice/DatabaseService.ts index 29e716bf..a904ab76 100644 --- a/Goobieverse/src/dbservice/DatabaseService.ts +++ b/Goobieverse/src/dbservice/DatabaseService.ts @@ -1,10 +1,10 @@ -import { Service } from 'feathers-mongodb'; +import { Service, } from 'feathers-mongodb'; import { Application } from '../declarations'; -import { HookContext, Paginated } from '@feathersjs/feathers'; +import { HookContext, Paginated, Id, } from '@feathersjs/feathers'; import { DatabaseServiceOptions } from './DatabaseServiceOptions'; import { Db, Collection, Document, FindCursor, WithId,Filter } from 'mongodb'; import { IsNotNullOrEmpty, IsNullOrEmpty } from '../utils/Misc'; - +import { VKeyedCollection } from '../utils/vTypes'; export class DatabaseService extends Service { app?: Application; @@ -37,10 +37,15 @@ export class DatabaseService extends Service { return this.Model; } + async getData(tableName: string, id:Id ): Promise{ + await (this.getService(tableName)); + return super.get(id); + } + + async findData(tableName: string, filter?:Filter ): Promise | any[]>{ await (this.getService(tableName)); if(IsNotNullOrEmpty(filter)){ - console.log(filter); return await super.find(filter!); } else { return await super.find(); @@ -57,4 +62,21 @@ export class DatabaseService extends Service { } } + + async patchData(tableName:string,id:Id,data:VKeyedCollection){ + console.log(tableName + ' ' + id); + await (this.getService(tableName)); + return await super.patch(id,data); + } + + async deleteData(tableName:string,id:Id,filter?:Filter){ + await (this.getService(tableName)); + return await super.remove(id,filter); + } + + async deleteMultipleData(tableName:string,filter?:Filter){ + await (this.getService(tableName)); + return await super.remove(null,filter); + } + } \ No newline at end of file diff --git a/Goobieverse/src/dbservice/DatabaseServiceOptions.ts b/Goobieverse/src/dbservice/DatabaseServiceOptions.ts index 99a05d9b..7b0a75e4 100644 --- a/Goobieverse/src/dbservice/DatabaseServiceOptions.ts +++ b/Goobieverse/src/dbservice/DatabaseServiceOptions.ts @@ -1,4 +1,3 @@ import { MongoDBServiceOptions } from 'feathers-mongodb'; -import { Collection } from 'mongodb'; export interface DatabaseServiceOptions extends MongoDBServiceOptions {} \ No newline at end of file diff --git a/Goobieverse/src/hooks/checkAccess.ts b/Goobieverse/src/hooks/checkAccess.ts new file mode 100644 index 00000000..a5391468 --- /dev/null +++ b/Goobieverse/src/hooks/checkAccess.ts @@ -0,0 +1,134 @@ +import { AccountModel } from '../interfaces/AccountModel'; +import { Application, Paginated } from '@feathersjs/feathers'; +import { HookContext } from '@feathersjs/feathers'; +import { IsNotNullOrEmpty } from '../utils/Misc'; +import { Perm } from '../utils/Perm'; +import { isAdmin } from '../utils/Utils'; +import config from '../appconfig'; +import {HTTPStatusCode} from '../utils/response'; +import {Availability} from '../utils/sets/Availability'; +import { SArray } from '../utils/vTypes'; +import { DatabaseService } from '../dbservice/DatabaseService'; +import { messages } from '../utils/messages'; + +export default (collection: string ,pRequiredAccess: Perm[]) => { + return async (context: HookContext): Promise => { + const dbService = new DatabaseService({},undefined,context); + const loginUser = context.params.user; + + const entryDataArray = await dbService.findDataToArray(collection,{query:{id:context.id}}); + let canAccess = false; + + let pTargetEntity: any; + if(IsNotNullOrEmpty(entryDataArray)){ + pTargetEntity = entryDataArray[0]; + } + + + if (IsNotNullOrEmpty(pTargetEntity) && pTargetEntity) { + for (const perm of pRequiredAccess) { + switch (perm) { + case Perm.ALL: + canAccess = true; + break; + case Perm.PUBLIC: + // The target entity is publicly visible + // Mostly AccountEntities that must have an 'availability' field + if (pTargetEntity?.hasOwnProperty('availability')) { + if ((pTargetEntity).availability.includes(Availability.ALL)) { + canAccess = true; + } + } + break; + case Perm.DOMAIN: + // requestor is a domain and it's account is the domain's sponsoring account + if (loginUser) { + if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { + canAccess = loginUser.id === (pTargetEntity as any).sponsorAccountId; + } + else { + // Super special case where domain doesn't have a sponsor but has an api_key. + // In this case, the API_KEY is put in the accountId field of the DOMAIN scoped AuthToken + if (pTargetEntity.hasOwnProperty('apiKey')) { + canAccess = loginUser.id === (pTargetEntity as any).apiKey; + } + } + } + break; + case Perm.OWNER: + // The requestor wants to be the same account as the target entity + if (loginUser && pTargetEntity.hasOwnProperty('id')) { + canAccess = (loginUser.id === (pTargetEntity as any).id); + } + if (loginUser && !canAccess && pTargetEntity.hasOwnProperty('accountId')) { + canAccess = loginUser.id === (pTargetEntity as any).accountId; + } + break; + case Perm.FRIEND: + // The requestor is a 'friend' of the target entity + if (loginUser && pTargetEntity.hasOwnProperty('friends')) { + const targetFriends: string[] = (pTargetEntity as AccountModel).friends; + if (targetFriends) { + canAccess = SArray.hasNoCase(targetFriends, loginUser.username); + } + } + break; + case Perm.CONNECTION: + // The requestor is a 'connection' of the target entity + if (loginUser && pTargetEntity.hasOwnProperty('connections')) { + const targetConnections: string[] = (pTargetEntity as AccountModel).connections; + if (targetConnections) { + canAccess = SArray.hasNoCase(targetConnections, loginUser.username); + } + } + break; + case Perm.ADMIN: + // If the authToken is an account, has access if admin + if (loginUser) { + canAccess = isAdmin(loginUser as AccountModel); + } + break; + case Perm.SPONSOR: + // Requestor is a regular account and is the sponsor of the domain + if (loginUser) { + if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { + canAccess = loginUser.id === (pTargetEntity as any).sponsorAccountId; + } + } + break; + case Perm.MANAGER: + // See if requesting account is in the list of managers of this entity + if (loginUser) { + if (pTargetEntity.hasOwnProperty('managers')) { + const managers: string[] = (pTargetEntity as any).managers; + if (managers && managers.includes(loginUser.username.toLowerCase())) { + canAccess = true; + } + } + } + break; + case Perm.DOMAINACCESS: + // Target entity has a domain reference and verify the requestor is able to reference that domain + if (loginUser && pTargetEntity.hasOwnProperty('domainId')) { + const aDomains = await dbService.findDataToArray(config.dbCollections.domains,{query:{id:(pTargetEntity as any).domainId}}); + if (IsNotNullOrEmpty(aDomains)) { + canAccess = aDomains[0].sponsorAccountId === loginUser.id; + } + } + break; + } + if(canAccess)break; + } + }else{ + context.statusCode = HTTPStatusCode.NotFound; + throw new Error(messages.common_messages_target_account_notfound); + } + + if (!canAccess){ + context.statusCode = HTTPStatusCode.Unauthorized; + throw new Error(messages.common_messages_unauthorized); + } + + return context; + }; +}; \ No newline at end of file diff --git a/Goobieverse/src/hooks/checkAccessToAccount.ts b/Goobieverse/src/hooks/checkAccessToAccount.ts deleted file mode 100644 index 057ea941..00000000 --- a/Goobieverse/src/hooks/checkAccessToAccount.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { AccountModel } from './../interfaces/AccountModel'; -import { Application, Paginated } from '@feathersjs/feathers'; -import { HookContext } from '@feathersjs/feathers'; -import { IsNotNullOrEmpty } from '../utils/Misc'; -import { Perm } from '../utils/Perm'; -import config from '../appconfig'; -import {HTTPStatusCode} from '../utils/response'; -import {Availability} from '../utils/sets/Availability'; -import { SArray } from '../utils/vTypes'; -import { DatabaseService } from '../dbservice/DatabaseService'; - - -export default (pRequiredAccess: Perm[]) => { - return async (context: HookContext): Promise => { - const dbService = new DatabaseService({},undefined,context); - - const accounts = await dbService.findData(config.dbCollections.accounts,{query:{id:context.id}}); - console.log(accounts); - let canAccess = false; - - let pTargetEntity:AccountModel | undefined; - if(accounts instanceof Array){ - pTargetEntity = (accounts as Array)[0]; - }else if(IsNotNullOrEmpty(accounts.data[0])){ - pTargetEntity = accounts.data[0]; - } - - if (IsNotNullOrEmpty(pTargetEntity)) { - for (const perm of pRequiredAccess) { - switch (perm) { - case Perm.ALL: - canAccess = true; - break; - case Perm.PUBLIC: - // The target entity is publicly visible - // Mostly AccountEntities that must have an 'availability' field - if (pTargetEntity?.hasOwnProperty('availability')) { - if ((pTargetEntity).availability.includes(Availability.ALL)) { - canAccess = true; - } - } - break; - /* case Perm.DOMAIN: - // requestor is a domain and it's account is the domain's sponsoring account - if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.DOMAIN)) { - if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { - canAccess = pAuthToken.accountId === (pTargetEntity as any).sponsorAccountId; - } - else { - // Super special case where domain doesn't have a sponsor but has an api_key. - // In this case, the API_KEY is put in the accountId field of the DOMAIN scoped AuthToken - if (pTargetEntity.hasOwnProperty('apiKey')) { - canAccess = pAuthToken.accountId === (pTargetEntity as any).apiKey; - } - } - } - break;*/ - case Perm.OWNER: - // The requestor wants to be the same account as the target entity - if (pAuthToken && pTargetEntity.hasOwnProperty('id')) { - canAccess = pAuthToken.accountId === (pTargetEntity as AccountEntity).id; - } - if (!canAccess && pTargetEntity.hasOwnProperty('accountId')) { - canAccess = pAuthToken.accountId === (pTargetEntity as any).accountId; - } - break; - case Perm.FRIEND: - // The requestor is a 'friend' of the target entity - if (pAuthToken && pTargetEntity.hasOwnProperty('friends')) { - const targetFriends: string[] = (pTargetEntity as AccountEntity).friends; - if (targetFriends) { - requestingAccount = requestingAccount ?? await Accounts.getAccountWithId(pAuthToken.accountId); - canAccess = SArray.hasNoCase(targetFriends, requestingAccount.username); - } - } - break; - case Perm.CONNECTION: - // The requestor is a 'connection' of the target entity - if (pAuthToken && pTargetEntity.hasOwnProperty('connections')) { - const targetConnections: string[] = (pTargetEntity as AccountEntity).connections; - if (targetConnections) { - requestingAccount = requestingAccount ?? await Accounts.getAccountWithId(pAuthToken.accountId); - canAccess = SArray.hasNoCase(targetConnections, requestingAccount.username); - } - } - break; - case Perm.ADMIN: - if (pAuthToken && Tokens.isSpecialAdminToken(pAuthToken)) { - Logger.cdebug('field-setting', `checkAccessToEntity: isSpecialAdminToken`); - canAccess = true; - } - else { - // If the authToken is an account, has access if admin - if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.OWNER)) { - Logger.cdebug('field-setting', `checkAccessToEntity: admin. auth.AccountId=${pAuthToken.accountId}`); - requestingAccount = requestingAccount ?? await Accounts.getAccountWithId(pAuthToken.accountId); - canAccess = Accounts.isAdmin(requestingAccount); - } - } - break; - case Perm.SPONSOR: - // Requestor is a regular account and is the sponsor of the domain - if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.OWNER)) { - if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { - Logger.cdebug('field-setting', `checkAccessToEntity: authToken is domain. auth.AccountId=${pAuthToken.accountId}, sponsor=${(pTargetEntity as any).sponsorAccountId}`); - canAccess = pAuthToken.accountId === (pTargetEntity as DomainEntity).sponsorAccountId; - } - } - break; - case Perm.MANAGER: - // See if requesting account is in the list of managers of this entity - if (pAuthToken && SArray.has(pAuthToken.scope, TokenScope.OWNER)) { - if (pTargetEntity.hasOwnProperty('managers')) { - requestingAccount = requestingAccount ?? await Accounts.getAccountWithId(pAuthToken.accountId); - if (requestingAccount) { - const managers: string[] = (pTargetEntity as DomainEntity).managers; - // Logger.debug(`Perm.MANAGER: managers=${JSON.stringify(managers)}, target=${requestingAccount.username}`); - if (managers && managers.includes(requestingAccount.username.toLowerCase())) { - canAccess = true; - } - } - } - } - break; - case Perm.DOMAINACCESS: - // Target entity has a domain reference and verify the requestor is able to reference that domain - if (pAuthToken && pTargetEntity.hasOwnProperty('domainId')) { - const aDomain = await Domains.getDomainWithId((pTargetEntity as any).domainId); - if (aDomain) { - canAccess = aDomain.sponsorAccountId === pAuthToken.accountId; - } - } - break;*/ - default: - canAccess = false; - break; - } - // If some permission allows access, we are done - - } - }else{ - context.statusCode = HTTPStatusCode.NotFound; - throw new Error('Target account not found'); - } - - if (!canAccess){ - context.statusCode = HTTPStatusCode.Unauthorized; - throw new Error('Unauthorized'); - } - - return context; - }; -}; \ No newline at end of file diff --git a/Goobieverse/src/hooks/isHasAuthToken.ts b/Goobieverse/src/hooks/isHasAuthToken.ts new file mode 100644 index 00000000..cacc48c1 --- /dev/null +++ b/Goobieverse/src/hooks/isHasAuthToken.ts @@ -0,0 +1,7 @@ +import { HookContext } from '@feathersjs/feathers'; + +export default (): any => { + return (context:HookContext): boolean => { + return !!(context.params.headers?.authorization&& context.params.headers.authorization !== 'null'&& context.params.headers.authorization !== ''); + }; +}; \ No newline at end of file diff --git a/Goobieverse/src/services/accounts/accounts.class.ts b/Goobieverse/src/services/accounts/accounts.class.ts new file mode 100644 index 00000000..b43c5c82 --- /dev/null +++ b/Goobieverse/src/services/accounts/accounts.class.ts @@ -0,0 +1,176 @@ + +import { DatabaseServiceOptions } from './../../dbservice/DatabaseServiceOptions'; +import { Params, Id, NullableId } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { DatabaseService } from './../../dbservice/DatabaseService'; +import config from '../../appconfig'; +import { AccountModel } from '../../interfaces/AccountModel'; +import { DomainModel } from './../../interfaces/DomainModel'; +import { buildAccountInfo } from '../../responsebuilder/accountsBuilder'; +import { IsNotNullOrEmpty } from '../../utils/Misc'; +import { isAdmin,dateWhenNotOnline,couldBeDomainId,validateEmail } from '../../utils/Utils'; +import { messages } from '../../utils/messages'; +import { VKeyedCollection,SArray } from '../../utils/vTypes'; + +export class Accounts extends DatabaseService { + constructor(options: Partial, app: Application) { + super(options,app); + } + + async find(params?: Params): Promise { + + const loginUserId = params?.user?.id ?? ''; + const perPage = parseInt(params?.query?.per_page) || 10; + const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; + + + // Passed the request, get the filter parameters from the query. + // Here we pre-process the parameters to make the DB query construction quicker. + // filter=connections|friends|all + // status=online|domainId + // search=wildcardSearchString + // The administrator can specify an account to limit requests to + // acct = account id + //asAdmin=true: if logged in account is administrator, list all accounts. Value is optional. + let asAdmin = params?.query?.asAdmin === 'true'?true:false; + const filter:string[] = (params?.query?.filter ?? '').split(','); + const status:string = params?.query?.status ?? ''; + const targetAccount = params?.query?.account ?? ''; + + + const filterQuery: any = {}; + + if(asAdmin && IsNotNullOrEmpty(params?.user) && isAdmin(params?.user as AccountModel)){ + asAdmin = true; + }else{ + asAdmin =false; + } + + if(filter.length > 0){ + if(!filter.includes('all')){ + if(filter.includes('friends') && (params?.user?.friends??[]).length > 0){ + filterQuery.friends = {$in: params?.user?.friends}; + } + if(filter.includes('connections') && (params?.user?.connections??[]).length > 0){ + filterQuery.connections = {$in: params?.user?.connections}; + } + } + } + + if(IsNotNullOrEmpty(status)){ + if (status ==='online'){ + filterQuery.timeOfLastHeartbeat = {$gte:dateWhenNotOnline()}; + }else if(couldBeDomainId(status)){ + filterQuery.locationDomainId = status; + } + } + + if(!asAdmin){ + filterQuery.id = loginUserId; + }else if(IsNotNullOrEmpty(targetAccount)){ + filterQuery.id = targetAccount; + } + + const accountData = await this.findData(config.dbCollections.accounts,{ + query:{ + ...filterQuery, + $skip: skip, + $limit: perPage + }, + }); + + let accountsList:AccountModel[] = []; + + if(accountData instanceof Array){ + accountsList = accountData as Array; + }else{ + accountsList = accountData.data as Array; + } + + const accounts: Array = []; + + (accountsList as Array)?.forEach(async (element) => { + + accounts.push(await buildAccountInfo(element)); + }); + return Promise.resolve({ accounts }); + } + + async get(id: Id, params: Params): Promise { + const objAccount = await this.getData(config.dbCollections.accounts,id); + if(IsNotNullOrEmpty(objAccount)){ + const account = await buildAccountInfo(objAccount); + return Promise.resolve({ account }); + } else { + throw new Error(messages.common_messages_target_account_notfound); + } + } + + async patch(id: NullableId, data: any, params: Params): Promise { + if(IsNotNullOrEmpty(id)){ + if(IsNotNullOrEmpty(params?.user) && isAdmin(params?.user as AccountModel) || id === params?.user?.id){ + const valuesToSet = data.accounts??{}; + const updates: VKeyedCollection = {}; + if(IsNotNullOrEmpty(valuesToSet.email)) { + if(!validateEmail(valuesToSet.email)){ + throw new Error(messages.common_messages_email_validation_error); + } + const accountData = await this.findDataToArray(config.dbCollections.accounts,{query:{email: valuesToSet.email}}); + if(accountData.length>0 && accountData[0].id !== id){ + throw new Error(messages.common_messages_user_email_link_error); + } + updates.email = valuesToSet.email; + } + if(IsNotNullOrEmpty(valuesToSet.public_key)) { + updates.public_key = valuesToSet.public_key; + } + + if (valuesToSet.hasOwnProperty('images')) { + if (IsNotNullOrEmpty(valuesToSet.images.hero)) { + updates.imagesHero = valuesToSet.images.hero; + } + + if (IsNotNullOrEmpty(valuesToSet.images.tiny)) { + updates.imagesTiny = valuesToSet.images.tiny; + } + + if (IsNotNullOrEmpty(valuesToSet.images.thumbnail)) { + updates.imagesThumbnail = valuesToSet.images.thumbnail; + } + } + await this.patchData(config.dbCollections.accounts,id!,updates); + return Promise.resolve(); + }else{ + throw new Error(messages.common_messages_unauthorized); + } + } else { + throw new Error(messages.common_messages_target_account_notfound); + } + } + + async remove(id: NullableId, params?: Params): Promise { + if(IsNotNullOrEmpty(id)){ + const account = await this.getData(config.dbCollections.accounts,id!); + + if(IsNotNullOrEmpty(account)){ + this.deleteData(config.dbCollections.accounts,id!); + const accounts:AccountModel[] = await this.findDataToArray(config.dbCollections.accounts, {query:{$or:[{connections:{$in: [account.username] }},{friends:{$in:[account.username] }}]}}); + + for(const element of accounts){ + SArray.remove(element.connections,account.username); + SArray.remove(element.friends,account.username); + await super.patchData(config.dbCollections.accounts,element.id,{connections:element.connections,friends:element.friends}); + } + + await this.deleteMultipleData(config.dbCollections.domains,{query:{sponsorAccountId:account.id}}); + await this.deleteMultipleData(config.dbCollections.places,{query:{accountId:account.id}}); + + return Promise.resolve(); + }else{ + throw new Error(messages.common_messages_target_account_notfound); + } + }else{ + throw new Error(messages.common_messages_target_account_notfound); + } + } +} diff --git a/Goobieverse/src/services/accounts/accounts.hooks.ts b/Goobieverse/src/services/accounts/accounts.hooks.ts new file mode 100644 index 00000000..bcbcc7da --- /dev/null +++ b/Goobieverse/src/services/accounts/accounts.hooks.ts @@ -0,0 +1,42 @@ +import * as feathersAuthentication from '@feathersjs/authentication'; +const { authenticate } = feathersAuthentication.hooks; +import requestSuccess from '../../hooks/requestSuccess'; +import requestFail from '../../hooks/requestFail'; +import checkAccessToAccount from '../../hooks/checkAccess'; +import config from '../../appconfig'; +import {Perm} from '../../utils/Perm'; +import isHasAuthToken from '../../hooks/isHasAuthToken'; +import { iff } from 'feathers-hooks-common'; +import { disallow } from 'feathers-hooks-common'; + +export default { + before: { + all: [], + find: [authenticate('jwt')], + get: [iff(isHasAuthToken(),authenticate('jwt')),checkAccessToAccount(config.dbCollections.accounts,[Perm.PUBLIC,Perm.OWNER,Perm.ADMIN])], + create: [disallow()], + update: [disallow()], + patch: [authenticate('jwt')], + remove: [authenticate('jwt'),checkAccessToAccount(config.dbCollections.accounts,[Perm.ADMIN])] + }, + + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, + + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } +}; diff --git a/Goobieverse/src/services/accounts/accounts.service.ts b/Goobieverse/src/services/accounts/accounts.service.ts new file mode 100644 index 00000000..ed3e116d --- /dev/null +++ b/Goobieverse/src/services/accounts/accounts.service.ts @@ -0,0 +1,30 @@ +// Initializes the `accounts` service on path `/accounts` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { Accounts } from './accounts.class'; +import hooks from './accounts.hooks'; + +// Add this service to the service type index +declare module '../../declarations' { + interface ServiceTypes { + 'accounts': Accounts & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get('paginate'), + id:'id', + multi:['remove'] + }; + + // Initialize our service with any options it requires + app.use('/accounts', new Accounts(options, app)); + + //app.use('/accounts/:accountId/field/:fieldName', app.service('accounts')); + + // Get our initialized service so that we can register hooks + const service = app.service('accounts'); + + service.hooks(hooks); +} diff --git a/Goobieverse/src/services/index.ts b/Goobieverse/src/services/index.ts index 15da0617..751c9336 100644 --- a/Goobieverse/src/services/index.ts +++ b/Goobieverse/src/services/index.ts @@ -1,13 +1,16 @@ import { Application } from '../declarations'; -import profiles from './profiles/profiles.service'; +import profiles from './profiles/profiles.service'; // Don't remove this comment. It's needed to format import lines nicely. import users from './users/users.service'; import friends from './friends/friends.service'; import auth from './auth/auth.service'; +import accounts from './accounts/accounts.service'; + export default function (app: Application): void { app.configure(auth); app.configure(users); app.configure(friends); app.configure(profiles); + app.configure(accounts); } diff --git a/Goobieverse/src/services/profiles/profiles.class.ts b/Goobieverse/src/services/profiles/profiles.class.ts index 6a36727d..a97f0cd6 100644 --- a/Goobieverse/src/services/profiles/profiles.class.ts +++ b/Goobieverse/src/services/profiles/profiles.class.ts @@ -1,17 +1,17 @@ +import { DatabaseServiceOptions } from './../../dbservice/DatabaseServiceOptions'; import { DatabaseService } from './../../dbservice/DatabaseService'; import { DomainModel } from './../../interfaces/DomainModel'; import { AccountModel } from '../../interfaces/AccountModel'; import config from '../../appconfig'; import { Availability } from '../../utils/sets/Availability'; import { Params, Id } from '@feathersjs/feathers'; -import { MongoDBServiceOptions } from 'feathers-mongodb'; import { Application } from '../../declarations'; import { buildAccountProfile } from '../../responsebuilder/accountsBuilder'; import { IsNotNullOrEmpty } from '../../utils/Misc'; - +import { messages } from '../../utils/messages'; export class Profiles extends DatabaseService { - constructor(options: Partial, app: Application) { + constructor(options: Partial, app: Application) { super(options,app); } @@ -44,15 +44,7 @@ export class Profiles extends DatabaseService { self.indexOf(value) === index && value !== undefined ); - const domainData= await this.findData(config.dbCollections.domains,{ query:{id: { $in: domainIds }}}); - - let domains:DomainModel[] = []; - - if(domainData instanceof Array){ - domains = domainData as Array; - }else{ - domains = domainData.data as Array; - } + const domains= await this.findDataToArray(config.dbCollections.domains,{ query:{id: { $in: domainIds }}}); const profiles: Array = []; @@ -70,35 +62,16 @@ export class Profiles extends DatabaseService { } async get(id: Id, params: Params): Promise { - const accountData = await this.findData(config.dbCollections.accounts,{query:{id:id}}); + const account = await this.getData(config.dbCollections.accounts,id); - let accounts:AccountModel[] = []; - - if(accountData instanceof Array){ - accounts = accountData as Array; - }else{ - accounts = accountData.data as Array; - } - - if(IsNotNullOrEmpty(accounts)){ - const account = (accounts as Array)[0]; - - const domainData = await this.findData(config.dbCollections.domains,{ id: { $eq: account.locationDomainId } }); - - let domains:DomainModel[] = []; - - if(domainData instanceof Array){ - domains = domainData as Array; - }else{ - domains = domainData.data as Array; - } - + if(IsNotNullOrEmpty(account)){ + const domains = await this.findDataToArray(config.dbCollections.domains,{ id: { $eq: account.locationDomainId } }); let domainModel: any; if(IsNotNullOrEmpty(domains)){domainModel = domains[0];} const profile = await buildAccountProfile(account, domainModel); return Promise.resolve({ profile }); }else{ - return Promise.resolve({}); + throw new Error(messages.common_messages_target_profile_notfound); } } } diff --git a/Goobieverse/src/services/profiles/profiles.hooks.ts b/Goobieverse/src/services/profiles/profiles.hooks.ts index fd8a4416..33e0bb88 100644 --- a/Goobieverse/src/services/profiles/profiles.hooks.ts +++ b/Goobieverse/src/services/profiles/profiles.hooks.ts @@ -1,13 +1,20 @@ import { disallow } from 'feathers-hooks-common'; -import checkAccessToAccount from '../../hooks/checkAccessToAccount'; +import checkAccessToAccount from '../../hooks/checkAccess'; import requestFail from '../../hooks/requestFail'; import requestSuccess from '../../hooks/requestSuccess'; import {Perm} from '../../utils/Perm'; +import config from '../../appconfig'; +import { iff } from 'feathers-hooks-common'; +import isHasAuthToken from '../../hooks/isHasAuthToken'; +import * as feathersAuthentication from '@feathersjs/authentication'; +const { authenticate } = feathersAuthentication.hooks; + + export default { before: { - all: [], + all: [iff(isHasAuthToken(),authenticate('jwt'))], find: [], - get: [checkAccessToAccount([Perm.PUBLIC,Perm.OWNER,Perm.ADMIN])], + get: [checkAccessToAccount(config.dbCollections.accounts,[Perm.PUBLIC,Perm.OWNER,Perm.ADMIN])], create: [disallow()], update: [disallow()], patch: [disallow()], diff --git a/Goobieverse/src/services/profiles/profiles.service.ts b/Goobieverse/src/services/profiles/profiles.service.ts index c1a4c30b..b146299f 100644 --- a/Goobieverse/src/services/profiles/profiles.service.ts +++ b/Goobieverse/src/services/profiles/profiles.service.ts @@ -13,7 +13,8 @@ declare module '../../declarations' { export default function (app: Application): void { const options = { - paginate: app.get('paginate') + paginate: app.get('paginate'), + id:'id', }; // Initialize our service with any options it requires diff --git a/Goobieverse/src/utils/Utils.ts b/Goobieverse/src/utils/Utils.ts index 95a35e5f..64b410c4 100644 --- a/Goobieverse/src/utils/Utils.ts +++ b/Goobieverse/src/utils/Utils.ts @@ -1,6 +1,6 @@ +import { AccountModel } from './../interfaces/AccountModel'; import { SArray } from './vTypes'; import { Roles } from './sets/Roles'; -import { AccountModel } from '../interfaces/AccountModel'; import config from '../appconfig'; @@ -41,3 +41,18 @@ export function isOnline(pAcct: AccountModel): boolean { } return false; } + +export function couldBeDomainId(pId: string): boolean { + return pId.length === 36; +} + +// Return the ISODate when an account is considered offline +export function dateWhenNotOnline(): Date { + const whenOffline = new Date(Date.now() - config.metaverseServer.heartbeat_seconds_until_offline); + return whenOffline; +} + +export function validateEmail(email:string):boolean{ + const emailRegexp = /^[a-zA-Z0-9.!#$%&'+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)$/; + return emailRegexp.test(email); +} \ No newline at end of file diff --git a/Goobieverse/src/utils/messages.ts b/Goobieverse/src/utils/messages.ts index 8aa24979..060210e7 100644 --- a/Goobieverse/src/utils/messages.ts +++ b/Goobieverse/src/utils/messages.ts @@ -4,5 +4,10 @@ export const messages = { common_messages_record_not_available: 'Record is not available.', common_messages_records_available: 'Records are available.', common_messages_records_not_available: 'Records are not available.', - common_messages_record_added_failed:'Failed to add record!' + common_messages_record_added_failed:'Failed to add record!', + common_messages_target_profile_notfound:'Target profile not found', + common_messages_target_account_notfound:'Target account not found', + common_messages_user_email_link_error:'email already exists for another account', + common_messages_unauthorized:'Unauthorized', + common_messages_email_validation_error:'Invalid email address' }; From a14040bbce2f2c1995c7d9250322c3262bade5b4 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Sat, 1 Jan 2022 21:19:38 +0530 Subject: [PATCH 015/128] acced accounts test file --- Goobieverse/test/services/accounts.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Goobieverse/test/services/accounts.test.ts diff --git a/Goobieverse/test/services/accounts.test.ts b/Goobieverse/test/services/accounts.test.ts new file mode 100644 index 00000000..b9223f3f --- /dev/null +++ b/Goobieverse/test/services/accounts.test.ts @@ -0,0 +1,8 @@ +import app from '../../src/app'; + +describe('\'accounts\' service', () => { + it('registered the service', () => { + const service = app.service('accounts'); + expect(service).toBeTruthy(); + }); +}); From b68b9909fc65c3bc1cdcc004d68e134734f778f9 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Mon, 3 Jan 2022 12:16:56 +0530 Subject: [PATCH 016/128] added eslint --- Goobieverse/.eslintrc.json | 2 +- Goobieverse/package.json | 2 +- Goobieverse/src/app.hooks.ts | 54 ++-- Goobieverse/src/app.ts | 3 +- Goobieverse/src/appconfig.ts | 186 +++++------ Goobieverse/src/authentication.ts | 10 +- Goobieverse/src/channels.ts | 78 ++--- .../src/controllers/PublicRoutesController.ts | 48 +-- Goobieverse/src/dbservice/DatabaseService.ts | 153 +++++---- .../src/dbservice/DatabaseServiceOptions.ts | 4 +- Goobieverse/src/hooks/checkAccess.ts | 293 +++++++++++------- Goobieverse/src/hooks/isHasAuthToken.ts | 6 +- Goobieverse/src/hooks/requestFail.ts | 13 +- Goobieverse/src/hooks/requestSuccess.ts | 10 +- Goobieverse/src/index.ts | 4 +- Goobieverse/src/interfaces/AccountModel.ts | 70 ++--- Goobieverse/src/interfaces/DomainModel.ts | 64 ++-- Goobieverse/src/interfaces/MetaverseInfo.ts | 14 +- Goobieverse/src/interfaces/PlaceModel.ts | 43 ++- Goobieverse/src/interfaces/RequestModal.ts | 34 +- Goobieverse/src/logger.ts | 18 +- Goobieverse/src/middleware/index.ts | 3 +- Goobieverse/src/mongodb.ts | 28 +- .../src/responsebuilder/accountsBuilder.ts | 110 +++---- .../src/responsebuilder/domainsBuilder.ts | 134 ++++---- .../src/responsebuilder/placesBuilder.ts | 148 ++++----- .../src/services/accounts/accounts.class.ts | 273 ++++++++-------- .../src/services/accounts/accounts.hooks.ts | 54 ++-- .../src/services/accounts/accounts.service.ts | 26 +- Goobieverse/src/services/auth/auth.class.ts | 14 +- Goobieverse/src/services/auth/auth.hooks.ts | 56 ++-- Goobieverse/src/services/auth/auth.service.ts | 16 +- .../services/connections/connections.class.ts | 36 +-- .../services/connections/connections.hooks.ts | 54 ++-- .../connections/connections.service.ts | 18 +- Goobieverse/src/services/email/email.class.ts | 11 +- Goobieverse/src/services/email/email.hooks.ts | 54 ++-- .../src/services/email/email.service.ts | 8 +- .../src/services/friends/friends.class.ts | 77 +++-- .../src/services/friends/friends.hooks.ts | 54 ++-- .../src/services/friends/friends.service.ts | 18 +- Goobieverse/src/services/index.ts | 14 +- .../src/services/profiles/profiles.class.ts | 115 +++---- .../src/services/profiles/profiles.hooks.ts | 54 ++-- .../src/services/profiles/profiles.service.ts | 18 +- Goobieverse/src/services/users/users.class.ts | 262 +++++++++------- Goobieverse/src/services/users/users.hooks.ts | 56 ++-- .../src/services/users/users.service.ts | 18 +- Goobieverse/src/utils/Misc.ts | 192 ++++++------ Goobieverse/src/utils/Perm.ts | 22 +- Goobieverse/src/utils/Utils.ts | 42 +-- Goobieverse/src/utils/mail.ts | 26 +- Goobieverse/src/utils/messages.ts | 23 +- Goobieverse/src/utils/response.ts | 12 +- Goobieverse/src/utils/sets/Availability.ts | 16 +- Goobieverse/src/utils/sets/Maturity.ts | 28 +- Goobieverse/src/utils/sets/Roles.ts | 14 +- Goobieverse/src/utils/sets/Visibility.ts | 30 +- Goobieverse/src/utils/vTypes.ts | 52 ++-- Goobieverse/test/app.test.ts | 89 +++--- Goobieverse/test/authentication.test.ts | 46 +-- Goobieverse/test/services/accounts.test.ts | 8 +- Goobieverse/test/services/connections.test.ts | 8 +- Goobieverse/test/services/email.test.ts | 8 +- Goobieverse/test/services/friends.test.ts | 8 +- .../test/services/metaverse_info.test.ts | 8 +- Goobieverse/test/services/profiles.test.ts | 8 +- Goobieverse/test/services/users.test.ts | 8 +- 68 files changed, 1798 insertions(+), 1688 deletions(-) diff --git a/Goobieverse/.eslintrc.json b/Goobieverse/.eslintrc.json index d0fbe201..cc295f5a 100644 --- a/Goobieverse/.eslintrc.json +++ b/Goobieverse/.eslintrc.json @@ -18,7 +18,7 @@ "rules": { "indent": [ "error", - 2 + 4 ], "linebreak-style": [ "error", diff --git a/Goobieverse/package.json b/Goobieverse/package.json index 2ae24693..033869c3 100644 --- a/Goobieverse/package.json +++ b/Goobieverse/package.json @@ -84,7 +84,7 @@ "@typescript-eslint/eslint-plugin": "^5.8.0", "@typescript-eslint/parser": "^5.8.0", "axios": "^0.24.0", - "eslint": "^8.5.0", + "eslint": "^8.6.0", "jest": "^27.4.5", "shx": "^0.3.3", "ts-jest": "^27.0.7", diff --git a/Goobieverse/src/app.hooks.ts b/Goobieverse/src/app.hooks.ts index 1be53383..8ac90480 100644 --- a/Goobieverse/src/app.hooks.ts +++ b/Goobieverse/src/app.hooks.ts @@ -2,33 +2,33 @@ // Don't remove this comment. It's needed to format import lines nicely. export default { - before: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, + before: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, - after: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, + after: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, - error: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } + error: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } }; diff --git a/Goobieverse/src/app.ts b/Goobieverse/src/app.ts index 5f08aa78..abacf34d 100644 --- a/Goobieverse/src/app.ts +++ b/Goobieverse/src/app.ts @@ -5,7 +5,6 @@ import helmet from 'helmet'; import cors from 'cors'; import feathers from '@feathersjs/feathers'; -import configuration from '@feathersjs/configuration'; import express from '@feathersjs/express'; import socketio from '@feathersjs/socketio'; import { publicRoutes } from './routes/publicRoutes'; @@ -26,7 +25,7 @@ export type HookContext = { app: Application } & FeathersHookContext // Enable security, CORS, compression, favicon and body parsing app.use(helmet({ - contentSecurityPolicy: false + contentSecurityPolicy: false })); app.use(cors()); app.use(compress()); diff --git a/Goobieverse/src/appconfig.ts b/Goobieverse/src/appconfig.ts index 3cffb17f..edf68708 100644 --- a/Goobieverse/src/appconfig.ts +++ b/Goobieverse/src/appconfig.ts @@ -4,20 +4,20 @@ import { IsNullOrEmpty, getMyExternalIPAddress } from './utils/Misc'; import fs from 'fs'; if (globalThis.process?.env.APP_ENV === 'development') { - // const fs = require('fs'); - if ( - !fs.existsSync(appRootPath.path + '/.env') && + // const fs = require('fs'); + if ( + !fs.existsSync(appRootPath.path + '/.env') && !fs.existsSync(appRootPath.path + '/.env.local') - ) { - const fromEnvPath = appRootPath.path + '/.env.local.default'; - const toEnvPath = appRootPath.path + '/.env.local'; - fs.copyFileSync(fromEnvPath, toEnvPath, fs.constants.COPYFILE_EXCL); - } + ) { + const fromEnvPath = appRootPath.path + '/.env.local.default'; + const toEnvPath = appRootPath.path + '/.env.local'; + fs.copyFileSync(fromEnvPath, toEnvPath, fs.constants.COPYFILE_EXCL); + } } dotenv.config({ - path: appRootPath.path, - silent: true, + path: appRootPath.path, + silent: true, }); /** @@ -25,25 +25,25 @@ dotenv.config({ */ const server = { - local: process.env.LOCAL === 'true', - hostName: process.env.SERVER_HOST, - port: process.env.PORT ?? 3030, - paginate: { - default: 10, - max: 100, - }, - publicPath: process.env.PUBLIC_PATH, - version: process.env.SERVER_VERSION ?? '', + local: process.env.LOCAL === 'true', + hostName: process.env.SERVER_HOST, + port: process.env.PORT ?? 3030, + paginate: { + default: 10, + max: 100, + }, + publicPath: process.env.PUBLIC_PATH, + version: process.env.SERVER_VERSION ?? '', }; const email = { - host: process.env.SMTP_HOST ?? 'smtp.gmail.com', - port: process.env.SMTP_PORT ?? '465', - secure: process.env.SMTP_SECURE ?? true, - auth: { - user: process.env.SMTP_USER ?? 'khilan.odan@gmail.com', - pass: process.env.SMTP_PASS ?? 'blackhawk143', - } + host: process.env.SMTP_HOST ?? 'smtp.gmail.com', + port: process.env.SMTP_PORT ?? '465', + secure: process.env.SMTP_SECURE ?? true, + auth: { + user: process.env.SMTP_USER ?? 'khilan.odan@gmail.com', + pass: process.env.SMTP_PASS ?? 'blackhawk143', + } }; /** @@ -51,52 +51,52 @@ const email = { */ const metaverseServer = { - listen_host: process.env.LISTEN_HOST ?? '0.0.0.0', - listen_port: process.env.LISTEN_PORT ?? 9400, - metaverseInfoAdditionFile: process.env.METAVERSE_INFO_File ?? '', - session_timeout_minutes: 5, - heartbeat_seconds_until_offline: 5 * 60, // seconds until non-heartbeating user is offline - domain_seconds_until_offline: 10 * 60, // seconds until non-heartbeating domain is offline - domain_seconds_check_if_online: 2 * 60, // how often to check if a domain is online - handshake_request_expiration_minutes: 1, // minutes that a handshake friend request is active - connection_request_expiration_minutes: 60 * 24 * 4, // 4 days - friend_request_expiration_minutes: 60 * 24 * 4, // 4 days - base_admin_account:process.env.ADMIN_ACCOUNT ?? 'Goobieverse', - place_current_timeout_minutes: 5, // minutes until current place info is stale - place_inactive_timeout_minutes: 60, // minutes until place is considered inactive - place_check_last_activity_seconds: (3 * 60) - 5, // seconds between checks for Place lastActivity updates - email_verification_timeout_minutes: process.env.EMAIL_VERIFICATION_TIME, - enable_account_email_verification: process.env.ENABLE_ACCOUNT_VERIFICATION ?? 'true', - email_verification_email_body: '../verificationEmail.html', + listen_host: process.env.LISTEN_HOST ?? '0.0.0.0', + listen_port: process.env.LISTEN_PORT ?? 9400, + metaverseInfoAdditionFile: process.env.METAVERSE_INFO_File ?? '', + session_timeout_minutes: 5, + heartbeat_seconds_until_offline: 5 * 60, // seconds until non-heartbeating user is offline + domain_seconds_until_offline: 10 * 60, // seconds until non-heartbeating domain is offline + domain_seconds_check_if_online: 2 * 60, // how often to check if a domain is online + handshake_request_expiration_minutes: 1, // minutes that a handshake friend request is active + connection_request_expiration_minutes: 60 * 24 * 4, // 4 days + friend_request_expiration_minutes: 60 * 24 * 4, // 4 days + base_admin_account:process.env.ADMIN_ACCOUNT ?? 'Goobieverse', + place_current_timeout_minutes: 5, // minutes until current place info is stale + place_inactive_timeout_minutes: 60, // minutes until place is considered inactive + place_check_last_activity_seconds: (3 * 60) - 5, // seconds between checks for Place lastActivity updates + email_verification_timeout_minutes: process.env.EMAIL_VERIFICATION_TIME, + enable_account_email_verification: process.env.ENABLE_ACCOUNT_VERIFICATION ?? 'true', + email_verification_email_body: '../verificationEmail.html', }; /** * Authentication */ const authentication = { - entity: 'user', - service: 'auth', - secret: process.env.AUTH_SECRET ?? 'testing', - authStrategies: ['jwt', 'local'], - jwtOptions: { - expiresIn: '60 days', - }, - local: { - usernameField: 'username', - passwordField: 'password', - }, - bearerToken: { - numBytes: 16, - }, - oauth: { - redirect: '/', - auth0: { - key: '', - secret: '', - subdomain: '', - scope: ['profile', 'openid', 'email'], + entity: 'user', + service: 'auth', + secret: process.env.AUTH_SECRET ?? 'testing', + authStrategies: ['jwt', 'local'], + jwtOptions: { + expiresIn: '60 days', + }, + local: { + usernameField: 'username', + passwordField: 'password', + }, + bearerToken: { + numBytes: 16, + }, + oauth: { + redirect: '/', + auth0: { + key: '', + secret: '', + subdomain: '', + scope: ['profile', 'openid', 'email'], + }, }, - }, }; /** @@ -104,37 +104,37 @@ const authentication = { */ const metaverse = { - metaverseName: process.env.METAVERSE_NAME ?? '', - metaverseNickName: process.env.METAVERSE_NICK_NAME ?? '', - metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? '', // if empty, set to self - defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? '', // if empty, set to self - dashboardUrl: process.env.DASHBOARD_URL + metaverseName: process.env.METAVERSE_NAME ?? '', + metaverseNickName: process.env.METAVERSE_NICK_NAME ?? '', + metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? '', // if empty, set to self + defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? '', // if empty, set to self + dashboardUrl: process.env.DASHBOARD_URL }; if ( - IsNullOrEmpty(metaverse.metaverseServerUrl) || + IsNullOrEmpty(metaverse.metaverseServerUrl) || IsNullOrEmpty(metaverse.defaultIceServerUrl) ) { - getMyExternalIPAddress().then((ipAddress) => { - if (IsNullOrEmpty(metaverse.metaverseServerUrl)) { - const newUrl = `http://${ipAddress}:${metaverseServer.listen_port.toString()}/`; - metaverse.metaverseServerUrl = newUrl; - } - if (IsNullOrEmpty(metaverse.defaultIceServerUrl)) { - metaverse.defaultIceServerUrl = ipAddress; - } - }); + getMyExternalIPAddress().then((ipAddress) => { + if (IsNullOrEmpty(metaverse.metaverseServerUrl)) { + const newUrl = `http://${ipAddress}:${metaverseServer.listen_port.toString()}/`; + metaverse.metaverseServerUrl = newUrl; + } + if (IsNullOrEmpty(metaverse.defaultIceServerUrl)) { + metaverse.defaultIceServerUrl = ipAddress; + } + }); } const dbCollections = { - domains : 'domains', - accounts : 'accounts', - places : 'places', - tokens : 'tokens', - requests:'requests', - email:'email' + domains : 'domains', + accounts : 'accounts', + places : 'places', + tokens : 'tokens', + requests:'requests', + email:'email' }; @@ -143,13 +143,13 @@ const dbCollections = { */ const config = { - deployStage: process.env.DEPLOY_STAGE, - authentication, - server, - metaverse, - metaverseServer, - dbCollections, - email + deployStage: process.env.DEPLOY_STAGE, + authentication, + server, + metaverse, + metaverseServer, + dbCollections, + email }; export default config; diff --git a/Goobieverse/src/authentication.ts b/Goobieverse/src/authentication.ts index 76809616..409fa474 100644 --- a/Goobieverse/src/authentication.ts +++ b/Goobieverse/src/authentication.ts @@ -11,11 +11,11 @@ declare module './declarations' { } } export default function(app: Application): void { - const authentication = new AuthenticationService(app); + const authentication = new AuthenticationService(app); - authentication.register('jwt', new JWTStrategy()); - authentication.register('local', new LocalStrategy()); + authentication.register('jwt', new JWTStrategy()); + authentication.register('local', new LocalStrategy()); - app.use('/authentication', authentication); - app.configure(expressOauth()); + app.use('/authentication', authentication); + app.configure(expressOauth()); } diff --git a/Goobieverse/src/channels.ts b/Goobieverse/src/channels.ts index 687c756e..44c54c0f 100644 --- a/Goobieverse/src/channels.ts +++ b/Goobieverse/src/channels.ts @@ -3,63 +3,63 @@ import { HookContext } from '@feathersjs/feathers'; import { Application } from './declarations'; export default function(app: Application): void { - if(typeof app.channel !== 'function') { + if(typeof app.channel !== 'function') { // If no real-time functionality has been configured just return - return; - } + return; + } - app.on('connection', (connection: any): void => { + app.on('connection', (connection: any): void => { // On a new real-time connection, add it to the anonymous channel - app.channel('anonymous').join(connection); - }); + app.channel('anonymous').join(connection); + }); - app.on('login', (authResult: any, { connection }: any): void => { + app.on('login', (authResult: any, { connection }: any): void => { // connection can be undefined if there is no // real-time connection, e.g. when logging in via REST - if(connection) { - // Obtain the logged in user from the connection - // const user = connection.user; + if(connection) { + // Obtain the logged in user from the connection + // const user = connection.user; - // The connection is no longer anonymous, remove it - app.channel('anonymous').leave(connection); + // The connection is no longer anonymous, remove it + app.channel('anonymous').leave(connection); - // Add it to the authenticated user channel - app.channel('authenticated').join(connection); + // Add it to the authenticated user channel + app.channel('authenticated').join(connection); - // Channels can be named anything and joined on any condition + // Channels can be named anything and joined on any condition - // E.g. to send real-time events only to admins use - // if(user.isAdmin) { app.channel('admins').join(connection); } + // E.g. to send real-time events only to admins use + // if(user.isAdmin) { app.channel('admins').join(connection); } - // If the user has joined e.g. chat rooms - // if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(connection)); + // If the user has joined e.g. chat rooms + // if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(connection)); - // Easily organize users by email and userid for things like messaging - // app.channel(`emails/${user.email}`).join(connection); - // app.channel(`userIds/${user.id}`).join(connection); - } - }); + // Easily organize users by email and userid for things like messaging + // app.channel(`emails/${user.email}`).join(connection); + // app.channel(`userIds/${user.id}`).join(connection); + } + }); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - app.publish((data: any, hook: HookContext) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + app.publish((data: any, hook: HookContext) => { // Here you can add event publishers to channels set up in `channels.ts` // To publish only for a specific event use `app.publish(eventname, () => {})` console.log('Publishing all events to all authenticated users. See `channels.ts` and https://docs.feathersjs.com/api/channels.html for more information.'); // eslint-disable-line - // e.g. to publish all service events to all authenticated users use - return app.channel('authenticated'); - }); + // e.g. to publish all service events to all authenticated users use + return app.channel('authenticated'); + }); - // Here you can also add service specific event publishers - // e.g. the publish the `users` service `created` event to the `admins` channel - // app.service('users').publish('created', () => app.channel('admins')); + // Here you can also add service specific event publishers + // e.g. the publish the `users` service `created` event to the `admins` channel + // app.service('users').publish('created', () => app.channel('admins')); - // With the userid and email organization from above you can easily select involved users - // app.service('messages').publish(() => { - // return [ - // app.channel(`userIds/${data.createdBy}`), - // app.channel(`emails/${data.recipientEmail}`) - // ]; - // }); + // With the userid and email organization from above you can easily select involved users + // app.service('messages').publish(() => { + // return [ + // app.channel(`userIds/${data.createdBy}`), + // app.channel(`emails/${data.recipientEmail}`) + // ]; + // }); } diff --git a/Goobieverse/src/controllers/PublicRoutesController.ts b/Goobieverse/src/controllers/PublicRoutesController.ts index 8f88c306..eb575f96 100644 --- a/Goobieverse/src/controllers/PublicRoutesController.ts +++ b/Goobieverse/src/controllers/PublicRoutesController.ts @@ -3,29 +3,29 @@ import config from '../appconfig'; import {IsNotNullOrEmpty, readInJSON } from '../utils/Misc'; export const PublicRoutesController = ()=>{ - const metaverseInfo = async(req:any,res:any,next:any) => { - const response:MetaverseInfoModel = { - metaverse_name:config.metaverse.metaverseName, - metaverse_nick_name: config.metaverse.metaverseNickName, - ice_server_url: config.metaverse.defaultIceServerUrl , - metaverse_url: config.metaverse.metaverseServerUrl, - metaverse_server_version:{ - version_tag:config.server.version - } - }; - try { - const additionUrl: string = config.metaverseServer.metaverseInfoAdditionFile; - if (IsNotNullOrEmpty(additionUrl)) { - const additional = await readInJSON(additionUrl); - if (IsNotNullOrEmpty(additional)) { - response.metaverse_server_version = additional; + const metaverseInfo = async(req:any,res:any) => { + const response:MetaverseInfoModel = { + metaverse_name:config.metaverse.metaverseName, + metaverse_nick_name: config.metaverse.metaverseNickName, + ice_server_url: config.metaverse.defaultIceServerUrl , + metaverse_url: config.metaverse.metaverseServerUrl, + metaverse_server_version:{ + version_tag:config.server.version + } + }; + try { + const additionUrl: string = config.metaverseServer.metaverseInfoAdditionFile; + if (IsNotNullOrEmpty(additionUrl)) { + const additional = await readInJSON(additionUrl); + if (IsNotNullOrEmpty(additional)) { + response.metaverse_server_version = additional; + } + } + } + catch (err) { + //console.error(`procMetaverseInfo: exception reading additional info file: ${err}`); } - } - } - catch (err) { - //console.error(`procMetaverseInfo: exception reading additional info file: ${err}`); - } - res.status(200).json(response); - }; - return {metaverseInfo}; + res.status(200).json(response); + }; + return {metaverseInfo}; }; \ No newline at end of file diff --git a/Goobieverse/src/dbservice/DatabaseService.ts b/Goobieverse/src/dbservice/DatabaseService.ts index 524233fd..3d75c93f 100644 --- a/Goobieverse/src/dbservice/DatabaseService.ts +++ b/Goobieverse/src/dbservice/DatabaseService.ts @@ -2,87 +2,108 @@ import { Service, } from 'feathers-mongodb'; import { Application } from '../declarations'; import { HookContext, Paginated, Id, } from '@feathersjs/feathers'; import { DatabaseServiceOptions } from './DatabaseServiceOptions'; -import { Db, Collection, Document, FindCursor, WithId,Filter } from 'mongodb'; +import { Db, Collection, Document, Filter } from 'mongodb'; import { IsNotNullOrEmpty, IsNullOrEmpty } from '../utils/Misc'; import { VKeyedCollection } from '../utils/vTypes'; +import { messages } from '../utils/messages'; export class DatabaseService extends Service { - app?: Application; - db?:Db; - context?:HookContext; - constructor(options: Partial,app?: Application,context?: HookContext) { - super(options); - this.app = app; - this.context = context; - this.loadDatabase(); - } - - - async loadDatabase() { - if(IsNotNullOrEmpty(this.app) && this.app){ - this.db = await this.app.get('mongoClient'); - }else if(IsNotNullOrEmpty(this.context) && this.context){ - this.db = await this.context.app.get('mongoClient'); + app?: Application; + db?: Db; + context?: HookContext; + constructor( + options: Partial, + app?: Application, + context?: HookContext + ) { + super(options); + this.app = app; + this.context = context; + this.loadDatabase(); } - } - async getDatabase(): Promise { - if(IsNullOrEmpty(this.db)){ - await this.loadDatabase(); + async loadDatabase() { + if (IsNotNullOrEmpty(this.app) && this.app) { + this.db = await this.app.get('mongoClient'); + } else if (IsNotNullOrEmpty(this.context) && this.context) { + this.db = await this.context.app.get('mongoClient'); + } } - return this.db!; - } - async getService(tableName:string):Promise>{ - this.Model = await (await this.getDatabase()).collection(tableName); - return this.Model; - } + async getDatabase(): Promise { + if (IsNullOrEmpty(this.db)) { + await this.loadDatabase(); + } + if (this.db) { + return this.db; + } + throw new Error(messages.common_messages_db_error); + } - async getData(tableName: string, id:Id ): Promise{ - await (this.getService(tableName)); - return super.get(id); - } + async getService(tableName: string): Promise> { + this.Model = await (await this.getDatabase()).collection(tableName); + return this.Model; + } + async getData(tableName: string, id: Id): Promise { + await this.getService(tableName); + return super.get(id); + } - async findData(tableName: string, filter?:Filter ): Promise | any[]>{ - await (this.getService(tableName)); - if(IsNotNullOrEmpty(filter)){ - return await super.find(filter!); - } else { - return await super.find(); + async findData( + tableName: string, + filter?: Filter + ): Promise | any[]> { + await this.getService(tableName); + if (filter) { + return await super.find(filter); + } else { + return await super.find(); + } } - } - async findDataToArray(tableName: string, filter?:Filter ): Promise{ - await (this.getService(tableName)); - const data = await this.findData(tableName,filter); - if(data instanceof Array){ - return data; - }else{ - return data.data; + async findDataToArray( + tableName: string, + filter?: Filter + ): Promise { + await this.getService(tableName); + const data = await this.findData(tableName, filter); + if (data instanceof Array) { + return data; + } else { + return data.data; + } } - } - - async patchData(tableName:string,id:Id,data:VKeyedCollection): Promise { - console.log(tableName + ' ' + id); - await (this.getService(tableName)); - return await super.patch(id,data); - } + async patchData( + tableName: string, + id: Id, + data: VKeyedCollection + ): Promise { + console.log(tableName + ' ' + id); + await this.getService(tableName); + return await super.patch(id, data); + } - async deleteData(tableName:string,id:Id,filter?:Filter): Promise { - await (this.getService(tableName)); - return await super.remove(id,filter); - } + async deleteData( + tableName: string, + id: Id, + filter?: Filter + ): Promise { + await this.getService(tableName); + return await super.remove(id, filter); + } - async deleteMultipleData(tableName:string,filter?:Filter): Promise { - await (this.getService(tableName)); - return await super.remove(null,filter); - } + async deleteMultipleData( + tableName: string, + filter: Filter + ): Promise { + await this.getService(tableName); + return await super.remove(null, filter); + } - async CreateData(tableName: string, data:any): Promise { - await (this.getService(tableName)); - return await super.create(data); - } - -} \ No newline at end of file + async CreateData(tableName: string, data: any): Promise { + await this.getService(tableName); + return await super.create(data); + } +} diff --git a/Goobieverse/src/dbservice/DatabaseServiceOptions.ts b/Goobieverse/src/dbservice/DatabaseServiceOptions.ts index 7b0a75e4..d8c00fa4 100644 --- a/Goobieverse/src/dbservice/DatabaseServiceOptions.ts +++ b/Goobieverse/src/dbservice/DatabaseServiceOptions.ts @@ -1,3 +1,3 @@ -import { MongoDBServiceOptions } from 'feathers-mongodb'; +import { MongoDBServiceOptions } from 'feathers-mongodb'; -export interface DatabaseServiceOptions extends MongoDBServiceOptions {} \ No newline at end of file +export interface DatabaseServiceOptions extends MongoDBServiceOptions {} diff --git a/Goobieverse/src/hooks/checkAccess.ts b/Goobieverse/src/hooks/checkAccess.ts index a5391468..2fa0c5df 100644 --- a/Goobieverse/src/hooks/checkAccess.ts +++ b/Goobieverse/src/hooks/checkAccess.ts @@ -1,134 +1,189 @@ import { AccountModel } from '../interfaces/AccountModel'; -import { Application, Paginated } from '@feathersjs/feathers'; import { HookContext } from '@feathersjs/feathers'; import { IsNotNullOrEmpty } from '../utils/Misc'; import { Perm } from '../utils/Perm'; import { isAdmin } from '../utils/Utils'; import config from '../appconfig'; -import {HTTPStatusCode} from '../utils/response'; -import {Availability} from '../utils/sets/Availability'; +import { HTTPStatusCode } from '../utils/response'; +import { Availability } from '../utils/sets/Availability'; import { SArray } from '../utils/vTypes'; import { DatabaseService } from '../dbservice/DatabaseService'; import { messages } from '../utils/messages'; -export default (collection: string ,pRequiredAccess: Perm[]) => { - return async (context: HookContext): Promise => { - const dbService = new DatabaseService({},undefined,context); - const loginUser = context.params.user; - - const entryDataArray = await dbService.findDataToArray(collection,{query:{id:context.id}}); - let canAccess = false; +export default (collection: string, pRequiredAccess: Perm[]) => { + return async (context: HookContext): Promise => { + const dbService = new DatabaseService({}, undefined, context); + const loginUser = context.params.user; - let pTargetEntity: any; - if(IsNotNullOrEmpty(entryDataArray)){ - pTargetEntity = entryDataArray[0]; - } + const entryDataArray = await dbService.findDataToArray(collection, { + query: { id: context.id }, + }); + let canAccess = false; + let pTargetEntity: any; + if (IsNotNullOrEmpty(entryDataArray)) { + pTargetEntity = entryDataArray[0]; + } - if (IsNotNullOrEmpty(pTargetEntity) && pTargetEntity) { - for (const perm of pRequiredAccess) { - switch (perm) { - case Perm.ALL: - canAccess = true; - break; - case Perm.PUBLIC: - // The target entity is publicly visible - // Mostly AccountEntities that must have an 'availability' field - if (pTargetEntity?.hasOwnProperty('availability')) { - if ((pTargetEntity).availability.includes(Availability.ALL)) { - canAccess = true; - } - } - break; - case Perm.DOMAIN: - // requestor is a domain and it's account is the domain's sponsoring account - if (loginUser) { - if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { - canAccess = loginUser.id === (pTargetEntity as any).sponsorAccountId; - } - else { - // Super special case where domain doesn't have a sponsor but has an api_key. - // In this case, the API_KEY is put in the accountId field of the DOMAIN scoped AuthToken - if (pTargetEntity.hasOwnProperty('apiKey')) { - canAccess = loginUser.id === (pTargetEntity as any).apiKey; - } - } - } - break; - case Perm.OWNER: - // The requestor wants to be the same account as the target entity - if (loginUser && pTargetEntity.hasOwnProperty('id')) { - canAccess = (loginUser.id === (pTargetEntity as any).id); - } - if (loginUser && !canAccess && pTargetEntity.hasOwnProperty('accountId')) { - canAccess = loginUser.id === (pTargetEntity as any).accountId; - } - break; - case Perm.FRIEND: - // The requestor is a 'friend' of the target entity - if (loginUser && pTargetEntity.hasOwnProperty('friends')) { - const targetFriends: string[] = (pTargetEntity as AccountModel).friends; - if (targetFriends) { - canAccess = SArray.hasNoCase(targetFriends, loginUser.username); - } - } - break; - case Perm.CONNECTION: - // The requestor is a 'connection' of the target entity - if (loginUser && pTargetEntity.hasOwnProperty('connections')) { - const targetConnections: string[] = (pTargetEntity as AccountModel).connections; - if (targetConnections) { - canAccess = SArray.hasNoCase(targetConnections, loginUser.username); - } - } - break; - case Perm.ADMIN: - // If the authToken is an account, has access if admin - if (loginUser) { - canAccess = isAdmin(loginUser as AccountModel); - } - break; - case Perm.SPONSOR: - // Requestor is a regular account and is the sponsor of the domain - if (loginUser) { - if (pTargetEntity.hasOwnProperty('sponsorAccountId')) { - canAccess = loginUser.id === (pTargetEntity as any).sponsorAccountId; + if (IsNotNullOrEmpty(pTargetEntity) && pTargetEntity) { + for (const perm of pRequiredAccess) { + switch (perm) { + case Perm.ALL: + canAccess = true; + break; + case Perm.PUBLIC: + // The target entity is publicly visible + // Mostly AccountEntities that must have an 'availability' field + if (pTargetEntity?.hasOwnProperty('availability')) { + if ( + pTargetEntity.availability.includes( + Availability.ALL + ) + ) { + canAccess = true; + } + } + break; + case Perm.DOMAIN: + // requestor is a domain and it's account is the domain's sponsoring account + if (loginUser) { + if ( + pTargetEntity.hasOwnProperty('sponsorAccountId') + ) { + canAccess = + loginUser.id === + (pTargetEntity as any).sponsorAccountId; + } else { + // Super special case where domain doesn't have a sponsor but has an api_key. + // In this case, the API_KEY is put in the accountId field of the DOMAIN scoped AuthToken + if (pTargetEntity.hasOwnProperty('apiKey')) { + canAccess = + loginUser.id === + (pTargetEntity as any).apiKey; + } + } + } + break; + case Perm.OWNER: + // The requestor wants to be the same account as the target entity + if (loginUser && pTargetEntity.hasOwnProperty('id')) { + canAccess = + loginUser.id === (pTargetEntity as any).id; + } + if ( + loginUser && + !canAccess && + pTargetEntity.hasOwnProperty('accountId') + ) { + canAccess = + loginUser.id === + (pTargetEntity as any).accountId; + } + break; + case Perm.FRIEND: + // The requestor is a 'friend' of the target entity + if ( + loginUser && + pTargetEntity.hasOwnProperty('friends') + ) { + const targetFriends: string[] = ( + pTargetEntity as AccountModel + ).friends; + if (targetFriends) { + canAccess = SArray.hasNoCase( + targetFriends, + loginUser.username + ); + } + } + break; + case Perm.CONNECTION: + // The requestor is a 'connection' of the target entity + if ( + loginUser && + pTargetEntity.hasOwnProperty('connections') + ) { + const targetConnections: string[] = ( + pTargetEntity as AccountModel + ).connections; + if (targetConnections) { + canAccess = SArray.hasNoCase( + targetConnections, + loginUser.username + ); + } + } + break; + case Perm.ADMIN: + // If the authToken is an account, has access if admin + if (loginUser) { + canAccess = isAdmin(loginUser as AccountModel); + } + break; + case Perm.SPONSOR: + // Requestor is a regular account and is the sponsor of the domain + if (loginUser) { + if ( + pTargetEntity.hasOwnProperty('sponsorAccountId') + ) { + canAccess = + loginUser.id === + (pTargetEntity as any).sponsorAccountId; + } + } + break; + case Perm.MANAGER: + // See if requesting account is in the list of managers of this entity + if (loginUser) { + if (pTargetEntity.hasOwnProperty('managers')) { + const managers: string[] = ( + pTargetEntity as any + ).managers; + if ( + managers && + managers.includes( + loginUser.username.toLowerCase() + ) + ) { + canAccess = true; + } + } + } + break; + case Perm.DOMAINACCESS: + // Target entity has a domain reference and verify the requestor is able to reference that domain + if ( + loginUser && + pTargetEntity.hasOwnProperty('domainId') + ) { + const aDomains = await dbService.findDataToArray( + config.dbCollections.domains, + { + query: { + id: (pTargetEntity as any).domainId, + }, + } + ); + if (IsNotNullOrEmpty(aDomains)) { + canAccess = + aDomains[0].sponsorAccountId === + loginUser.id; + } + } + break; + } + if (canAccess) break; } - } - break; - case Perm.MANAGER: - // See if requesting account is in the list of managers of this entity - if (loginUser) { - if (pTargetEntity.hasOwnProperty('managers')) { - const managers: string[] = (pTargetEntity as any).managers; - if (managers && managers.includes(loginUser.username.toLowerCase())) { - canAccess = true; - } - } - } - break; - case Perm.DOMAINACCESS: - // Target entity has a domain reference and verify the requestor is able to reference that domain - if (loginUser && pTargetEntity.hasOwnProperty('domainId')) { - const aDomains = await dbService.findDataToArray(config.dbCollections.domains,{query:{id:(pTargetEntity as any).domainId}}); - if (IsNotNullOrEmpty(aDomains)) { - canAccess = aDomains[0].sponsorAccountId === loginUser.id; - } - } - break; + } else { + context.statusCode = HTTPStatusCode.NotFound; + throw new Error(messages.common_messages_target_account_notfound); } - if(canAccess)break; - } - }else{ - context.statusCode = HTTPStatusCode.NotFound; - throw new Error(messages.common_messages_target_account_notfound); - } - - if (!canAccess){ - context.statusCode = HTTPStatusCode.Unauthorized; - throw new Error(messages.common_messages_unauthorized); - } - - return context; - }; -}; \ No newline at end of file + + if (!canAccess) { + context.statusCode = HTTPStatusCode.Unauthorized; + throw new Error(messages.common_messages_unauthorized); + } + + return context; + }; +}; diff --git a/Goobieverse/src/hooks/isHasAuthToken.ts b/Goobieverse/src/hooks/isHasAuthToken.ts index cacc48c1..661f5746 100644 --- a/Goobieverse/src/hooks/isHasAuthToken.ts +++ b/Goobieverse/src/hooks/isHasAuthToken.ts @@ -1,7 +1,7 @@ import { HookContext } from '@feathersjs/feathers'; export default (): any => { - return (context:HookContext): boolean => { - return !!(context.params.headers?.authorization&& context.params.headers.authorization !== 'null'&& context.params.headers.authorization !== ''); - }; + return (context:HookContext): boolean => { + return !!(context.params.headers?.authorization&& context.params.headers.authorization !== 'null'&& context.params.headers.authorization !== ''); + }; }; \ No newline at end of file diff --git a/Goobieverse/src/hooks/requestFail.ts b/Goobieverse/src/hooks/requestFail.ts index 258c34d0..2cea5410 100644 --- a/Goobieverse/src/hooks/requestFail.ts +++ b/Goobieverse/src/hooks/requestFail.ts @@ -1,12 +1,9 @@ import { HookContext } from '@feathersjs/feathers'; -import { IsNotNullOrEmpty } from '../utils/Misc'; -import { Perm } from '../utils/Perm'; import { Response } from '../utils/response'; export default () => { - return async (context: HookContext): Promise => { - - context.result = Response.error(context?.error?.message); - return context; - }; -}; \ No newline at end of file + return async (context: HookContext): Promise => { + context.result = Response.error(context?.error?.message); + return context; + }; +}; diff --git a/Goobieverse/src/hooks/requestSuccess.ts b/Goobieverse/src/hooks/requestSuccess.ts index 0cc54059..01fc2854 100644 --- a/Goobieverse/src/hooks/requestSuccess.ts +++ b/Goobieverse/src/hooks/requestSuccess.ts @@ -1,11 +1,9 @@ import { HookContext } from '@feathersjs/feathers'; -import { IsNotNullOrEmpty } from '../utils/Misc'; -import { Perm } from '../utils/Perm'; import { Response } from '../utils/response'; export default () => { - return async (context: HookContext): Promise => { - context.result = Response.success(context.result); - return context; - }; + return async (context: HookContext): Promise => { + context.result = Response.success(context.result); + return context; + }; }; \ No newline at end of file diff --git a/Goobieverse/src/index.ts b/Goobieverse/src/index.ts index 7c8a791c..ae1c9afc 100644 --- a/Goobieverse/src/index.ts +++ b/Goobieverse/src/index.ts @@ -5,9 +5,9 @@ const port = app.get('port'); const server = app.listen(port); process.on('unhandledRejection', (reason, p) => - logger.error('Unhandled Rejection at: Promise ', p, reason) + logger.error('Unhandled Rejection at: Promise ', p, reason) ); server.on('listening', () => - logger.info('Feathers application started on http://%s:%d', app.get('host'), port) + logger.info('Feathers application started on http://%s:%d', app.get('host'), port) ); diff --git a/Goobieverse/src/interfaces/AccountModel.ts b/Goobieverse/src/interfaces/AccountModel.ts index ae4bff71..f5a1d48b 100755 --- a/Goobieverse/src/interfaces/AccountModel.ts +++ b/Goobieverse/src/interfaces/AccountModel.ts @@ -1,42 +1,42 @@ export interface AccountModel { - id: string; - username: string; - email: string; - accountSettings: string; // JSON of client settings - imagesHero: string; - imagesThumbnail: string; - imagesTiny: string; - password: string; + id: string; + username: string; + email: string; + accountSettings: string; // JSON of client settings + imagesHero: string; + imagesThumbnail: string; + imagesTiny: string; + password: string; - locationConnected: boolean; - locationPath: string; // "/floatX,floatY,floatZ/floatX,floatY,floatZ,floatW" - locationPlaceId: string; // uuid of place - locationDomainId: string; // uuid of domain located in - locationNetworkAddress: string; - locationNetworkPort: number; - locationNodeId: string; // sessionId - availability: string[]; // contains 'none', 'friends', 'connections', 'all' + locationConnected: boolean; + locationPath: string; // "/floatX,floatY,floatZ/floatX,floatY,floatZ,floatW" + locationPlaceId: string; // uuid of place + locationDomainId: string; // uuid of domain located in + locationNetworkAddress: string; + locationNetworkPort: number; + locationNodeId: string; // sessionId + availability: string[]; // contains 'none', 'friends', 'connections', 'all' - connections: string[]; - friends: string[]; - locker: any; // JSON blob stored for user from server - profileDetail: any; // JSON blob stored for user from server + connections: string[]; + friends: string[]; + locker: any; // JSON blob stored for user from server + profileDetail: any; // JSON blob stored for user from server - // User authentication - // passwordHash: string; - // passwordSalt: string; - sessionPublicKey: string; // PEM public key generated for this session - accountEmailVerified: boolean; // default true if not present + // User authentication + // passwordHash: string; + // passwordSalt: string; + sessionPublicKey: string; // PEM public key generated for this session + accountEmailVerified: boolean; // default true if not present - // Old stuff - xmppPassword: string; - discourseApiKey: string; - walletId: string; + // Old stuff + xmppPassword: string; + discourseApiKey: string; + walletId: string; - // Admin stuff - // ALWAYS USE functions in Roles class to manipulate this list of roles - roles: string[]; // account roles (like 'admin') - IPAddrOfCreator: string; // IP address that created this account - whenCreated: Date; // date of account creation - timeOfLastHeartbeat: Date; // when we last heard from this user + // Admin stuff + // ALWAYS USE functions in Roles class to manipulate this list of roles + roles: string[]; // account roles (like 'admin') + IPAddrOfCreator: string; // IP address that created this account + whenCreated: Date; // date of account creation + timeOfLastHeartbeat: Date; // when we last heard from this user } diff --git a/Goobieverse/src/interfaces/DomainModel.ts b/Goobieverse/src/interfaces/DomainModel.ts index 8720af5b..d624d64c 100755 --- a/Goobieverse/src/interfaces/DomainModel.ts +++ b/Goobieverse/src/interfaces/DomainModel.ts @@ -1,38 +1,38 @@ -export interface DomainModel{ - id: string, // globally unique domain identifier - name: string, // domain name/label - visibility: string, // visibility of this entry in general domain lists - publicKey: string, // DomainServers's public key in multi-line PEM format - apiKey: string, // Access key if a temp domain - sponsorAccountId: string, // The account that gave this domain an access key - iceServerAddr: string,// IP address of ICE server being used by this domain +export interface DomainModel { + id: string; // globally unique domain identifier + name: string; // domain name/label + visibility: string; // visibility of this entry in general domain lists + publicKey: string; // DomainServers's public key in multi-line PEM format + apiKey: string; // Access key if a temp domain + sponsorAccountId: string; // The account that gave this domain an access key + iceServerAddr: string; // IP address of ICE server being used by this domain // Information that comes in via heartbeat - version: string, // DomainServer's build version (like "K3") - protocol: string, // Protocol version - networkAddr: string, // reported network address - networkPort: string, // reported network address - networkingMode: string, // one of "full", "ip", "disabled" - restricted: boolean, // 'true' if restricted to users with accounts - numUsers: number, // total number of logged in users - anonUsers: number, // number of anonymous users - hostnames: string[], // User segmentation + version: string; // DomainServer's build version (like "K3") + protocol: string; // Protocol version + networkAddr: string; // reported network address + networkPort: string; // reported network address + networkingMode: string; // one of "full", "ip", "disabled" + restricted: boolean; // 'true' if restricted to users with accounts + numUsers: number; // total number of logged in users + anonUsers: number; // number of anonymous users + hostnames: string[]; // User segmentation // More information that's metadata that's passed in PUT domain - capacity: number, // Total possible users - description: string, // Short description of domain - contactInfo: string, // domain contact information - thumbnail: string, // thumbnail image of domain - images: string[], // collection of images for the domain - maturity: string, // Maturity rating - restriction: string, // Access restrictions ("open") - managers: string[], // Usernames of people who are domain admins - tags: string[], // Categories for describing the domain + capacity: number; // Total possible users + description: string; // Short description of domain + contactInfo: string; // domain contact information + thumbnail: string; // thumbnail image of domain + images: string[]; // collection of images for the domain + maturity: string; // Maturity rating + restriction: string; // Access restrictions ("open") + managers: string[]; // Usernames of people who are domain admins + tags: string[]; // Categories for describing the domain // admin stuff - iPAddrOfFirstContact: string, // IP address that registered this domain - whenCreated: Date, // What the variable name says - active: boolean, // domain is heartbeating - timeOfLastHeartbeat: Date, // time of last heartbeat - lastSenderKey: string, // a key identifying the sender -} \ No newline at end of file + iPAddrOfFirstContact: string; // IP address that registered this domain + whenCreated: Date; // What the variable name says + active: boolean; // domain is heartbeating + timeOfLastHeartbeat: Date; // time of last heartbeat + lastSenderKey: string; // a key identifying the sender +} diff --git a/Goobieverse/src/interfaces/MetaverseInfo.ts b/Goobieverse/src/interfaces/MetaverseInfo.ts index 4552ccb1..02b64af6 100644 --- a/Goobieverse/src/interfaces/MetaverseInfo.ts +++ b/Goobieverse/src/interfaces/MetaverseInfo.ts @@ -1,7 +1,7 @@ -export interface MetaverseInfoModel{ - metaverse_name:string, - metaverse_nick_name:string, - metaverse_url:string, - ice_server_url:string, - metaverse_server_version:any -} \ No newline at end of file +export interface MetaverseInfoModel { + metaverse_name: string; + metaverse_nick_name: string; + metaverse_url: string; + ice_server_url: string; + metaverse_server_version: any; +} diff --git a/Goobieverse/src/interfaces/PlaceModel.ts b/Goobieverse/src/interfaces/PlaceModel.ts index 2793916e..fcf2ce2a 100755 --- a/Goobieverse/src/interfaces/PlaceModel.ts +++ b/Goobieverse/src/interfaces/PlaceModel.ts @@ -1,29 +1,28 @@ -export interface PlaceModel{ - id: string, // globally unique place identifier - name: string, // Human friendly name of the place - displayName: string, // Human friendly name of the place - description: string, // Human friendly description of the place - visibility: string, // visibility of this Place in general Place lists - maturity: string, // maturity level of the place (see Sets/Maturity.ts) - tags: string[], // tags defining the string content - domainId: string, // domain the place is in - managers: string[], // Usernames of people who are domain admins - path: string, // address within the domain: "optional-domain/x,y,z/x,y,z,x" - thumbnail: string, // thumbnail for place - images: string[], // images for the place +export interface PlaceModel { + id: string; // globally unique place identifier + name: string; // Human friendly name of the place + displayName: string; // Human friendly name of the place + description: string; // Human friendly description of the place + visibility: string; // visibility of this Place in general Place lists + maturity: string; // maturity level of the place (see Sets/Maturity.ts) + tags: string[]; // tags defining the string content + domainId: string; // domain the place is in + managers: string[]; // Usernames of people who are domain admins + path: string; // address within the domain: "optional-domain/x,y,z/x,y,z,x" + thumbnail: string; // thumbnail for place + images: string[]; // images for the place // A Place can have a beacon that updates current state and information // If current information is not supplied, attendance defaults to domain's - currentAttendance: number // current attendance at the Place - currentImages: string[] // images at the session - currentInfo: string // JSON information about the session - currentLastUpdateTime: Date // time that the last session information was updated - currentAPIKeyTokenId: string // API key for updating the session information + currentAttendance: number; // current attendance at the Place + currentImages: string[]; // images at the session + currentInfo: string; // JSON information about the session + currentLastUpdateTime: Date; // time that the last session information was updated + currentAPIKeyTokenId: string; // API key for updating the session information // admin stuff - iPAddrOfFirstContact: string, // IP address that registered this place - whenCreated: Date, // What the variable name says + iPAddrOfFirstContact: string; // IP address that registered this place + whenCreated: Date; // What the variable name says // 'lastActivity' is computed by Places.initPlaces and used for aliveness checks - lastActivity: Date, // newest of currentLastUpdateTime and Domain.timeOfLastHeartbeat + lastActivity: Date; // newest of currentLastUpdateTime and Domain.timeOfLastHeartbeat } - diff --git a/Goobieverse/src/interfaces/RequestModal.ts b/Goobieverse/src/interfaces/RequestModal.ts index 441b08aa..8b840094 100644 --- a/Goobieverse/src/interfaces/RequestModal.ts +++ b/Goobieverse/src/interfaces/RequestModal.ts @@ -1,22 +1,22 @@ export interface RequestEntity { - id: string; - requestType: string; + id: string; + requestType: string; - // requestor and target - requestingAccountId: string; - targetAccountId: string; + // requestor and target + requestingAccountId: string; + targetAccountId: string; - // administration - expirationTime: Date; - whenCreated: Date; + // administration + expirationTime: Date; + whenCreated: Date; - // requestType == HANDSHAKE - requesterNodeId: string; - targetNodeId: string; - requesterAccepted: boolean; - targetAccepted: boolean; + // requestType == HANDSHAKE + requesterNodeId: string; + targetNodeId: string; + requesterAccepted: boolean; + targetAccepted: boolean; - // requestType == VERIFYEMAIL - // 'requestingAccountId' is the account being verified - verificationCode: string; // the code we're waiting for -} \ No newline at end of file + // requestType == VERIFYEMAIL + // 'requestingAccountId' is the account being verified + verificationCode: string; // the code we're waiting for +} diff --git a/Goobieverse/src/logger.ts b/Goobieverse/src/logger.ts index 739c222b..a533961a 100644 --- a/Goobieverse/src/logger.ts +++ b/Goobieverse/src/logger.ts @@ -2,15 +2,15 @@ import { createLogger, format, transports } from 'winston'; // Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston const logger = createLogger({ - // To see more detailed errors, change this to 'debug' - level: 'info', - format: format.combine( - format.splat(), - format.simple() - ), - transports: [ - new transports.Console() - ], + // To see more detailed errors, change this to 'debug' + level: 'info', + format: format.combine( + format.splat(), + format.simple() + ), + transports: [ + new transports.Console() + ], }); export default logger; diff --git a/Goobieverse/src/middleware/index.ts b/Goobieverse/src/middleware/index.ts index e7826837..0bad39e0 100644 --- a/Goobieverse/src/middleware/index.ts +++ b/Goobieverse/src/middleware/index.ts @@ -2,5 +2,4 @@ import { Application } from '../declarations'; // Don't remove this comment. It's needed to format import lines nicely. // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function -export default function (app: Application): void { -} +export default function (app: Application): void {} diff --git a/Goobieverse/src/mongodb.ts b/Goobieverse/src/mongodb.ts index 67703836..97cda256 100644 --- a/Goobieverse/src/mongodb.ts +++ b/Goobieverse/src/mongodb.ts @@ -2,19 +2,19 @@ import { MongoClient } from 'mongodb'; import { Application } from './declarations'; import { IsNotNullOrEmpty } from './utils/Misc'; export default function (app: Application): void { - let connection = ''; - if(IsNotNullOrEmpty(process.env.DATABASE_URL)){ - connection = process.env.DATABASE_URL || ''; - }else{ - const userSpec = `${process.env.DB_USER}:${process.env.DB_PASSWORD}`; - const hostSpec = `${process.env.DB_HOST}:${process.env.DB_PORT}`; - let optionsSpec = ''; - if (process.env.DB_AUTHDB !== 'admin') { - optionsSpec += `?authSource=${process.env.DB_AUTHDB}`; + let connection = ''; + if(IsNotNullOrEmpty(process.env.DATABASE_URL)){ + connection = process.env.DATABASE_URL || ''; + }else{ + const userSpec = `${process.env.DB_USER}:${process.env.DB_PASSWORD}`; + const hostSpec = `${process.env.DB_HOST}:${process.env.DB_PORT}`; + let optionsSpec = ''; + if (process.env.DB_AUTHDB !== 'admin') { + optionsSpec += `?authSource=${process.env.DB_AUTHDB}`; + } + connection = `mongodb://${userSpec}@${hostSpec}/${optionsSpec}`; } - connection = `mongodb://${userSpec}@${hostSpec}/${optionsSpec}`; - } - const database = process.env.DB_NAME; - const mongoClient = MongoClient.connect(connection).then(client => client.db(database)); - app.set('mongoClient', mongoClient); + const database = process.env.DB_NAME; + const mongoClient = MongoClient.connect(connection).then(client => client.db(database)); + app.set('mongoClient', mongoClient); } diff --git a/Goobieverse/src/responsebuilder/accountsBuilder.ts b/Goobieverse/src/responsebuilder/accountsBuilder.ts index d88ebf29..fea1af45 100644 --- a/Goobieverse/src/responsebuilder/accountsBuilder.ts +++ b/Goobieverse/src/responsebuilder/accountsBuilder.ts @@ -5,75 +5,75 @@ import { VKeyedCollection } from '../utils/vTypes'; import { isAdmin, isEnabled, createSimplifiedPublicKey } from '../utils/Utils'; // Return the limited "user" info.. used by /api/v1/users export async function buildUserInfo(pAccount: AccountModel): Promise { - return { - accountId: pAccount.id, - id: pAccount.id, - username: pAccount.username, - images: await buildImageInfo(pAccount), - location: await buildLocationInfo(pAccount), - }; + return { + accountId: pAccount.id, + id: pAccount.id, + username: pAccount.username, + images: await buildImageInfo(pAccount), + location: await buildLocationInfo(pAccount), + }; } export async function buildImageInfo(pAccount: AccountModel): Promise { - const ret: VKeyedCollection = {}; + const ret: VKeyedCollection = {}; - if (pAccount.imagesTiny) ret.tiny = pAccount.imagesTiny; - if (pAccount.imagesHero) ret.hero = pAccount.imagesHero; - if (pAccount.imagesThumbnail) ret.thumbnail = pAccount.imagesThumbnail; - return ret; + if (pAccount.imagesTiny) ret.tiny = pAccount.imagesTiny; + if (pAccount.imagesHero) ret.hero = pAccount.imagesHero; + if (pAccount.imagesThumbnail) ret.thumbnail = pAccount.imagesThumbnail; + return ret; } // Return the block of account information. // Used by several of the requests to return the complete account information. export async function buildAccountInfo( - pAccount: AccountModel + pAccount: AccountModel ): Promise { - return { - accountId: pAccount.id, - id: pAccount.id, - username: pAccount.username, - email: pAccount.email, - administrator: isAdmin(pAccount), - enabled: isEnabled(pAccount), - roles: pAccount.roles, - availability: pAccount.availability, - public_key: createSimplifiedPublicKey(pAccount.sessionPublicKey), - images: { - hero: pAccount.imagesHero, - tiny: pAccount.imagesTiny, - thumbnail: pAccount.imagesThumbnail, - }, - profile_detail: pAccount.profileDetail, - location: await buildLocationInfo(pAccount), - friends: pAccount.friends, - connections: pAccount.connections, - when_account_created: pAccount.whenCreated?.toISOString(), - when_account_created_s: pAccount.whenCreated?.getTime().toString(), - time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat?.getTime().toString(), - }; + return { + accountId: pAccount.id, + id: pAccount.id, + username: pAccount.username, + email: pAccount.email, + administrator: isAdmin(pAccount), + enabled: isEnabled(pAccount), + roles: pAccount.roles, + availability: pAccount.availability, + public_key: createSimplifiedPublicKey(pAccount.sessionPublicKey), + images: { + hero: pAccount.imagesHero, + tiny: pAccount.imagesTiny, + thumbnail: pAccount.imagesThumbnail, + }, + profile_detail: pAccount.profileDetail, + location: await buildLocationInfo(pAccount), + friends: pAccount.friends, + connections: pAccount.connections, + when_account_created: pAccount.whenCreated?.toISOString(), + when_account_created_s: pAccount.whenCreated?.getTime().toString(), + time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat?.getTime().toString(), + }; } // Return the block of account information used as the account 'profile'. // Anyone can fetch a profile (if 'availability' is 'any') so not all info is returned export async function buildAccountProfile( - pAccount: AccountModel, - aDomain?: DomainModel + pAccount: AccountModel, + aDomain?: DomainModel ): Promise { - return { - accountId: pAccount.id, - id: pAccount.id, - username: pAccount.username, - images: { - hero: pAccount.imagesHero, - tiny: pAccount.imagesTiny, - thumbnail: pAccount.imagesThumbnail, - }, - profile_detail: pAccount.profileDetail, - location: await buildLocationInfo(pAccount, aDomain), - when_account_created: pAccount.whenCreated?.toISOString(), - when_account_created_s: pAccount.whenCreated?.getTime().toString(), - time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat?.getTime().toString(), - }; + return { + accountId: pAccount.id, + id: pAccount.id, + username: pAccount.username, + images: { + hero: pAccount.imagesHero, + tiny: pAccount.imagesTiny, + thumbnail: pAccount.imagesThumbnail, + }, + profile_detail: pAccount.profileDetail, + location: await buildLocationInfo(pAccount, aDomain), + when_account_created: pAccount.whenCreated?.toISOString(), + when_account_created_s: pAccount.whenCreated?.getTime().toString(), + time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat?.getTime().toString(), + }; } \ No newline at end of file diff --git a/Goobieverse/src/responsebuilder/domainsBuilder.ts b/Goobieverse/src/responsebuilder/domainsBuilder.ts index a4f89b90..842582bb 100644 --- a/Goobieverse/src/responsebuilder/domainsBuilder.ts +++ b/Goobieverse/src/responsebuilder/domainsBuilder.ts @@ -5,76 +5,76 @@ import { buildPlacesForDomain } from './placesBuilder'; import { Maturity } from '../utils/sets/Maturity'; // A smaller, top-level domain info block export async function buildDomainInfo(pDomain: DomainModel): Promise { - return { - id: pDomain.id, - domainId: pDomain.id, - name: pDomain.name, - visibility: pDomain.visibility ?? Visibility.OPEN, - capacity: pDomain.capacity, - sponsorAccountId: pDomain.sponsorAccountId, - label: pDomain.name, - network_address: pDomain.networkAddr, - network_port: pDomain.networkPort, - ice_server_address: pDomain.iceServerAddr, - version: pDomain.version, - protocol_version: pDomain.protocol, - active: pDomain.active ?? false, - time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), - num_users: pDomain.numUsers, - }; + return { + id: pDomain.id, + domainId: pDomain.id, + name: pDomain.name, + visibility: pDomain.visibility ?? Visibility.OPEN, + capacity: pDomain.capacity, + sponsorAccountId: pDomain.sponsorAccountId, + label: pDomain.name, + network_address: pDomain.networkAddr, + network_port: pDomain.networkPort, + ice_server_address: pDomain.iceServerAddr, + version: pDomain.version, + protocol_version: pDomain.protocol, + active: pDomain.active ?? false, + time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), + num_users: pDomain.numUsers, + }; } // Return a structure with the usual domain information. export async function buildDomainInfoV1(pDomain: DomainModel): Promise { - return { - domainId: pDomain.id, - id: pDomain.id, // legacy - name: pDomain.name, - visibility: pDomain.visibility ?? Visibility.OPEN, - world_name: pDomain.name, // legacy - label: pDomain.name, // legacy - public_key: pDomain.publicKey? createSimplifiedPublicKey(pDomain.publicKey): undefined, - owner_places: await buildPlacesForDomain(pDomain), - sponsor_account_id: pDomain.sponsorAccountId, - ice_server_address: pDomain.iceServerAddr, - version: pDomain.version, - protocol_version: pDomain.protocol, - network_address: pDomain.networkAddr, - network_port: pDomain.networkPort, - automatic_networking: pDomain.networkingMode, - restricted: pDomain.restricted, - num_users: pDomain.numUsers, - anon_users: pDomain.anonUsers, - total_users: pDomain.numUsers, - capacity: pDomain.capacity, - description: pDomain.description, - maturity: pDomain.maturity ?? Maturity.UNRATED, - restriction: pDomain.restriction, - managers: pDomain.managers, - tags: pDomain.tags, - meta: { - capacity: pDomain.capacity, - contact_info: pDomain.contactInfo, - description: pDomain.description, - images: pDomain.images, - managers: pDomain.managers, - restriction: pDomain.restriction, - tags: pDomain.tags, - thumbnail: pDomain.thumbnail, - world_name: pDomain.name, - }, - users: { - num_anon_users: pDomain.anonUsers, - num_users: pDomain.numUsers, - user_hostnames: pDomain.hostnames, - }, - time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), - last_sender_key: pDomain.lastSenderKey, - addr_of_first_contact: pDomain.iPAddrOfFirstContact, - when_domain_entry_created: pDomain.whenCreated?.toISOString(), - when_domain_entry_created_s: pDomain.whenCreated?.getTime().toString(), - }; + return { + domainId: pDomain.id, + id: pDomain.id, // legacy + name: pDomain.name, + visibility: pDomain.visibility ?? Visibility.OPEN, + world_name: pDomain.name, // legacy + label: pDomain.name, // legacy + public_key: pDomain.publicKey? createSimplifiedPublicKey(pDomain.publicKey): undefined, + owner_places: await buildPlacesForDomain(pDomain), + sponsor_account_id: pDomain.sponsorAccountId, + ice_server_address: pDomain.iceServerAddr, + version: pDomain.version, + protocol_version: pDomain.protocol, + network_address: pDomain.networkAddr, + network_port: pDomain.networkPort, + automatic_networking: pDomain.networkingMode, + restricted: pDomain.restricted, + num_users: pDomain.numUsers, + anon_users: pDomain.anonUsers, + total_users: pDomain.numUsers, + capacity: pDomain.capacity, + description: pDomain.description, + maturity: pDomain.maturity ?? Maturity.UNRATED, + restriction: pDomain.restriction, + managers: pDomain.managers, + tags: pDomain.tags, + meta: { + capacity: pDomain.capacity, + contact_info: pDomain.contactInfo, + description: pDomain.description, + images: pDomain.images, + managers: pDomain.managers, + restriction: pDomain.restriction, + tags: pDomain.tags, + thumbnail: pDomain.thumbnail, + world_name: pDomain.name, + }, + users: { + num_anon_users: pDomain.anonUsers, + num_users: pDomain.numUsers, + user_hostnames: pDomain.hostnames, + }, + time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), + time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), + last_sender_key: pDomain.lastSenderKey, + addr_of_first_contact: pDomain.iPAddrOfFirstContact, + when_domain_entry_created: pDomain.whenCreated?.toISOString(), + when_domain_entry_created_s: pDomain.whenCreated?.getTime().toString(), + }; } \ No newline at end of file diff --git a/Goobieverse/src/responsebuilder/placesBuilder.ts b/Goobieverse/src/responsebuilder/placesBuilder.ts index 9fc18e58..76eed347 100644 --- a/Goobieverse/src/responsebuilder/placesBuilder.ts +++ b/Goobieverse/src/responsebuilder/placesBuilder.ts @@ -11,42 +11,42 @@ import { Maturity } from '../utils/sets/Maturity'; // Return a structure that represents the target account's domain export async function buildLocationInfo(pAcct: AccountModel,aDomain?: DomainModel): Promise { - let ret: any = {}; - if (pAcct.locationDomainId) { - if (IsNotNullOrEmpty(aDomain) && aDomain) { - ret = { - root: { - domain: await buildDomainInfo(aDomain), - }, - path: pAcct.locationPath, - }; - } else { - // The domain doesn't have an ID - ret = { - root: { - domain: { - network_address: pAcct.locationNetworkAddress, - network_port: pAcct.locationNetworkPort, - }, - }, - }; + let ret: any = {}; + if (pAcct.locationDomainId) { + if (IsNotNullOrEmpty(aDomain) && aDomain) { + ret = { + root: { + domain: await buildDomainInfo(aDomain), + }, + path: pAcct.locationPath, + }; + } else { + // The domain doesn't have an ID + ret = { + root: { + domain: { + network_address: pAcct.locationNetworkAddress, + network_port: pAcct.locationNetworkPort, + }, + }, + }; + } } - } - ret.node_id = pAcct.locationNodeId; - ret.online = isOnline(pAcct); - return ret; + ret.node_id = pAcct.locationNodeId; + ret.online = isOnline(pAcct); + return ret; } // Return an object with the formatted place information // Pass the PlaceModel and the place's domain if known. export async function buildPlaceInfo(pPlace: PlaceModel,pDomain?: DomainModel): Promise { - const ret = await buildPlaceInfoSmall(pPlace, pDomain); + const ret = await buildPlaceInfoSmall(pPlace, pDomain); - // if the place points to a domain, add that information also - if (IsNotNullOrEmpty(pDomain) && pDomain) { - ret.domain = await buildDomainInfo(pDomain); - } - return ret; + // if the place points to a domain, add that information also + if (IsNotNullOrEmpty(pDomain) && pDomain) { + ret.domain = await buildDomainInfo(pDomain); + } + return ret; } @@ -54,57 +54,57 @@ function getAddressString(pPlace: PlaceModel,aDomain?: DomainModel): string { // Compute and return the string for the Places's address. // The address is of the form "optional-domain/x,y,z/x,y,z,w". // If the domain is missing, the domain-server's network address is added - let addr = pPlace.path ?? '/0,0,0/0,0,0,1'; + let addr = pPlace.path ?? '/0,0,0/0,0,0,1'; - // If no domain/address specified in path, build addr using reported domain IP/port - const pieces = addr.split('/'); - if (pieces[0].length === 0) { - if (IsNotNullOrEmpty(aDomain) && aDomain) { - if (IsNotNullOrEmpty(aDomain.networkAddr)) { - let domainAddr = aDomain.networkAddr; - if (IsNotNullOrEmpty(aDomain.networkPort)) { - domainAddr = aDomain.networkAddr + ':' + aDomain.networkPort; + // If no domain/address specified in path, build addr using reported domain IP/port + const pieces = addr.split('/'); + if (pieces[0].length === 0) { + if (IsNotNullOrEmpty(aDomain) && aDomain) { + if (IsNotNullOrEmpty(aDomain.networkAddr)) { + let domainAddr = aDomain.networkAddr; + if (IsNotNullOrEmpty(aDomain.networkPort)) { + domainAddr = aDomain.networkAddr + ':' + aDomain.networkPort; + } + addr = domainAddr + addr; + } } - addr = domainAddr + addr; - } } - } - return addr; + return addr; } // Return the basic information block for a Place export async function buildPlaceInfoSmall(pPlace: PlaceModel,pDomain?: DomainModel): Promise { - const ret = { - placeId: pPlace.id, - id: pPlace.id, - name: pPlace.name, - displayName: pPlace.displayName, - visibility: pPlace.visibility ?? Visibility.OPEN, - address: getAddressString(pPlace,pDomain), - path: pPlace.path, - description: pPlace.description, - maturity: pPlace.maturity ?? Maturity.UNRATED, - tags: pPlace.tags, - managers: await getManagers(pPlace,pDomain), - thumbnail: pPlace.thumbnail, - images: pPlace.images, - current_attendance: pPlace.currentAttendance ?? 0, - current_images: pPlace.currentImages, - current_info: pPlace.currentInfo, - current_last_update_time: pPlace.currentLastUpdateTime?.toISOString(), - current_last_update_time_s: pPlace.currentLastUpdateTime?.getTime().toString(), - last_activity_update: pPlace.lastActivity?.toISOString(), - last_activity_update_s: pPlace.lastActivity?.getTime().toString(), - }; - return ret; + const ret = { + placeId: pPlace.id, + id: pPlace.id, + name: pPlace.name, + displayName: pPlace.displayName, + visibility: pPlace.visibility ?? Visibility.OPEN, + address: getAddressString(pPlace,pDomain), + path: pPlace.path, + description: pPlace.description, + maturity: pPlace.maturity ?? Maturity.UNRATED, + tags: pPlace.tags, + managers: await getManagers(pPlace,pDomain), + thumbnail: pPlace.thumbnail, + images: pPlace.images, + current_attendance: pPlace.currentAttendance ?? 0, + current_images: pPlace.currentImages, + current_info: pPlace.currentInfo, + current_last_update_time: pPlace.currentLastUpdateTime?.toISOString(), + current_last_update_time_s: pPlace.currentLastUpdateTime?.getTime().toString(), + last_activity_update: pPlace.lastActivity?.toISOString(), + last_activity_update_s: pPlace.lastActivity?.getTime().toString(), + }; + return ret; } async function getManagers(pPlace: PlaceModel,aDomain?: DomainModel): Promise { - if(IsNullOrEmpty(pPlace.managers)) { - pPlace.managers = []; - //uncomment after complete Accounts Places api + if(IsNullOrEmpty(pPlace.managers)) { + pPlace.managers = []; + //uncomment after complete Accounts Places api /* if (aDomain) { const aAccount = await Accounts.getAccountWithId(aDomain.sponsorAccountId); @@ -114,17 +114,17 @@ async function getManagers(pPlace: PlaceModel,aDomain?: DomainModel): Promise { - const ret: any[] = []; - //uncomment after complete Places api - /* for await (const aPlace of Places.enumerateAsync(new GenericFilter({ domainId: pDomain.id }))) { + const ret: any[] = []; + //uncomment after complete Places api + /* for await (const aPlace of Places.enumerateAsync(new GenericFilter({ domainId: pDomain.id }))) { ret.push(await buildPlaceInfoSmall(aPlace, pDomain)); }*/ - return ret; + return ret; } \ No newline at end of file diff --git a/Goobieverse/src/services/accounts/accounts.class.ts b/Goobieverse/src/services/accounts/accounts.class.ts index b43c5c82..db64af00 100644 --- a/Goobieverse/src/services/accounts/accounts.class.ts +++ b/Goobieverse/src/services/accounts/accounts.class.ts @@ -4,8 +4,7 @@ import { Params, Id, NullableId } from '@feathersjs/feathers'; import { Application } from '../../declarations'; import { DatabaseService } from './../../dbservice/DatabaseService'; import config from '../../appconfig'; -import { AccountModel } from '../../interfaces/AccountModel'; -import { DomainModel } from './../../interfaces/DomainModel'; +import { AccountModel } from '../../interfaces/AccountModel'; import { buildAccountInfo } from '../../responsebuilder/accountsBuilder'; import { IsNotNullOrEmpty } from '../../utils/Misc'; import { isAdmin,dateWhenNotOnline,couldBeDomainId,validateEmail } from '../../utils/Utils'; @@ -13,164 +12,162 @@ import { messages } from '../../utils/messages'; import { VKeyedCollection,SArray } from '../../utils/vTypes'; export class Accounts extends DatabaseService { - constructor(options: Partial, app: Application) { - super(options,app); - } - - async find(params?: Params): Promise { - - const loginUserId = params?.user?.id ?? ''; - const perPage = parseInt(params?.query?.per_page) || 10; - const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; - - - // Passed the request, get the filter parameters from the query. - // Here we pre-process the parameters to make the DB query construction quicker. - // filter=connections|friends|all - // status=online|domainId - // search=wildcardSearchString - // The administrator can specify an account to limit requests to - // acct = account id - //asAdmin=true: if logged in account is administrator, list all accounts. Value is optional. - let asAdmin = params?.query?.asAdmin === 'true'?true:false; - const filter:string[] = (params?.query?.filter ?? '').split(','); - const status:string = params?.query?.status ?? ''; - const targetAccount = params?.query?.account ?? ''; - - - const filterQuery: any = {}; - - if(asAdmin && IsNotNullOrEmpty(params?.user) && isAdmin(params?.user as AccountModel)){ - asAdmin = true; - }else{ - asAdmin =false; + constructor(options: Partial, app: Application) { + super(options,app); } + + async find(params?: Params): Promise { + const loginUserId = params?.user?.id ?? ''; + const perPage = parseInt(params?.query?.per_page) || 10; + const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; - if(filter.length > 0){ - if(!filter.includes('all')){ - if(filter.includes('friends') && (params?.user?.friends??[]).length > 0){ - filterQuery.friends = {$in: params?.user?.friends}; + // Passed the request, get the filter parameters from the query. + // Here we pre-process the parameters to make the DB query construction quicker. + // filter=connections|friends|all + // status=online|domainId + // search=wildcardSearchString + // The administrator can specify an account to limit requests to + // acct = account id + //asAdmin=true: if logged in account is administrator, list all accounts. Value is optional. + let asAdmin = params?.query?.asAdmin === 'true'?true:false; + const filter:string[] = (params?.query?.filter ?? '').split(','); + const status:string = params?.query?.status ?? ''; + const targetAccount = params?.query?.account ?? ''; + + const filterQuery: any = {}; + + if(asAdmin && IsNotNullOrEmpty(params?.user) && isAdmin(params?.user as AccountModel)){ + asAdmin = true; + }else{ + asAdmin =false; } - if(filter.includes('connections') && (params?.user?.connections??[]).length > 0){ - filterQuery.connections = {$in: params?.user?.connections}; + + if(filter.length > 0){ + if(!filter.includes('all')){ + if(filter.includes('friends') && (params?.user?.friends??[]).length > 0){ + filterQuery.friends = {$in: params?.user?.friends}; + } + if(filter.includes('connections') && (params?.user?.connections??[]).length > 0){ + filterQuery.connections = {$in: params?.user?.connections}; + } + } } - } - } - if(IsNotNullOrEmpty(status)){ - if (status ==='online'){ - filterQuery.timeOfLastHeartbeat = {$gte:dateWhenNotOnline()}; - }else if(couldBeDomainId(status)){ - filterQuery.locationDomainId = status; - } - } + if(IsNotNullOrEmpty(status)){ + if (status ==='online'){ + filterQuery.timeOfLastHeartbeat = {$gte:dateWhenNotOnline()}; + }else if(couldBeDomainId(status)){ + filterQuery.locationDomainId = status; + } + } - if(!asAdmin){ - filterQuery.id = loginUserId; - }else if(IsNotNullOrEmpty(targetAccount)){ - filterQuery.id = targetAccount; - } + if(!asAdmin){ + filterQuery.id = loginUserId; + }else if(IsNotNullOrEmpty(targetAccount)){ + filterQuery.id = targetAccount; + } - const accountData = await this.findData(config.dbCollections.accounts,{ - query:{ - ...filterQuery, - $skip: skip, - $limit: perPage - }, - }); + const accountData = await this.findData(config.dbCollections.accounts,{ + query:{ + ...filterQuery, + $skip: skip, + $limit: perPage + }, + }); - let accountsList:AccountModel[] = []; + let accountsList:AccountModel[] = []; - if(accountData instanceof Array){ - accountsList = accountData as Array; - }else{ - accountsList = accountData.data as Array; - } + if(accountData instanceof Array){ + accountsList = accountData as Array; + }else{ + accountsList = accountData.data as Array; + } - const accounts: Array = []; + const accounts: Array = []; - (accountsList as Array)?.forEach(async (element) => { + (accountsList as Array)?.forEach(async (element) => { - accounts.push(await buildAccountInfo(element)); - }); - return Promise.resolve({ accounts }); - } - - async get(id: Id, params: Params): Promise { - const objAccount = await this.getData(config.dbCollections.accounts,id); - if(IsNotNullOrEmpty(objAccount)){ - const account = await buildAccountInfo(objAccount); - return Promise.resolve({ account }); - } else { - throw new Error(messages.common_messages_target_account_notfound); + accounts.push(await buildAccountInfo(element)); + }); + return Promise.resolve({ accounts }); } - } - - async patch(id: NullableId, data: any, params: Params): Promise { - if(IsNotNullOrEmpty(id)){ - if(IsNotNullOrEmpty(params?.user) && isAdmin(params?.user as AccountModel) || id === params?.user?.id){ - const valuesToSet = data.accounts??{}; - const updates: VKeyedCollection = {}; - if(IsNotNullOrEmpty(valuesToSet.email)) { - if(!validateEmail(valuesToSet.email)){ - throw new Error(messages.common_messages_email_validation_error); - } - const accountData = await this.findDataToArray(config.dbCollections.accounts,{query:{email: valuesToSet.email}}); - if(accountData.length>0 && accountData[0].id !== id){ - throw new Error(messages.common_messages_user_email_link_error); - } - updates.email = valuesToSet.email; - } - if(IsNotNullOrEmpty(valuesToSet.public_key)) { - updates.public_key = valuesToSet.public_key; + + async get(id: Id): Promise { + const objAccount = await this.getData(config.dbCollections.accounts,id); + if(IsNotNullOrEmpty(objAccount)){ + const account = await buildAccountInfo(objAccount); + return Promise.resolve({ account }); + } else { + throw new Error(messages.common_messages_target_account_notfound); } - if (valuesToSet.hasOwnProperty('images')) { - if (IsNotNullOrEmpty(valuesToSet.images.hero)) { - updates.imagesHero = valuesToSet.images.hero; - } + } + + async patch(id: NullableId, data: any, params: Params): Promise { + if(IsNotNullOrEmpty(id) && id){ + if(IsNotNullOrEmpty(params?.user) && isAdmin(params?.user as AccountModel) || id === params?.user?.id){ + const valuesToSet = data.accounts??{}; + const updates: VKeyedCollection = {}; + if(IsNotNullOrEmpty(valuesToSet.email)) { + if(!validateEmail(valuesToSet.email)){ + throw new Error(messages.common_messages_email_validation_error); + } + const accountData = await this.findDataToArray(config.dbCollections.accounts,{query:{email: valuesToSet.email}}); + if(accountData.length>0 && accountData[0].id !== id){ + throw new Error(messages.common_messages_user_email_link_error); + } + updates.email = valuesToSet.email; + } + if(IsNotNullOrEmpty(valuesToSet.public_key)) { + updates.public_key = valuesToSet.public_key; + } + + if (valuesToSet.hasOwnProperty('images')) { + if (IsNotNullOrEmpty(valuesToSet.images.hero)) { + updates.imagesHero = valuesToSet.images.hero; + } - if (IsNotNullOrEmpty(valuesToSet.images.tiny)) { - updates.imagesTiny = valuesToSet.images.tiny; - } + if (IsNotNullOrEmpty(valuesToSet.images.tiny)) { + updates.imagesTiny = valuesToSet.images.tiny; + } - if (IsNotNullOrEmpty(valuesToSet.images.thumbnail)) { - updates.imagesThumbnail = valuesToSet.images.thumbnail; - } + if (IsNotNullOrEmpty(valuesToSet.images.thumbnail)) { + updates.imagesThumbnail = valuesToSet.images.thumbnail; + } + } + await this.patchData(config.dbCollections.accounts,id,updates); + return Promise.resolve(); + }else{ + throw new Error(messages.common_messages_unauthorized); + } + } else { + throw new Error(messages.common_messages_target_account_notfound); } - await this.patchData(config.dbCollections.accounts,id!,updates); - return Promise.resolve(); - }else{ - throw new Error(messages.common_messages_unauthorized); - } - } else { - throw new Error(messages.common_messages_target_account_notfound); } - } - async remove(id: NullableId, params?: Params): Promise { - if(IsNotNullOrEmpty(id)){ - const account = await this.getData(config.dbCollections.accounts,id!); + async remove(id: NullableId): Promise { + if(IsNotNullOrEmpty(id) && id){ + const account = await this.getData(config.dbCollections.accounts,id); - if(IsNotNullOrEmpty(account)){ - this.deleteData(config.dbCollections.accounts,id!); - const accounts:AccountModel[] = await this.findDataToArray(config.dbCollections.accounts, {query:{$or:[{connections:{$in: [account.username] }},{friends:{$in:[account.username] }}]}}); + if(IsNotNullOrEmpty(account)){ + this.deleteData(config.dbCollections.accounts,id); + const accounts:AccountModel[] = await this.findDataToArray(config.dbCollections.accounts, {query:{$or:[{connections:{$in: [account.username] }},{friends:{$in:[account.username] }}]}}); - for(const element of accounts){ - SArray.remove(element.connections,account.username); - SArray.remove(element.friends,account.username); - await super.patchData(config.dbCollections.accounts,element.id,{connections:element.connections,friends:element.friends}); - } - - await this.deleteMultipleData(config.dbCollections.domains,{query:{sponsorAccountId:account.id}}); - await this.deleteMultipleData(config.dbCollections.places,{query:{accountId:account.id}}); + for(const element of accounts){ + SArray.remove(element.connections,account.username); + SArray.remove(element.friends,account.username); + await super.patchData(config.dbCollections.accounts,element.id,{connections:element.connections,friends:element.friends}); + } + + await this.deleteMultipleData(config.dbCollections.domains,{query:{sponsorAccountId:account.id}}); + await this.deleteMultipleData(config.dbCollections.places,{query:{accountId:account.id}}); - return Promise.resolve(); - }else{ - throw new Error(messages.common_messages_target_account_notfound); - } - }else{ - throw new Error(messages.common_messages_target_account_notfound); + return Promise.resolve(); + }else{ + throw new Error(messages.common_messages_target_account_notfound); + } + }else{ + throw new Error(messages.common_messages_target_account_notfound); + } } - } } diff --git a/Goobieverse/src/services/accounts/accounts.hooks.ts b/Goobieverse/src/services/accounts/accounts.hooks.ts index bcbcc7da..b416f3c6 100644 --- a/Goobieverse/src/services/accounts/accounts.hooks.ts +++ b/Goobieverse/src/services/accounts/accounts.hooks.ts @@ -10,33 +10,33 @@ import { iff } from 'feathers-hooks-common'; import { disallow } from 'feathers-hooks-common'; export default { - before: { - all: [], - find: [authenticate('jwt')], - get: [iff(isHasAuthToken(),authenticate('jwt')),checkAccessToAccount(config.dbCollections.accounts,[Perm.PUBLIC,Perm.OWNER,Perm.ADMIN])], - create: [disallow()], - update: [disallow()], - patch: [authenticate('jwt')], - remove: [authenticate('jwt'),checkAccessToAccount(config.dbCollections.accounts,[Perm.ADMIN])] - }, + before: { + all: [], + find: [authenticate('jwt')], + get: [iff(isHasAuthToken(),authenticate('jwt')),checkAccessToAccount(config.dbCollections.accounts,[Perm.PUBLIC,Perm.OWNER,Perm.ADMIN])], + create: [disallow()], + update: [disallow()], + patch: [authenticate('jwt')], + remove: [authenticate('jwt'),checkAccessToAccount(config.dbCollections.accounts,[Perm.ADMIN])] + }, - after: { - all: [requestSuccess()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, - error: { - all: [requestFail()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } }; diff --git a/Goobieverse/src/services/accounts/accounts.service.ts b/Goobieverse/src/services/accounts/accounts.service.ts index ed3e116d..1bdf1a33 100644 --- a/Goobieverse/src/services/accounts/accounts.service.ts +++ b/Goobieverse/src/services/accounts/accounts.service.ts @@ -1,7 +1,7 @@ // Initializes the `accounts` service on path `/accounts` import { ServiceAddons } from '@feathersjs/feathers'; import { Application } from '../../declarations'; -import { Accounts } from './accounts.class'; +import { Accounts } from './accounts.class'; import hooks from './accounts.hooks'; // Add this service to the service type index @@ -12,19 +12,17 @@ declare module '../../declarations' { } export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - id:'id', - multi:['remove'] - }; + const options = { + paginate: app.get('paginate'), + id:'id', + multi:['remove'] + }; - // Initialize our service with any options it requires - app.use('/accounts', new Accounts(options, app)); + // Initialize our service with any options it requires + app.use('/accounts', new Accounts(options, app)); - //app.use('/accounts/:accountId/field/:fieldName', app.service('accounts')); - - // Get our initialized service so that we can register hooks - const service = app.service('accounts'); - - service.hooks(hooks); + //app.use('/accounts/:accountId/field/:fieldName', app.service('accounts')); + // Get our initialized service so that we can register hooks + const service = app.service('accounts'); + service.hooks(hooks); } diff --git a/Goobieverse/src/services/auth/auth.class.ts b/Goobieverse/src/services/auth/auth.class.ts index d78dfc67..ce6fc722 100644 --- a/Goobieverse/src/services/auth/auth.class.ts +++ b/Goobieverse/src/services/auth/auth.class.ts @@ -3,11 +3,11 @@ import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; import { Application } from '../../declarations'; export class Auth extends Service { - constructor(options: Partial, app: Application) { - super(options); - const client: Promise = app.get('mongoClient'); - client.then((db) => { - this.Model = db.collection('accounts'); - }); - } + constructor(options: Partial, app: Application) { + super(options); + const client: Promise = app.get('mongoClient'); + client.then((db) => { + this.Model = db.collection('accounts'); + }); + } } diff --git a/Goobieverse/src/services/auth/auth.hooks.ts b/Goobieverse/src/services/auth/auth.hooks.ts index 661db2a9..006e7f9d 100644 --- a/Goobieverse/src/services/auth/auth.hooks.ts +++ b/Goobieverse/src/services/auth/auth.hooks.ts @@ -3,36 +3,36 @@ import * as feathersAuthentication from '@feathersjs/authentication'; import * as local from '@feathersjs/authentication-local'; import { disallow } from 'feathers-hooks-common'; const { authenticate } = feathersAuthentication.hooks; -const { hashPassword, protect } = local.hooks; +const { protect } = local.hooks; export default { - before: { - all: [], - find: [ authenticate('jwt') ], - get: [ authenticate('jwt') ], - create: [disallow('external')], - update: [disallow('external')], - patch: [disallow('external')], - remove: [ disallow('external')] - }, + before: { + all: [], + find: [ authenticate('jwt') ], + get: [ authenticate('jwt') ], + create: [disallow('external')], + update: [disallow('external')], + patch: [disallow('external')], + remove: [ disallow('external')] + }, - after: { - all: [protect('password')], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, + after: { + all: [protect('password')], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, - error: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, + error: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, } as HooksObject; diff --git a/Goobieverse/src/services/auth/auth.service.ts b/Goobieverse/src/services/auth/auth.service.ts index 3c8618a1..6311c26e 100644 --- a/Goobieverse/src/services/auth/auth.service.ts +++ b/Goobieverse/src/services/auth/auth.service.ts @@ -12,15 +12,15 @@ declare module '../../declarations' { } export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - }; + const options = { + paginate: app.get('paginate'), + }; - // Initialize our service with any options it requires - app.use('/auth', new Auth(options, app)); + // Initialize our service with any options it requires + app.use('/auth', new Auth(options, app)); - // Get our initialized service so that we can register hooks - const service = app.service('auth'); + // Get our initialized service so that we can register hooks + const service = app.service('auth'); - service.hooks(hooks); + service.hooks(hooks); } diff --git a/Goobieverse/src/services/connections/connections.class.ts b/Goobieverse/src/services/connections/connections.class.ts index 12669c87..31a252cc 100644 --- a/Goobieverse/src/services/connections/connections.class.ts +++ b/Goobieverse/src/services/connections/connections.class.ts @@ -6,27 +6,27 @@ import { Response } from '../../utils/response'; import { isValidObject } from '../../utils/Misc'; export class Connections extends DatabaseService { - //eslint-disable-next-line @typescript-eslint/no-unused-vars - constructor(options: Partial, app: Application) { - super(options, app); - this.app = app; - } + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options, app); + this.app = app; + } - async create(data: any, params?: any): Promise { - if (data && data.username) { - const userData: any = await this.getData(config.dbCollections.accounts, params.user.id); + async create(data: any, params?: any): Promise { + if (data && data.username) { + const userData: any = await this.getData(config.dbCollections.accounts, params.user.id); - userData.connections.push(data.username); - const addUserData = await this.patchData(config.dbCollections.accounts,params.user.id,userData); - if (isValidObject(addUserData)) { - return Promise.resolve({}); - } else { - return Response.error('cannot add connections this way'); - } - } else { - return Response.error('Badly formed request'); + userData.connections.push(data.username); + const addUserData = await this.patchData(config.dbCollections.accounts,params.user.id,userData); + if (isValidObject(addUserData)) { + return Promise.resolve({}); + } else { + return Response.error('cannot add connections this way'); + } + } else { + return Response.error('Badly formed request'); + } } - } } diff --git a/Goobieverse/src/services/connections/connections.hooks.ts b/Goobieverse/src/services/connections/connections.hooks.ts index 4e5135ae..4b49e80e 100644 --- a/Goobieverse/src/services/connections/connections.hooks.ts +++ b/Goobieverse/src/services/connections/connections.hooks.ts @@ -6,33 +6,33 @@ import requestSuccess from '../../hooks/requestSuccess'; const { authenticate } = authentication.hooks; export default { - before: { - all: [authenticate('jwt')], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, + before: { + all: [authenticate('jwt')], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, - after: { - all: [requestSuccess()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, - error: { - all: [requestFail()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } } as HooksObject; diff --git a/Goobieverse/src/services/connections/connections.service.ts b/Goobieverse/src/services/connections/connections.service.ts index 486e7e82..7c808902 100644 --- a/Goobieverse/src/services/connections/connections.service.ts +++ b/Goobieverse/src/services/connections/connections.service.ts @@ -12,16 +12,16 @@ declare module '../../declarations' { } export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - id:'id' - }; + const options = { + paginate: app.get('paginate'), + id:'id' + }; - // Initialize our service with any options it requires - app.use('/connections', new Connections(options, app)); + // Initialize our service with any options it requires + app.use('/connections', new Connections(options, app)); - // Get our initialized service so that we can register hooks - const service = app.service('connections'); + // Get our initialized service so that we can register hooks + const service = app.service('connections'); - service.hooks(hooks); + service.hooks(hooks); } diff --git a/Goobieverse/src/services/email/email.class.ts b/Goobieverse/src/services/email/email.class.ts index 188d2910..33ea265a 100644 --- a/Goobieverse/src/services/email/email.class.ts +++ b/Goobieverse/src/services/email/email.class.ts @@ -1,12 +1,9 @@ -import { Db } from 'mongodb'; import { DatabaseService } from './../../dbservice/DatabaseService'; import { DatabaseServiceOptions } from './../../dbservice/DatabaseServiceOptions'; import { Application } from '../../declarations'; -import config from '../../appconfig'; export class Email extends DatabaseService { - //eslint-disable-next-line @typescript-eslint/no-unused-vars - constructor(options: Partial, app: Application) { - super(options,app); - this.getService(config.dbCollections.email); - } + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options,app); + } } diff --git a/Goobieverse/src/services/email/email.hooks.ts b/Goobieverse/src/services/email/email.hooks.ts index cfbd4fde..71a766e7 100644 --- a/Goobieverse/src/services/email/email.hooks.ts +++ b/Goobieverse/src/services/email/email.hooks.ts @@ -1,33 +1,33 @@ import { HooksObject } from '@feathersjs/feathers'; export default { - before: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, + before: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, - after: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, + after: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, - error: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } + error: { + all: [], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } } as HooksObject; diff --git a/Goobieverse/src/services/email/email.service.ts b/Goobieverse/src/services/email/email.service.ts index cb018f96..c238f3f3 100644 --- a/Goobieverse/src/services/email/email.service.ts +++ b/Goobieverse/src/services/email/email.service.ts @@ -16,10 +16,10 @@ declare module '../../declarations' { } export default function (app: Application): void { - const event = Mailer(smtpTransport(config.email)); - app.use('email', event); + const event = Mailer(smtpTransport(config.email)); + app.use('email', event); - const service = app.service('email'); + const service = app.service('email'); - service.hooks(hooks); + service.hooks(hooks); } diff --git a/Goobieverse/src/services/friends/friends.class.ts b/Goobieverse/src/services/friends/friends.class.ts index ce9bc817..beaf666d 100644 --- a/Goobieverse/src/services/friends/friends.class.ts +++ b/Goobieverse/src/services/friends/friends.class.ts @@ -2,53 +2,52 @@ import { MongoDBServiceOptions } from 'feathers-mongodb'; import { DatabaseService } from './../../dbservice/DatabaseService'; import { Application } from '../../declarations'; import config from '../../appconfig'; -import { Response } from '../../utils/response'; -import { Params } from '@feathersjs/feathers'; +import { Response } from '../../utils/response'; export class Friends extends DatabaseService { - //eslint-disable-next-line @typescript-eslint/no-unused-vars - constructor(options: Partial, app: Application) { - super(options, app); - this.app = app; - } + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options, app); + this.app = app; + } - async create(data: any, params?: any): Promise { - if (data && data.username) { - const ParticularUserData: any = await this.findData(config.dbCollections.accounts, { query: { id: params.user.id } }); - if (ParticularUserData.data[0].connections.includes(data.username)) { - const newParticularUserData = ParticularUserData.data[0]; - newParticularUserData.friends.push(data.username); - await this.patchData(config.dbCollections.accounts, params.user.id,newParticularUserData); - } else { - return Response.error('cannot add friend who is not a connection'); - } - } else { - return Response.error('Badly formed request'); + async create(data: any, params?: any): Promise { + if (data && data.username) { + const ParticularUserData: any = await this.findData(config.dbCollections.accounts, { query: { id: params.user.id } }); + if (ParticularUserData.data[0].connections.includes(data.username)) { + const newParticularUserData = ParticularUserData.data[0]; + newParticularUserData.friends.push(data.username); + await this.patchData(config.dbCollections.accounts, params.user.id,newParticularUserData); + } else { + return Response.error('cannot add friend who is not a connection'); + } + } else { + return Response.error('Badly formed request'); + } } - } - async find(params?: any): Promise { - if (params.user.friends) { - const friends = params.user.friends; - return Promise.resolve({ friends }); - } else { - throw new Error('No friend found'); + async find(params?: any): Promise { + if (params.user.friends) { + const friends = params.user.friends; + return Promise.resolve({ friends }); + } else { + throw new Error('No friend found'); + } } - } - async remove(id: string, params?: any): Promise { - if (params.user.friends) { - const ParticularUserData: any = await this.findData(config.dbCollections.accounts, { query: { id: params.user.id } }); - const friends = ParticularUserData.data[0].friends.filter(function (value:string) { - return value !== id; - }); - ParticularUserData.data[0].friends = friends; - const newParticularUserData = ParticularUserData.data[0]; - await this.patchData(config.dbCollections.accounts,params.user.id,newParticularUserData); - } else { - throw new Error('Not logged in'); + async remove(id: string, params?: any): Promise { + if (params.user.friends) { + const ParticularUserData: any = await this.findData(config.dbCollections.accounts, { query: { id: params.user.id } }); + const friends = ParticularUserData.data[0].friends.filter(function (value:string) { + return value !== id; + }); + ParticularUserData.data[0].friends = friends; + const newParticularUserData = ParticularUserData.data[0]; + await this.patchData(config.dbCollections.accounts,params.user.id,newParticularUserData); + } else { + throw new Error('Not logged in'); + } } - } } diff --git a/Goobieverse/src/services/friends/friends.hooks.ts b/Goobieverse/src/services/friends/friends.hooks.ts index e2333ff5..b91b71ce 100644 --- a/Goobieverse/src/services/friends/friends.hooks.ts +++ b/Goobieverse/src/services/friends/friends.hooks.ts @@ -6,33 +6,33 @@ import requestSuccess from '../../hooks/requestSuccess'; const { authenticate } = authentication.hooks; export default { - before: { - all: [authenticate('jwt')], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, + before: { + all: [authenticate('jwt')], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, - after: { - all: [requestSuccess()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, - error: { - all: [requestFail()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, } as HooksObject; diff --git a/Goobieverse/src/services/friends/friends.service.ts b/Goobieverse/src/services/friends/friends.service.ts index 17e3180b..9c3117c3 100644 --- a/Goobieverse/src/services/friends/friends.service.ts +++ b/Goobieverse/src/services/friends/friends.service.ts @@ -12,16 +12,16 @@ declare module '../../declarations' { } export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - id:'id' - }; + const options = { + paginate: app.get('paginate'), + id:'id' + }; - // Initialize our service with any options it requires - app.use('/friends', new Friends(options, app)); + // Initialize our service with any options it requires + app.use('/friends', new Friends(options, app)); - // Get our initialized service so that we can register hooks - const service = app.service('friends'); + // Get our initialized service so that we can register hooks + const service = app.service('friends'); - service.hooks(hooks); + service.hooks(hooks); } diff --git a/Goobieverse/src/services/index.ts b/Goobieverse/src/services/index.ts index e6ae5664..83238d5e 100644 --- a/Goobieverse/src/services/index.ts +++ b/Goobieverse/src/services/index.ts @@ -10,11 +10,11 @@ import connections from './connections/connections.service'; import accounts from './accounts/accounts.service'; export default function (app: Application): void { - app.configure(auth); - app.configure(users); - app.configure(friends); - app.configure(profiles); - app.configure(accounts); - app.configure(email); - app.configure(connections); + app.configure(auth); + app.configure(users); + app.configure(friends); + app.configure(profiles); + app.configure(accounts); + app.configure(email); + app.configure(connections); } diff --git a/Goobieverse/src/services/profiles/profiles.class.ts b/Goobieverse/src/services/profiles/profiles.class.ts index a97f0cd6..baebc707 100644 --- a/Goobieverse/src/services/profiles/profiles.class.ts +++ b/Goobieverse/src/services/profiles/profiles.class.ts @@ -9,69 +9,78 @@ import { Application } from '../../declarations'; import { buildAccountProfile } from '../../responsebuilder/accountsBuilder'; import { IsNotNullOrEmpty } from '../../utils/Misc'; import { messages } from '../../utils/messages'; + export class Profiles extends DatabaseService { + constructor(options: Partial, app: Application) { + super(options, app); + } - constructor(options: Partial, app: Application) { - super(options,app); - } + async find(params?: Params): Promise { + const perPage = parseInt(params?.query?.per_page) || 10; + const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; - async find(params?: Params): Promise { - - const perPage = parseInt(params?.query?.per_page) || 10; - const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; + const accountData = await this.findData(config.dbCollections.accounts, { + query: { + $or: [ + { availability: undefined }, + { availability: Availability.ALL }, + ], + $skip: skip, + $limit: perPage, + }, + }); - const accountData = await this.findData(config.dbCollections.accounts,{ - query: { - $or: [{availability:undefined},{availability: Availability.ALL }], - $skip: skip, - $limit: perPage, - }, - }); + let accounts: AccountModel[] = []; - let accounts:AccountModel[] = []; + if (accountData instanceof Array) { + accounts = accountData as Array; + } else { + accounts = accountData.data as Array; + } - if(accountData instanceof Array){ - accounts = accountData as Array; - }else{ - accounts = accountData.data as Array; - } + const domainIds = (accounts as Array) + ?.map((item) => item.locationDomainId) + .filter( + (value, index, self) => + self.indexOf(value) === index && value !== undefined + ); + const domains = await this.findDataToArray( + config.dbCollections.domains, + { query: { id: { $in: domainIds } } } + ); - const domainIds = (accounts as Array) - ?.map((item) => item.locationDomainId) - .filter( - (value, index, self) => - self.indexOf(value) === index && value !== undefined - ); - - const domains= await this.findDataToArray(config.dbCollections.domains,{ query:{id: { $in: domainIds }}}); + const profiles: Array = []; - const profiles: Array = []; + (accounts as Array)?.forEach(async (element) => { + let domainModel: DomainModel | undefined; + for (const domain of domains) { + if (domain && domain.id === element.locationDomainId) { + domainModel = domain; + break; + } + } + profiles.push(await buildAccountProfile(element, domainModel)); + }); + return Promise.resolve({ profiles }); + } - (accounts as Array)?.forEach(async (element) => { - let domainModel: DomainModel | undefined; - for (const domain of domains) { - if (domain && domain.id === element.locationDomainId) { - domainModel = domain; - break; - } - } - profiles.push(await buildAccountProfile(element, domainModel)); - }); - return Promise.resolve({ profiles }); - } + async get(id: Id): Promise { + const account = await this.getData(config.dbCollections.accounts, id); - async get(id: Id, params: Params): Promise { - const account = await this.getData(config.dbCollections.accounts,id); - - if(IsNotNullOrEmpty(account)){ - const domains = await this.findDataToArray(config.dbCollections.domains,{ id: { $eq: account.locationDomainId } }); - let domainModel: any; - if(IsNotNullOrEmpty(domains)){domainModel = domains[0];} - const profile = await buildAccountProfile(account, domainModel); - return Promise.resolve({ profile }); - }else{ - throw new Error(messages.common_messages_target_profile_notfound); + if (IsNotNullOrEmpty(account)) { + const domains = await this.findDataToArray( + config.dbCollections.domains, + { id: { $eq: account.locationDomainId } } + ); + let domainModel: any; + if (IsNotNullOrEmpty(domains)) { + domainModel = domains[0]; + } + const profile = await buildAccountProfile(account, domainModel); + return Promise.resolve({ profile }); + } else { + throw new Error(messages.common_messages_target_profile_notfound); + } } - } } diff --git a/Goobieverse/src/services/profiles/profiles.hooks.ts b/Goobieverse/src/services/profiles/profiles.hooks.ts index 33e0bb88..aa41b209 100644 --- a/Goobieverse/src/services/profiles/profiles.hooks.ts +++ b/Goobieverse/src/services/profiles/profiles.hooks.ts @@ -11,33 +11,33 @@ const { authenticate } = feathersAuthentication.hooks; export default { - before: { - all: [iff(isHasAuthToken(),authenticate('jwt'))], - find: [], - get: [checkAccessToAccount(config.dbCollections.accounts,[Perm.PUBLIC,Perm.OWNER,Perm.ADMIN])], - create: [disallow()], - update: [disallow()], - patch: [disallow()], - remove: [disallow()] - }, + before: { + all: [iff(isHasAuthToken(),authenticate('jwt'))], + find: [], + get: [checkAccessToAccount(config.dbCollections.accounts,[Perm.PUBLIC,Perm.OWNER,Perm.ADMIN])], + create: [disallow()], + update: [disallow()], + patch: [disallow()], + remove: [disallow()] + }, - after: { - all: [requestSuccess()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + }, - error: { - all: [requestFail()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [] + } }; diff --git a/Goobieverse/src/services/profiles/profiles.service.ts b/Goobieverse/src/services/profiles/profiles.service.ts index b146299f..13f86647 100644 --- a/Goobieverse/src/services/profiles/profiles.service.ts +++ b/Goobieverse/src/services/profiles/profiles.service.ts @@ -12,16 +12,16 @@ declare module '../../declarations' { } export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - id:'id', - }; + const options = { + paginate: app.get('paginate'), + id:'id', + }; - // Initialize our service with any options it requires - app.use('/profiles', new Profiles(options, app)); + // Initialize our service with any options it requires + app.use('/profiles', new Profiles(options, app)); - // Get our initialized service so that we can register hooks - const service = app.service('profiles'); + // Get our initialized service so that we can register hooks + const service = app.service('profiles'); - service.hooks(hooks); + service.hooks(hooks); } diff --git a/Goobieverse/src/services/users/users.class.ts b/Goobieverse/src/services/users/users.class.ts index ddcbca12..a5f00f06 100644 --- a/Goobieverse/src/services/users/users.class.ts +++ b/Goobieverse/src/services/users/users.class.ts @@ -1,8 +1,8 @@ import { DatabaseService } from './../../dbservice/DatabaseService'; -import { MongoDBServiceOptions } from 'feathers-mongodb'; +import { MongoDBServiceOptions } from 'feathers-mongodb'; import { Application } from '../../declarations'; import config from '../../appconfig'; -import { Params } from '@feathersjs/feathers'; +import { Params } from '@feathersjs/feathers'; import { AccountModel } from '../../interfaces/AccountModel'; import { GenUUID } from '../../utils/Misc'; import { Roles } from '../../utils/sets/Roles'; @@ -13,122 +13,164 @@ import path from 'path'; import fsPromises from 'fs/promises'; export class Users extends DatabaseService { - app: Application; - //eslint-disable-next-line @typescript-eslint/no-unused-vars - constructor(options: Partial, app: Application) { - super(options, app); - this.app = app; - } + app: Application; + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options, app); + this.app = app; + } + + async create(data: AccountModel): Promise { + if (data.username && data.email && data.password) { + const username: string = data.username; + const email: string = data.email; + if (username) { + const accountsName: AccountModel[] = await this.findDataToArray( + config.dbCollections.accounts, + { query: { username: username } } + ); + const name = (accountsName as Array)?.map( + (item) => item.username + ); + if (!name.includes(username)) { + const accountsEmail: AccountModel[] = + await this.findDataToArray( + config.dbCollections.accounts, + { query: { email: email } } + ); + const emailAddress = ( + accountsEmail as Array + )?.map((item) => item.email); + if (!emailAddress.includes(email)) { + const id = GenUUID(); + const roles = [Roles.USER]; + const friends: string[] = []; + const connections: string[] = []; + const whenCreated = new Date(); + const accountIsActive = true; + const accountWaitingVerification = false; + const accounts = await this.CreateData( + config.dbCollections.accounts, + { + ...data, + id: id, + roles: roles, + whenCreated: whenCreated, + friends: friends, + connections: connections, + accountIsActive: accountIsActive, + accountWaitingVerification: + accountWaitingVerification, + } + ); + if (isValidObject(accounts)) { + const emailToValidate = data.email; + const emailRegexp = + /^[a-zA-Z0-9.!#$%&'+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)$/; + if (emailRegexp.test(emailToValidate)) { + try { + const adminAccountName = + config.metaverseServer[ + 'base_admin_account' + ]; + if ( + accounts.username === adminAccountName + ) { + if (IsNullOrEmpty(accounts.roles)) + accounts.roles = []; + SArray.add(accounts.roles, Roles.ADMIN); + } + + const verificationURL = + config.metaverse['metaverseServerUrl'] + + `/api/v1/account/verify/email?a=${accounts.id}&v=${accounts.id}`; + const metaverseName = + config.metaverse['metaverseName']; + const shortMetaverseName = + config.metaverse['metaverseNickName']; + const verificationFile = path.join( + __dirname, + '../..', + config.metaverseServer[ + 'email_verification_email_body' + ] + ); + + let emailBody = await fsPromises.readFile( + verificationFile, + 'utf-8' + ); + emailBody = emailBody + .replace( + 'VERIFICATION_URL', + verificationURL + ) + .replace( + 'METAVERSE_NAME', + metaverseName + ) + .replace( + 'SHORT_METAVERSE_NAME', + shortMetaverseName + ); - async create(data: AccountModel, params?: Params): Promise { - if (data.username && data.email && data.password) { - const username : string = data.username; - const email : string = data.email; - const password : string = data.password; - if (username) { - const accountsName: AccountModel[] = await this.findDataToArray(config.dbCollections.accounts, { query: { username: username } }); - const name = (accountsName as Array) - ?.map((item) => item.username); - if (!name.includes(username)) { - - const accountsEmail: AccountModel[] = await this.findDataToArray(config.dbCollections.accounts, { query:{email: email }}); - const emailAddress = (accountsEmail as Array) - ?.map((item) => item.email); - if (!emailAddress.includes(email)) { - - const id = GenUUID(); - const roles = [Roles.USER]; - const friends : string[] = []; - const connections : string[] = []; - const whenCreated = new Date(); - const accountIsActive = true; - const accountWaitingVerification = false; - const accounts = await this.CreateData(config.dbCollections.accounts, { - ...data, - id: id, - roles: roles, - whenCreated: whenCreated, - friends: friends, - connections: connections, - accountIsActive: accountIsActive, - accountWaitingVerification :accountWaitingVerification - }); - if (isValidObject(accounts)) { - const emailToValidate = data.email; - const emailRegexp = /^[a-zA-Z0-9.!#$%&'+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)$/; - if (emailRegexp.test(emailToValidate)) { - try { - const adminAccountName = config.metaverseServer['base_admin_account']; - if (accounts.username === adminAccountName) { - if (IsNullOrEmpty(accounts.roles)) accounts.roles = []; - SArray.add(accounts.roles, Roles.ADMIN); - } - - const verificationURL = config.metaverse['metaverseServerUrl'] - + `/api/v1/account/verify/email?a=${accounts.id}&v=${accounts.id}`; - const metaverseName = config.metaverse['metaverseName']; - const shortMetaverseName = config.metaverse['metaverseNickName']; - const verificationFile = path.join(__dirname, '../..', config.metaverseServer['email_verification_email_body']); - - let emailBody = await fsPromises.readFile(verificationFile, 'utf-8'); - emailBody = emailBody.replace('VERIFICATION_URL', verificationURL) - .replace('METAVERSE_NAME', metaverseName) - .replace('SHORT_METAVERSE_NAME', shortMetaverseName); - - const email = { - from: 'khilan.odan@gmail.com', - to: accounts.email, - subject: `${shortMetaverseName} account verification`, - html: emailBody, - }; - const mailSendResult = await sendEmail(this.app, email); - - return Promise.resolve({ - accountId: accounts.id, - username: accounts.username, - accountIsActive: accounts.accountIsActive, - accountWaitingVerification:accounts.accountWaitingVerification - }); - } catch (error: any) { - throw new Error('Exception adding user: ' + error); + const email = { + from: 'khilan.odan@gmail.com', + to: accounts.email, + subject: `${shortMetaverseName} account verification`, + html: emailBody, + }; + await sendEmail( + this.app, + email + ); + + return Promise.resolve({ + accountId: accounts.id, + username: accounts.username, + accountIsActive: + accounts.accountIsActive, + accountWaitingVerification: + accounts.accountWaitingVerification, + }); + } catch (error: any) { + throw new Error( + 'Exception adding user: ' + error + ); + } + } else { + throw new Error('Send valid Email address'); + } + } else { + throw new Error('Could not create account'); + } + } else { + throw new Error('Email already exists'); + } + } else { + throw new Error('Account already exists'); } - } - else { - throw new Error('Send valid Email address'); - } } else { - throw new Error('Could not create account'); + throw new Error('Badly formatted username'); } - } else { - throw new Error('Email already exists'); - } } else { - throw new Error('Account already exists'); + throw new Error('Badly formatted request'); } - } else { - throw new Error('Badly formatted username'); - } - } else { - throw new Error('Badly formatted request'); } - } - - async find(params?: Params): Promise { - - const perPage = parseInt(params?.query?.per_page) || 10; - const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; + async find(params?: Params): Promise { + const perPage = parseInt(params?.query?.per_page) || 10; + const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; - const user = await this.findDataToArray(config.dbCollections.accounts, { - query: { - accountIsActive: true , - $select: [ 'username', 'accountId' ], - $skip: skip, - $limit: perPage - } - }); - - return Promise.resolve({ user }); - } + const user = await this.findDataToArray(config.dbCollections.accounts, { + query: { + accountIsActive: true, + $select: ['username', 'accountId'], + $skip: skip, + $limit: perPage, + }, + }); + return Promise.resolve({ user }); + } } diff --git a/Goobieverse/src/services/users/users.hooks.ts b/Goobieverse/src/services/users/users.hooks.ts index 34a59886..36fabba4 100644 --- a/Goobieverse/src/services/users/users.hooks.ts +++ b/Goobieverse/src/services/users/users.hooks.ts @@ -7,36 +7,36 @@ import requestSuccess from '../../hooks/requestSuccess'; import * as authentication from '@feathersjs/authentication'; const { authenticate } = authentication.hooks; -const { hashPassword, protect } = local.hooks; +const { hashPassword } = local.hooks; export default { - before: { - all: [], - find: [authenticate('jwt')], - get: [], - create: [hashPassword('password')], - update: [hashPassword('password')], - patch: [], - remove: [], - }, + before: { + all: [], + find: [authenticate('jwt')], + get: [], + create: [hashPassword('password')], + update: [hashPassword('password')], + patch: [], + remove: [], + }, - after: { - all: [requestSuccess()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, - error: { - all: [requestFail()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, } as HooksObject; diff --git a/Goobieverse/src/services/users/users.service.ts b/Goobieverse/src/services/users/users.service.ts index 820552c4..109d3047 100644 --- a/Goobieverse/src/services/users/users.service.ts +++ b/Goobieverse/src/services/users/users.service.ts @@ -12,16 +12,16 @@ declare module '../../declarations' { } export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - id:'id' - }; + const options = { + paginate: app.get('paginate'), + id:'id' + }; - // Initialize our service with any options it requires - app.use('/users', new Users(options, app)); + // Initialize our service with any options it requires + app.use('/users', new Users(options, app)); - // Get our initialized service so that we can register hooks - const service = app.service('users'); + // Get our initialized service so that we can register hooks + const service = app.service('users'); - service.hooks(hooks); + service.hooks(hooks); } diff --git a/Goobieverse/src/utils/Misc.ts b/Goobieverse/src/utils/Misc.ts index 3edc7fd2..95477dfc 100644 --- a/Goobieverse/src/utils/Misc.ts +++ b/Goobieverse/src/utils/Misc.ts @@ -6,137 +6,137 @@ import { v4 as uuidv4 } from 'uuid'; // Return 'true' if the passed value is null or empty export function IsNullOrEmpty(pVal: any): boolean { - return ( - typeof pVal === 'undefined' || + return ( + typeof pVal === 'undefined' || pVal === null || (typeof pVal === 'string' && String(pVal).length === 0) - ); + ); } export function GenUUID(): string { - return uuidv4(); + return uuidv4(); } // Return 'true' if the passed value is not null or empty export function IsNotNullOrEmpty(pVal: any): boolean { - return !IsNullOrEmpty(pVal); + return !IsNullOrEmpty(pVal); } // Utility routine that reads in JSON content from either an URL or a filename. // Returns the parsed JSON object or 'undefined' if any errors. export async function readInJSON(pFilenameOrURL: string): Promise { - let configBody: string; - if (pFilenameOrURL.startsWith('http://')) { - configBody = await httpRequest(pFilenameOrURL); - } else { - if (pFilenameOrURL.startsWith('https://')) { - configBody = await httpsRequest(pFilenameOrURL); + let configBody: string; + if (pFilenameOrURL.startsWith('http://')) { + configBody = await httpRequest(pFilenameOrURL); } else { - try { - // We should technically sanitize this filename but if one can change the environment - // or config file variables, the app is already poned. - configBody = fs.readFileSync(pFilenameOrURL, 'utf-8'); - } catch (err) { - configBody = ''; - console.debug( - `readInJSON: failed read of user config file ${pFilenameOrURL}: ${err}` - ); - } + if (pFilenameOrURL.startsWith('https://')) { + configBody = await httpsRequest(pFilenameOrURL); + } else { + try { + // We should technically sanitize this filename but if one can change the environment + // or config file variables, the app is already poned. + configBody = fs.readFileSync(pFilenameOrURL, 'utf-8'); + } catch (err) { + configBody = ''; + console.debug( + `readInJSON: failed read of user config file ${pFilenameOrURL}: ${err}` + ); + } + } } - } - if (IsNotNullOrEmpty(configBody)) { - return JSON.parse(configBody); - } - return undefined; + if (IsNotNullOrEmpty(configBody)) { + return JSON.parse(configBody); + } + return undefined; } // Do a simple https GET and return the response as a string export async function httpsRequest(pUrl: string): Promise { - return new Promise((resolve, reject) => { - https - .get(pUrl, (resp: any) => { - let data = ''; - resp.on('data', (chunk: string) => { - data += chunk; - }); - resp.on('end', () => { - resolve(data); - }); - }) - .on('error', (err: any) => { - reject(err); - }); - }); + return new Promise((resolve, reject) => { + https + .get(pUrl, (resp: any) => { + let data = ''; + resp.on('data', (chunk: string) => { + data += chunk; + }); + resp.on('end', () => { + resolve(data); + }); + }) + .on('error', (err: any) => { + reject(err); + }); + }); } // Do a simple http GET and return the response as a string export async function httpRequest(pUrl: string): Promise { - return new Promise((resolve, reject) => { - http - .get(pUrl, (resp: any) => { - let data = ''; - resp.on('data', (chunk: string) => { - data += chunk; - }); - resp.on('end', () => { - resolve(data); - }); - }) - .on('error', (err: any) => { - reject(err); - }); - }); + return new Promise((resolve, reject) => { + http + .get(pUrl, (resp: any) => { + let data = ''; + resp.on('data', (chunk: string) => { + data += chunk; + }); + resp.on('end', () => { + resolve(data); + }); + }) + .on('error', (err: any) => { + reject(err); + }); + }); } let myExternalAddr: string; export async function getMyExternalIPAddress(): Promise { - if (IsNotNullOrEmpty(myExternalAddr)) { - return Promise.resolve(myExternalAddr); - } - return new Promise((resolve, reject) => { - httpsRequest('https://api.ipify.org') - .then((resp) => { - myExternalAddr = resp; - resolve(myExternalAddr); - }) - .catch((err) => { - // Can't get it that way for some reason. Ask our interface - const networkInterfaces = os.networkInterfaces(); - // { 'lo1': [ info, info ], 'eth0': [ info, info ]} where 'info' could be v4 and v6 addr infos + if (IsNotNullOrEmpty(myExternalAddr)) { + return Promise.resolve(myExternalAddr); + } + return new Promise((resolve, reject) => { + httpsRequest('https://api.ipify.org') + .then((resp) => { + myExternalAddr = resp; + resolve(myExternalAddr); + }) + .catch(() => { + // Can't get it that way for some reason. Ask our interface + const networkInterfaces = os.networkInterfaces(); + // { 'lo1': [ info, info ], 'eth0': [ info, info ]} where 'info' could be v4 and v6 addr infos - let addressv4 = ''; - let addressv6 = ''; + let addressv4 = ''; + let addressv6 = ''; - Object.keys(networkInterfaces).forEach((dev) => { - networkInterfaces[dev]?.filter((details) => { - if (details.family === 'IPv4' && details.internal === false) { - addressv4 = details.address; - } - if (details.family === 'IPv6' && details.internal === false) { - addressv6 = details.address; - } - }); - }); - let address = ''; - if (IsNullOrEmpty(addressv4)) { - address = addressv6; - } else { - address = addressv6; - } + Object.keys(networkInterfaces).forEach((dev) => { + networkInterfaces[dev]?.filter((details) => { + if (details.family === 'IPv4' && details.internal === false) { + addressv4 = details.address; + } + if (details.family === 'IPv6' && details.internal === false) { + addressv6 = details.address; + } + }); + }); + let address = ''; + if (IsNullOrEmpty(addressv4)) { + address = addressv6; + } else { + address = addressv6; + } - if (IsNullOrEmpty(address)) { - reject('No address found'); - } - myExternalAddr = address.toString(); - resolve(myExternalAddr); - }); - }); + if (IsNullOrEmpty(address)) { + reject('No address found'); + } + myExternalAddr = address.toString(); + resolve(myExternalAddr); + }); + }); } export const isValidArray = (arr: []) => { - return arr && Array.isArray(arr) && arr.length > 0; + return arr && Array.isArray(arr) && arr.length > 0; }; export const isValidObject = (obj: object) => { - return obj && Object.keys(obj).length > 0; + return obj && Object.keys(obj).length > 0; }; diff --git a/Goobieverse/src/utils/Perm.ts b/Goobieverse/src/utils/Perm.ts index bff36d70..5e31f941 100644 --- a/Goobieverse/src/utils/Perm.ts +++ b/Goobieverse/src/utils/Perm.ts @@ -24,16 +24,16 @@ // 'sponsor': the requesting account is the sponsor of the traget domain // 'domainaccess': the target entity has a domain and requesting account must be sponsor export class Perm { - public static NONE = 'none'; - public static ALL = 'all'; - public static PUBLIC = 'public'; // target account is publicly visible - public static DOMAIN = 'domain'; // check against .sponsorId - public static OWNER = 'owner'; // check against .id or .accountId - public static FRIEND = 'friend'; // check member of .friends - public static CONNECTION = 'connection';// check member of .connections - public static ADMIN = 'admin'; // check if isAdmin - public static SPONSOR = 'sponsor'; // check against .sponsorAccountId - public static MANAGER = 'manager'; // check against .managers - public static DOMAINACCESS = 'domainaccess'; // check that entity's domain has access + public static NONE = 'none'; + public static ALL = 'all'; + public static PUBLIC = 'public'; // target account is publicly visible + public static DOMAIN = 'domain'; // check against .sponsorId + public static OWNER = 'owner'; // check against .id or .accountId + public static FRIEND = 'friend'; // check member of .friends + public static CONNECTION = 'connection';// check member of .connections + public static ADMIN = 'admin'; // check if isAdmin + public static SPONSOR = 'sponsor'; // check against .sponsorAccountId + public static MANAGER = 'manager'; // check against .managers + public static DOMAINACCESS = 'domainaccess'; // check that entity's domain has access } diff --git a/Goobieverse/src/utils/Utils.ts b/Goobieverse/src/utils/Utils.ts index 64b410c4..557deb56 100644 --- a/Goobieverse/src/utils/Utils.ts +++ b/Goobieverse/src/utils/Utils.ts @@ -8,51 +8,51 @@ import config from '../appconfig'; // "stripped" in that the bounding "BEGIN" and "END" lines have been removed. // This routine returns a stripped key string from a properly PEM formatted public key string. export function createSimplifiedPublicKey(pPubKey: string): string { - let keyLines: string[] = []; - if (pPubKey) { - keyLines = pPubKey.split('\n'); - keyLines.shift(); // Remove the "BEGIN" first line - while (keyLines.length > 1 + let keyLines: string[] = []; + if (pPubKey) { + keyLines = pPubKey.split('\n'); + keyLines.shift(); // Remove the "BEGIN" first line + while (keyLines.length > 1 && ( keyLines[keyLines.length-1].length < 1 || keyLines[keyLines.length-1].includes('END PUBLIC KEY') ) ) { - keyLines.pop(); // Remove the "END" last line + keyLines.pop(); // Remove the "END" last line + } } - } - return keyLines.join(''); // Combine all lines into one long string + return keyLines.join(''); // Combine all lines into one long string } // getter property that is 'true' if the user is a grid administrator export function isAdmin(pAcct: AccountModel): boolean { - return SArray.has(pAcct.roles, Roles.ADMIN); + return SArray.has(pAcct.roles, Roles.ADMIN); } // Any logic to test of account is active // Currently checks if account email is verified or is legacy // account (no 'accountEmailVerified' variable) export function isEnabled(pAcct: AccountModel): boolean { - return pAcct.accountEmailVerified ?? true; + return pAcct.accountEmailVerified ?? true; } export function isOnline(pAcct: AccountModel): boolean { - if (pAcct && pAcct.timeOfLastHeartbeat) { - return ( - Date.now().valueOf() - pAcct.timeOfLastHeartbeat.valueOf() < + if (pAcct && pAcct.timeOfLastHeartbeat) { + return ( + Date.now().valueOf() - pAcct.timeOfLastHeartbeat.valueOf() < config.metaverseServer.heartbeat_seconds_until_offline * 1000 - ); - } - return false; + ); + } + return false; } export function couldBeDomainId(pId: string): boolean { - return pId.length === 36; + return pId.length === 36; } // Return the ISODate when an account is considered offline export function dateWhenNotOnline(): Date { - const whenOffline = new Date(Date.now() - config.metaverseServer.heartbeat_seconds_until_offline); - return whenOffline; + const whenOffline = new Date(Date.now() - config.metaverseServer.heartbeat_seconds_until_offline); + return whenOffline; } export function validateEmail(email:string):boolean{ - const emailRegexp = /^[a-zA-Z0-9.!#$%&'+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)$/; - return emailRegexp.test(email); + const emailRegexp = /^[a-zA-Z0-9.!#$%&'+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)$/; + return emailRegexp.test(email); } \ No newline at end of file diff --git a/Goobieverse/src/utils/mail.ts b/Goobieverse/src/utils/mail.ts index 8c2cbda9..c68f20df 100644 --- a/Goobieverse/src/utils/mail.ts +++ b/Goobieverse/src/utils/mail.ts @@ -2,18 +2,18 @@ import { Application } from '../declarations'; import { BadRequest } from '@feathersjs/errors'; export async function sendEmail(app: Application, email: any): Promise { - if (email.to) { - email.html = email.html.replace(/&/g, '&'); - try { - const abc = await app - .service('email') - .create(email) - .then(function (result) { - return result; - }); - return abc; - } catch (error: any) { - return Promise.reject(new BadRequest(error)); + if (email.to) { + email.html = email.html.replace(/&/g, '&'); + try { + const abc = await app + .service('email') + .create(email) + .then(function (result) { + return result; + }); + return abc; + } catch (error: any) { + return Promise.reject(new BadRequest(error)); + } } - } } \ No newline at end of file diff --git a/Goobieverse/src/utils/messages.ts b/Goobieverse/src/utils/messages.ts index 17504be5..5782c003 100644 --- a/Goobieverse/src/utils/messages.ts +++ b/Goobieverse/src/utils/messages.ts @@ -1,14 +1,15 @@ export const messages = { - common_messages_error: 'Something went wrong please try again later.', - common_messages_record_available: 'Record is available.', - common_messages_record_not_available: 'Record is not available.', - common_messages_records_available: 'Records are available.', - common_messages_records_not_available: 'Records are not available.', - common_messages_record_added_failed:'Failed to add record!', - common_messages_target_profile_notfound:'Target profile not found', - common_messages_target_account_notfound:'Target account not found', - common_messages_user_email_link_error:'email already exists for another account', - common_messages_unauthorized:'Unauthorized', - common_messages_email_validation_error:'Invalid email address' + common_messages_db_error: 'Database loading fail.', + common_messages_error: 'Something went wrong please try again later.', + common_messages_record_available: 'Record is available.', + common_messages_record_not_available: 'Record is not available.', + common_messages_records_available: 'Records are available.', + common_messages_records_not_available: 'Records are not available.', + common_messages_record_added_failed:'Failed to add record!', + common_messages_target_profile_notfound:'Target profile not found', + common_messages_target_account_notfound:'Target account not found', + common_messages_user_email_link_error:'email already exists for another account', + common_messages_unauthorized:'Unauthorized', + common_messages_email_validation_error:'Invalid email address' }; diff --git a/Goobieverse/src/utils/response.ts b/Goobieverse/src/utils/response.ts index 3a45a86c..d8bd30fb 100644 --- a/Goobieverse/src/utils/response.ts +++ b/Goobieverse/src/utils/response.ts @@ -1,10 +1,10 @@ export const Response = { - success : (data: any,additionalFields?:any) => { - return {status: 'success',data: data,...additionalFields}; - }, - error:(message: string,additionalFields?:any) => { - return { status: 'failure', message: message,...additionalFields}; - } + success : (data: any,additionalFields?:any) => { + return {status: 'success',data: data,...additionalFields}; + }, + error:(message: string,additionalFields?:any) => { + return { status: 'failure', message: message,...additionalFields}; + } }; export enum HTTPStatusCode { diff --git a/Goobieverse/src/utils/sets/Availability.ts b/Goobieverse/src/utils/sets/Availability.ts index 94ede550..26d71346 100644 --- a/Goobieverse/src/utils/sets/Availability.ts +++ b/Goobieverse/src/utils/sets/Availability.ts @@ -15,14 +15,14 @@ 'use strict'; export class Availability { - public static NONE = 'none'; // no one can see me - public static FRIENDS = 'friends'; // available to friends - public static CONNECTIONS= 'connections'; // available to connections - public static ALL = 'all'; // available to all + public static NONE = 'none'; // no one can see me + public static FRIENDS = 'friends'; // available to friends + public static CONNECTIONS= 'connections'; // available to connections + public static ALL = 'all'; // available to all - // See if the passed availability code is a known availability token - static async KnownAvailability(pAvailability: string): Promise { - return [ Availability.NONE,Availability.FRIENDS,Availability.CONNECTIONS,Availability.ALL].includes(pAvailability); - } + // See if the passed availability code is a known availability token + static async KnownAvailability(pAvailability: string): Promise { + return [ Availability.NONE,Availability.FRIENDS,Availability.CONNECTIONS,Availability.ALL].includes(pAvailability); + } } diff --git a/Goobieverse/src/utils/sets/Maturity.ts b/Goobieverse/src/utils/sets/Maturity.ts index 8668f40e..aac1b9fc 100644 --- a/Goobieverse/src/utils/sets/Maturity.ts +++ b/Goobieverse/src/utils/sets/Maturity.ts @@ -15,21 +15,21 @@ 'use strict'; export class Maturity { - public static UNRATED = 'unrated'; - public static EVERYONE = 'everyone'; - public static TEEN = 'teen'; - public static MATURE = 'mature'; - public static ADULT = 'adult'; + public static UNRATED = 'unrated'; + public static EVERYONE = 'everyone'; + public static TEEN = 'teen'; + public static MATURE = 'mature'; + public static ADULT = 'adult'; - static MaturityCategories = [ Maturity.UNRATED, - Maturity.EVERYONE, - Maturity.TEEN, - Maturity.MATURE, - Maturity.ADULT - ]; + static MaturityCategories = [ Maturity.UNRATED, + Maturity.EVERYONE, + Maturity.TEEN, + Maturity.MATURE, + Maturity.ADULT + ]; - static KnownMaturity(pMaturity: string): boolean { - return this.MaturityCategories.includes(pMaturity); - } + static KnownMaturity(pMaturity: string): boolean { + return this.MaturityCategories.includes(pMaturity); + } } diff --git a/Goobieverse/src/utils/sets/Roles.ts b/Goobieverse/src/utils/sets/Roles.ts index fb662496..24a1bd55 100644 --- a/Goobieverse/src/utils/sets/Roles.ts +++ b/Goobieverse/src/utils/sets/Roles.ts @@ -16,12 +16,12 @@ // Class to manage the manipulations on roles that accounts can have export class Roles { - // at the moment, the only role is 'admin' - public static ADMIN = 'admin'; // someone who has metaverse-server admin - public static USER = 'user'; // a 'user' or 'person' + // at the moment, the only role is 'admin' + public static ADMIN = 'admin'; // someone who has metaverse-server admin + public static USER = 'user'; // a 'user' or 'person' - // See if the passed role code is a known role token - static async KnownRole(pScope: string): Promise { - return [ Roles.ADMIN, Roles.USER ].includes(pScope); - } + // See if the passed role code is a known role token + static async KnownRole(pScope: string): Promise { + return [ Roles.ADMIN, Roles.USER ].includes(pScope); + } } diff --git a/Goobieverse/src/utils/sets/Visibility.ts b/Goobieverse/src/utils/sets/Visibility.ts index 9e8debea..cd7d60d4 100644 --- a/Goobieverse/src/utils/sets/Visibility.ts +++ b/Goobieverse/src/utils/sets/Visibility.ts @@ -15,22 +15,22 @@ 'use strict'; export class Visibility { - public static OPEN = 'open'; - public static FRIENDS = 'friends'; - public static CONNECTIONS = 'connections'; - public static GROUP = 'group'; - public static PRIVATE = 'private'; + public static OPEN = 'open'; + public static FRIENDS = 'friends'; + public static CONNECTIONS = 'connections'; + public static GROUP = 'group'; + public static PRIVATE = 'private'; - static VisibilityCategories = [ - Visibility.OPEN, - Visibility.FRIENDS, - Visibility.CONNECTIONS, - Visibility.GROUP, - Visibility.PRIVATE - ]; + static VisibilityCategories = [ + Visibility.OPEN, + Visibility.FRIENDS, + Visibility.CONNECTIONS, + Visibility.GROUP, + Visibility.PRIVATE + ]; - static KnownVisibility(pVisibility: string): boolean { - return this.VisibilityCategories.includes(pVisibility); - } + static KnownVisibility(pVisibility: string): boolean { + return this.VisibilityCategories.includes(pVisibility); + } } diff --git a/Goobieverse/src/utils/vTypes.ts b/Goobieverse/src/utils/vTypes.ts index 5e51a7d3..1615f543 100755 --- a/Goobieverse/src/utils/vTypes.ts +++ b/Goobieverse/src/utils/vTypes.ts @@ -28,37 +28,37 @@ export interface VKeyValue { // String array. // Several structures are an array of strings (TokenScope, AccountRoles, ...). export class SArray { - static has(pArray: string[], pCheck: string): boolean { - return IsNullOrEmpty(pArray) ? false : pArray.includes(pCheck); - } + static has(pArray: string[], pCheck: string): boolean { + return IsNullOrEmpty(pArray) ? false : pArray.includes(pCheck); + } - static hasNoCase(pArray: string[], pCheck: string): boolean { - const pCheckLower = pCheck.toLowerCase(); - if (IsNotNullOrEmpty(pArray)) { - for (const ent of pArray) { - if (ent.toLowerCase() === pCheckLower) { - return true; + static hasNoCase(pArray: string[], pCheck: string): boolean { + const pCheckLower = pCheck.toLowerCase(); + if (IsNotNullOrEmpty(pArray)) { + for (const ent of pArray) { + if (ent.toLowerCase() === pCheckLower) { + return true; + } + } } - } + return false; } - return false; - } - static add(pArray: string[], pAdd: string): boolean { - let added = false; - if (typeof(pAdd) === 'string') { - if (! pArray.includes(pAdd)) { - pArray.push(pAdd); - added = true; - } + static add(pArray: string[], pAdd: string): boolean { + let added = false; + if (typeof(pAdd) === 'string') { + if (! pArray.includes(pAdd)) { + pArray.push(pAdd); + added = true; + } + } + return added; } - return added; - } - static remove(pArray: string[], pRemove: string): void { - const idx = pArray.indexOf(pRemove); - if (idx >= 0) { - pArray.splice(idx, 1); + static remove(pArray: string[], pRemove: string): void { + const idx = pArray.indexOf(pRemove); + if (idx >= 0) { + pArray.splice(idx, 1); + } } - } } \ No newline at end of file diff --git a/Goobieverse/test/app.test.ts b/Goobieverse/test/app.test.ts index 4f7d365e..cf6216ef 100644 --- a/Goobieverse/test/app.test.ts +++ b/Goobieverse/test/app.test.ts @@ -1,4 +1,3 @@ -import assert from 'assert'; import { Server } from 'http'; import url from 'url'; import axios from 'axios'; @@ -7,63 +6,63 @@ import app from '../src/app'; const port = app.get('port') || 8998; const getUrl = (pathname?: string): string => url.format({ - hostname: app.get('host') || 'localhost', - protocol: 'http', - port, - pathname + hostname: app.get('host') || 'localhost', + protocol: 'http', + port, + pathname }); describe('Feathers application tests (with jest)', () => { - let server: Server; + let server: Server; - beforeAll(done => { - server = app.listen(port); - server.once('listening', () => done()); - }); + beforeAll(done => { + server = app.listen(port); + server.once('listening', () => done()); + }); - afterAll(done => { - server.close(done); - }); + afterAll(done => { + server.close(done); + }); - it('starts and shows the index page', async () => { - expect.assertions(1); + it('starts and shows the index page', async () => { + expect.assertions(1); - const { data } = await axios.get(getUrl()); + const { data } = await axios.get(getUrl()); - expect(data.indexOf('')).not.toBe(-1); - }); + expect(data.indexOf('')).not.toBe(-1); + }); - describe('404', () => { - it('shows a 404 HTML page', async () => { - expect.assertions(2); + describe('404', () => { + it('shows a 404 HTML page', async () => { + expect.assertions(2); - try { - await axios.get(getUrl('path/to/nowhere'), { - headers: { - 'Accept': 'text/html' - } - }); - } catch (error: any) { - const { response } = error; + try { + await axios.get(getUrl('path/to/nowhere'), { + headers: { + 'Accept': 'text/html' + } + }); + } catch (error: any) { + const { response } = error; - expect(response.status).toBe(404); - expect(response.data.indexOf('')).not.toBe(-1); - } - }); + expect(response.status).toBe(404); + expect(response.data.indexOf('')).not.toBe(-1); + } + }); - it('shows a 404 JSON error without stack trace', async () => { - expect.assertions(4); + it('shows a 404 JSON error without stack trace', async () => { + expect.assertions(4); - try { - await axios.get(getUrl('path/to/nowhere')); - } catch (error: any) { - const { response } = error; + try { + await axios.get(getUrl('path/to/nowhere')); + } catch (error: any) { + const { response } = error; - expect(response.status).toBe(404); - expect(response.data.code).toBe(404); - expect(response.data.message).toBe('Page not found'); - expect(response.data.name).toBe('NotFound'); - } + expect(response.status).toBe(404); + expect(response.data.code).toBe(404); + expect(response.data.message).toBe('Page not found'); + expect(response.data.name).toBe('NotFound'); + } + }); }); - }); }); diff --git a/Goobieverse/test/authentication.test.ts b/Goobieverse/test/authentication.test.ts index c92c9bb9..e4be0f99 100644 --- a/Goobieverse/test/authentication.test.ts +++ b/Goobieverse/test/authentication.test.ts @@ -1,32 +1,32 @@ import app from '../src/app'; describe('authentication', () => { - it('registered the authentication service', () => { - expect(app.service('authentication')).toBeTruthy(); - }); + it('registered the authentication service', () => { + expect(app.service('authentication')).toBeTruthy(); + }); - describe('local strategy', () => { - const userInfo = { - email: 'someone@example.com', - password: 'supersecret' - }; + describe('local strategy', () => { + const userInfo = { + email: 'someone@example.com', + password: 'supersecret' + }; - beforeAll(async () => { - try { - await app.service('users').create(userInfo); - } catch (error) { - // Do nothing, it just means the user already exists and can be tested - } - }); + beforeAll(async () => { + try { + await app.service('users').create(userInfo); + } catch (error) { + // Do nothing, it just means the user already exists and can be tested + } + }); - it('authenticates user and creates accessToken', async () => { - const { user, accessToken } = await app.service('authentication').create({ - strategy: 'local', - ...userInfo - }, {}); + it('authenticates user and creates accessToken', async () => { + const { user, accessToken } = await app.service('authentication').create({ + strategy: 'local', + ...userInfo + }, {}); - expect(accessToken).toBeTruthy(); - expect(user).toBeTruthy(); + expect(accessToken).toBeTruthy(); + expect(user).toBeTruthy(); + }); }); - }); }); diff --git a/Goobieverse/test/services/accounts.test.ts b/Goobieverse/test/services/accounts.test.ts index b9223f3f..52e4e2f4 100644 --- a/Goobieverse/test/services/accounts.test.ts +++ b/Goobieverse/test/services/accounts.test.ts @@ -1,8 +1,8 @@ import app from '../../src/app'; describe('\'accounts\' service', () => { - it('registered the service', () => { - const service = app.service('accounts'); - expect(service).toBeTruthy(); - }); + it('registered the service', () => { + const service = app.service('accounts'); + expect(service).toBeTruthy(); + }); }); diff --git a/Goobieverse/test/services/connections.test.ts b/Goobieverse/test/services/connections.test.ts index 1d840d40..bfef87ad 100644 --- a/Goobieverse/test/services/connections.test.ts +++ b/Goobieverse/test/services/connections.test.ts @@ -1,8 +1,8 @@ import app from '../../src/app'; describe('\'connections\' service', () => { - it('registered the service', () => { - const service = app.service('connections'); - expect(service).toBeTruthy(); - }); + it('registered the service', () => { + const service = app.service('connections'); + expect(service).toBeTruthy(); + }); }); diff --git a/Goobieverse/test/services/email.test.ts b/Goobieverse/test/services/email.test.ts index ecaedcc4..61421438 100644 --- a/Goobieverse/test/services/email.test.ts +++ b/Goobieverse/test/services/email.test.ts @@ -1,8 +1,8 @@ import app from '../../src/app'; describe('\'email\' service', () => { - it('registered the service', () => { - const service = app.service('email'); - expect(service).toBeTruthy(); - }); + it('registered the service', () => { + const service = app.service('email'); + expect(service).toBeTruthy(); + }); }); diff --git a/Goobieverse/test/services/friends.test.ts b/Goobieverse/test/services/friends.test.ts index fd36e7bd..55c8b625 100644 --- a/Goobieverse/test/services/friends.test.ts +++ b/Goobieverse/test/services/friends.test.ts @@ -1,8 +1,8 @@ import app from '../../src/app'; describe('\'friends\' service', () => { - it('registered the service', () => { - const service = app.service('friends'); - expect(service).toBeTruthy(); - }); + it('registered the service', () => { + const service = app.service('friends'); + expect(service).toBeTruthy(); + }); }); diff --git a/Goobieverse/test/services/metaverse_info.test.ts b/Goobieverse/test/services/metaverse_info.test.ts index a4d9320f..f0682157 100644 --- a/Goobieverse/test/services/metaverse_info.test.ts +++ b/Goobieverse/test/services/metaverse_info.test.ts @@ -1,8 +1,8 @@ import app from '../../src/app'; describe('\'metaverse_info\' service', () => { - it('registered the service', () => { - const service = app.service('metaverse_info'); - expect(service).toBeTruthy(); - }); + it('registered the service', () => { + const service = app.service('metaverse_info'); + expect(service).toBeTruthy(); + }); }); diff --git a/Goobieverse/test/services/profiles.test.ts b/Goobieverse/test/services/profiles.test.ts index 9525af9f..634f87e8 100644 --- a/Goobieverse/test/services/profiles.test.ts +++ b/Goobieverse/test/services/profiles.test.ts @@ -1,8 +1,8 @@ import app from '../../src/app'; describe('\'profiles\' service', () => { - it('registered the service', () => { - const service = app.service('profiles'); - expect(service).toBeTruthy(); - }); + it('registered the service', () => { + const service = app.service('profiles'); + expect(service).toBeTruthy(); + }); }); diff --git a/Goobieverse/test/services/users.test.ts b/Goobieverse/test/services/users.test.ts index 20d1ebb3..ff54c796 100644 --- a/Goobieverse/test/services/users.test.ts +++ b/Goobieverse/test/services/users.test.ts @@ -1,8 +1,8 @@ import app from '../../src/app'; describe('\'users\' service', () => { - it('registered the service', () => { - const service = app.service('users'); - expect(service).toBeTruthy(); - }); + it('registered the service', () => { + const service = app.service('users'); + expect(service).toBeTruthy(); + }); }); From fbbba5cd336169275278c9e30771c7c2a998d366 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Wed, 11 May 2022 20:07:53 +0530 Subject: [PATCH 017/128] metavers_v2 added --- Goobieverse/.editorconfig | 13 - Goobieverse/.env.local.default | 31 - Goobieverse/.eslintrc.json | 38 - Goobieverse/.gitignore | 115 - Goobieverse/README.md | 45 - Goobieverse/build_scripts/createVersion.js | 32 - Goobieverse/jest.config.js | 9 - Goobieverse/package.json | 94 - Goobieverse/public/favicon.ico | Bin 5533 -> 0 bytes Goobieverse/public/index.html | 75 - Goobieverse/src/app.hooks.ts | 34 - Goobieverse/src/app.ts | 73 - Goobieverse/src/appconfig.ts | 155 -- Goobieverse/src/authentication.ts | 21 - Goobieverse/src/channels.ts | 65 - .../src/controllers/PublicRoutesController.ts | 31 - Goobieverse/src/dbservice/DatabaseService.ts | 109 - .../src/dbservice/DatabaseServiceOptions.ts | 3 - Goobieverse/src/declarations.d.ts | 6 - Goobieverse/src/hooks/checkAccess.ts | 189 -- Goobieverse/src/hooks/isHasAuthToken.ts | 7 - Goobieverse/src/hooks/requestFail.ts | 9 - Goobieverse/src/hooks/requestSuccess.ts | 9 - Goobieverse/src/index.ts | 13 - Goobieverse/src/interfaces/AccountModel.ts | 42 - Goobieverse/src/interfaces/DomainModel.ts | 38 - Goobieverse/src/interfaces/MetaverseInfo.ts | 7 - Goobieverse/src/interfaces/PlaceModel.ts | 28 - Goobieverse/src/interfaces/RequestModal.ts | 22 - Goobieverse/src/logger.ts | 16 - Goobieverse/src/middleware/index.ts | 5 - Goobieverse/src/mongodb.ts | 20 - .../src/responsebuilder/accountsBuilder.ts | 79 - .../src/responsebuilder/domainsBuilder.ts | 80 - .../src/responsebuilder/placesBuilder.ts | 130 - .../src/responsebuilder/tokensBuilder.ts | 0 Goobieverse/src/routes/publicRoutes.ts | 9 - .../src/services/accounts/accounts.class.ts | 173 -- .../src/services/accounts/accounts.hooks.ts | 42 - .../src/services/accounts/accounts.service.ts | 28 - Goobieverse/src/services/auth/auth.class.ts | 13 - Goobieverse/src/services/auth/auth.hooks.ts | 38 - Goobieverse/src/services/auth/auth.service.ts | 26 - .../services/connections/connections.class.ts | 32 - .../services/connections/connections.hooks.ts | 38 - .../connections/connections.service.ts | 27 - Goobieverse/src/services/email/email.class.ts | 9 - Goobieverse/src/services/email/email.hooks.ts | 33 - .../src/services/email/email.service.ts | 25 - .../src/services/friends/friends.class.ts | 53 - .../src/services/friends/friends.hooks.ts | 38 - .../src/services/friends/friends.service.ts | 27 - Goobieverse/src/services/index.ts | 20 - .../src/services/profiles/profiles.class.ts | 86 - .../src/services/profiles/profiles.hooks.ts | 43 - .../src/services/profiles/profiles.service.ts | 27 - Goobieverse/src/services/users/users.class.ts | 176 -- Goobieverse/src/services/users/users.hooks.ts | 42 - .../src/services/users/users.service.ts | 27 - Goobieverse/src/utils/Misc.ts | 142 -- Goobieverse/src/utils/Perm.ts | 39 - Goobieverse/src/utils/Utils.ts | 58 - Goobieverse/src/utils/mail.ts | 19 - Goobieverse/src/utils/messages.ts | 15 - Goobieverse/src/utils/response.ts | 17 - Goobieverse/src/utils/sets/Availability.ts | 28 - Goobieverse/src/utils/sets/Maturity.ts | 35 - Goobieverse/src/utils/sets/Roles.ts | 27 - Goobieverse/src/utils/sets/Visibility.ts | 36 - Goobieverse/src/utils/vTypes.ts | 64 - Goobieverse/test/app.test.ts | 68 - Goobieverse/test/authentication.test.ts | 32 - Goobieverse/test/services/accounts.test.ts | 8 - Goobieverse/test/services/connections.test.ts | 8 - Goobieverse/test/services/email.test.ts | 8 - Goobieverse/test/services/friends.test.ts | 8 - .../test/services/metaverse_info.test.ts | 8 - Goobieverse/test/services/profiles.test.ts | 8 - Goobieverse/test/services/users.test.ts | 8 - Goobieverse/tsconfig.json | 13 - Goobieverse/types.d.ts | 5 - Goobieverse/verificationEmail.html | 18 - Metaverse_v2 | 1 + package-lock.json | 2167 ++++++++++++++++- 84 files changed, 2159 insertions(+), 3356 deletions(-) delete mode 100644 Goobieverse/.editorconfig delete mode 100644 Goobieverse/.env.local.default delete mode 100644 Goobieverse/.eslintrc.json delete mode 100644 Goobieverse/.gitignore delete mode 100644 Goobieverse/README.md delete mode 100644 Goobieverse/build_scripts/createVersion.js delete mode 100644 Goobieverse/jest.config.js delete mode 100644 Goobieverse/package.json delete mode 100644 Goobieverse/public/favicon.ico delete mode 100644 Goobieverse/public/index.html delete mode 100644 Goobieverse/src/app.hooks.ts delete mode 100644 Goobieverse/src/app.ts delete mode 100644 Goobieverse/src/appconfig.ts delete mode 100644 Goobieverse/src/authentication.ts delete mode 100644 Goobieverse/src/channels.ts delete mode 100644 Goobieverse/src/controllers/PublicRoutesController.ts delete mode 100644 Goobieverse/src/dbservice/DatabaseService.ts delete mode 100644 Goobieverse/src/dbservice/DatabaseServiceOptions.ts delete mode 100644 Goobieverse/src/declarations.d.ts delete mode 100644 Goobieverse/src/hooks/checkAccess.ts delete mode 100644 Goobieverse/src/hooks/isHasAuthToken.ts delete mode 100644 Goobieverse/src/hooks/requestFail.ts delete mode 100644 Goobieverse/src/hooks/requestSuccess.ts delete mode 100644 Goobieverse/src/index.ts delete mode 100755 Goobieverse/src/interfaces/AccountModel.ts delete mode 100755 Goobieverse/src/interfaces/DomainModel.ts delete mode 100644 Goobieverse/src/interfaces/MetaverseInfo.ts delete mode 100755 Goobieverse/src/interfaces/PlaceModel.ts delete mode 100644 Goobieverse/src/interfaces/RequestModal.ts delete mode 100644 Goobieverse/src/logger.ts delete mode 100644 Goobieverse/src/middleware/index.ts delete mode 100644 Goobieverse/src/mongodb.ts delete mode 100644 Goobieverse/src/responsebuilder/accountsBuilder.ts delete mode 100644 Goobieverse/src/responsebuilder/domainsBuilder.ts delete mode 100644 Goobieverse/src/responsebuilder/placesBuilder.ts delete mode 100644 Goobieverse/src/responsebuilder/tokensBuilder.ts delete mode 100644 Goobieverse/src/routes/publicRoutes.ts delete mode 100644 Goobieverse/src/services/accounts/accounts.class.ts delete mode 100644 Goobieverse/src/services/accounts/accounts.hooks.ts delete mode 100644 Goobieverse/src/services/accounts/accounts.service.ts delete mode 100644 Goobieverse/src/services/auth/auth.class.ts delete mode 100644 Goobieverse/src/services/auth/auth.hooks.ts delete mode 100644 Goobieverse/src/services/auth/auth.service.ts delete mode 100644 Goobieverse/src/services/connections/connections.class.ts delete mode 100644 Goobieverse/src/services/connections/connections.hooks.ts delete mode 100644 Goobieverse/src/services/connections/connections.service.ts delete mode 100644 Goobieverse/src/services/email/email.class.ts delete mode 100644 Goobieverse/src/services/email/email.hooks.ts delete mode 100644 Goobieverse/src/services/email/email.service.ts delete mode 100644 Goobieverse/src/services/friends/friends.class.ts delete mode 100644 Goobieverse/src/services/friends/friends.hooks.ts delete mode 100644 Goobieverse/src/services/friends/friends.service.ts delete mode 100644 Goobieverse/src/services/index.ts delete mode 100644 Goobieverse/src/services/profiles/profiles.class.ts delete mode 100644 Goobieverse/src/services/profiles/profiles.hooks.ts delete mode 100644 Goobieverse/src/services/profiles/profiles.service.ts delete mode 100644 Goobieverse/src/services/users/users.class.ts delete mode 100644 Goobieverse/src/services/users/users.hooks.ts delete mode 100644 Goobieverse/src/services/users/users.service.ts delete mode 100644 Goobieverse/src/utils/Misc.ts delete mode 100644 Goobieverse/src/utils/Perm.ts delete mode 100644 Goobieverse/src/utils/Utils.ts delete mode 100644 Goobieverse/src/utils/mail.ts delete mode 100644 Goobieverse/src/utils/messages.ts delete mode 100644 Goobieverse/src/utils/response.ts delete mode 100644 Goobieverse/src/utils/sets/Availability.ts delete mode 100644 Goobieverse/src/utils/sets/Maturity.ts delete mode 100644 Goobieverse/src/utils/sets/Roles.ts delete mode 100644 Goobieverse/src/utils/sets/Visibility.ts delete mode 100755 Goobieverse/src/utils/vTypes.ts delete mode 100644 Goobieverse/test/app.test.ts delete mode 100644 Goobieverse/test/authentication.test.ts delete mode 100644 Goobieverse/test/services/accounts.test.ts delete mode 100644 Goobieverse/test/services/connections.test.ts delete mode 100644 Goobieverse/test/services/email.test.ts delete mode 100644 Goobieverse/test/services/friends.test.ts delete mode 100644 Goobieverse/test/services/metaverse_info.test.ts delete mode 100644 Goobieverse/test/services/profiles.test.ts delete mode 100644 Goobieverse/test/services/users.test.ts delete mode 100644 Goobieverse/tsconfig.json delete mode 100644 Goobieverse/types.d.ts delete mode 100644 Goobieverse/verificationEmail.html create mode 160000 Metaverse_v2 diff --git a/Goobieverse/.editorconfig b/Goobieverse/.editorconfig deleted file mode 100644 index 24c41426..00000000 --- a/Goobieverse/.editorconfig +++ /dev/null @@ -1,13 +0,0 @@ -# http://editorconfig.org -root = true - -[*] -charset = utf-8 -indent_style = space -indent_size = 4 -trim_trailing_whitespace = true -end_of_line = crlf -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/Goobieverse/.env.local.default b/Goobieverse/.env.local.default deleted file mode 100644 index 0acb07ea..00000000 --- a/Goobieverse/.env.local.default +++ /dev/null @@ -1,31 +0,0 @@ - -# DB variables ------------------- -DB_HOST=localhost -DB_PORT=27017 -DB_NAME=tester -DB_USER=metaverse -DB_PASSWORD=nooneknowsit -DB_AUTHDB=admin -DATABASE_URL= - -# Authentication variables ------------------- -AUTH_SECRET=M7iszvSxttilkptn22GT4/NbFGY= - -# Server variables --------------- -SERVER_HOST=localhost -SERVER_PORT=3030 -SERVER_VERSION=1.1.1-20200101-abcdefg - -# General ------------------------ -LOCAL=true -APP_ENV=development -PUBLIC_PATH=./public - -# Metaverse ------------------------ -METAVERSE_NAME=Vircadia noobie -METAVERSE_NICK_NAME=Noobie -METAVERSE_SERVER_URL= -DEFAULT_ICE_SERVER_URL= -DASHBOARD_URL=https://dashboard.vircadia.com -LISTEN_HOST=0.0.0.0 -LISTEN_PORT=9400 \ No newline at end of file diff --git a/Goobieverse/.eslintrc.json b/Goobieverse/.eslintrc.json deleted file mode 100644 index cc295f5a..00000000 --- a/Goobieverse/.eslintrc.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "env": { - "es6": true, - "node": true, - "jest": true - }, - "parserOptions": { - "parser": "@typescript-eslint/parser", - "ecmaVersion": 2018, - "sourceType": "module" - }, - "plugins": [ - "@typescript-eslint" - ], - "extends": [ - "plugin:@typescript-eslint/recommended" - ], - "rules": { - "indent": [ - "error", - 4 - ], - "linebreak-style": [ - "error", - "unix" - ], - "quotes": [ - "error", - "single" - ], - "semi": [ - "error", - "always" - ], - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-empty-interface": "off" - } -} diff --git a/Goobieverse/.gitignore b/Goobieverse/.gitignore deleted file mode 100644 index 25d7c18a..00000000 --- a/Goobieverse/.gitignore +++ /dev/null @@ -1,115 +0,0 @@ -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directory -# Commenting this out is preferred by some people, see -# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- -node_modules - -# Users Environment Variables -.lock-wscript - -# IDEs and editors (shamelessly copied from @angular/cli's .gitignore) -/.idea -.project -.classpath -.c9/ -*.launch -.settings/ -*.sublime-workspace - -# IDE - VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json - -### Linux ### -*~ - -# temporary files which can be created if a process still has a handle open of a deleted file -.fuse_hidden* - -# KDE directory preferences -.directory - -# Linux trash folder which might appear on any partition or disk -.Trash-* - -# .nfs files are created when an open file is removed but is still being accessed -.nfs* - -### OSX ### -*.DS_Store -.AppleDouble -.LSOverride - -# Icon must end with two \r -Icon - - -# Thumbnails -._* - -# Files that might appear in the root of a volume -.DocumentRevisions-V100 -.fseventsd -.Spotlight-V100 -.TemporaryItems -.Trashes -.VolumeIcon.icns -.com.apple.timemachine.donotpresent - -# Directories potentially created on remote AFP share -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk - -### Windows ### -# Windows thumbnail cache files -Thumbs.db -ehthumbs.db -ehthumbs_vista.db - -# Folder config file -Desktop.ini - -# Recycle Bin used on file shares -$RECYCLE.BIN/ - -# Windows Installer files -*.cab -*.msi -*.msm -*.msp - -# Windows shortcuts -*.lnk - -# Others -lib/ -data/ - -.env.local -package-lock.json \ No newline at end of file diff --git a/Goobieverse/README.md b/Goobieverse/README.md deleted file mode 100644 index 6a184037..00000000 --- a/Goobieverse/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Goobieverse - -> - -## About - -This project uses [Feathers](http://feathersjs.com). An open source web framework for building modern real-time applications. - -## Getting Started - -Getting up and running is as easy as 1, 2, 3. - -1. Make sure you have [NodeJS](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed. -2. Install your dependencies - - ``` - cd path/to/Goobieverse - npm install - ``` - -3. Start your app - - ``` - npm start - ``` - -## Testing - -Simply run `npm test` and all your tests in the `test/` directory will be run. - -## Scaffolding - -Feathers has a powerful command line interface. Here are a few things it can do: - -``` -$ npm install -g @feathersjs/cli # Install Feathers CLI - -$ feathers generate service # Generate a new Service -$ feathers generate hook # Generate a new Hook -$ feathers help # Show all commands -``` - -## Help - -For more information on all the things you can do with Feathers visit [docs.feathersjs.com](http://docs.feathersjs.com). diff --git a/Goobieverse/build_scripts/createVersion.js b/Goobieverse/build_scripts/createVersion.js deleted file mode 100644 index 7943714a..00000000 --- a/Goobieverse/build_scripts/createVersion.js +++ /dev/null @@ -1,32 +0,0 @@ - -const fse = require('fs-extra'); - -var gitVer = require('child_process').execSync('git rev-parse --short HEAD').toString().trim(); -var gitVerFull = require('child_process').execSync('git rev-parse HEAD').toString().trim(); -var packageVersion = process.env.npm_package_version; -const filePath = './lib/metaverse_info.json'; - -console.log('Found package version', packageVersion); -console.log('Found Git commit short hash', gitVer); -console.log('Found Git commit long hash', gitVerFull); - -function yyyymmdd() { - var x = new Date(); - var y = x.getFullYear().toString(); - var m = (x.getMonth() + 1).toString(); - var d = x.getDate().toString(); - (d.length == 1) && (d = '0' + d); - (m.length == 1) && (m = '0' + m); - var yyyymmdd = y + m + d; - return yyyymmdd; -} - -var jsonToWrite = { - npm_package_version: packageVersion, - git_commit: gitVerFull, - version_tag: (packageVersion + '-' + yyyymmdd() + '-' + gitVer) -}; - -jsonToWrite = JSON.stringify(jsonToWrite); - -var attemptFileWrite = fse.outputFileSync(filePath, jsonToWrite); diff --git a/Goobieverse/jest.config.js b/Goobieverse/jest.config.js deleted file mode 100644 index 57070898..00000000 --- a/Goobieverse/jest.config.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - globals: { - 'ts-jest': { - diagnostics: false - } - } -}; diff --git a/Goobieverse/package.json b/Goobieverse/package.json deleted file mode 100644 index 033869c3..00000000 --- a/Goobieverse/package.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "name": "goobie-verse", - "description": "Goobieverse", - "version": "0.0.1", - "homepage": "", - "private": true, - "main": "src", - "keywords": [ - "feathers" - ], - "author": { - "name": "", - "email": "" - }, - "contributors": [], - "bugs": {}, - "directories": { - "lib": "src", - "test": "test/" - }, - "engines": { - "node": "^16.0.0", - "npm": ">= 3.0.0" - }, - "scripts": { - "test": "npm run lint && npm run compile && npm run jest", - "lint": "eslint src/. test/. --config .eslintrc.json --ext .ts --fix", - "dev": "ts-eager src/index.ts cross-env APP_ENV=development", - "start": "cross-env APP_ENV=prod npm run compile && node lib/", - "jest": "jest --forceExit", - "compile": "shx rm -rf lib/ && tsc", - "create-version": "node build_scripts/createVersion.js" - }, - "standard": { - "env": [ - "jest" - ], - "ignore": [] - }, - "types": "lib/", - "dependencies": { - "@feathersjs/authentication": "^4.5.11", - "@feathersjs/authentication-local": "^4.5.11", - "@feathersjs/authentication-oauth": "^4.5.11", - "@feathersjs/configuration": "^4.5.11", - "@feathersjs/errors": "^4.5.11", - "@feathersjs/express": "^4.5.11", - "@feathersjs/feathers": "^4.5.11", - "@feathersjs/socketio": "^4.5.11", - "@feathersjs/transport-commons": "^4.5.11", - "@types/dotenv-flow": "^3.2.0", - "app-root-path": "^3.0.0", - "bcrypt": "^5.0.1", - "compression": "^1.7.4", - "cors": "^2.8.5", - "cross-env": "^7.0.3", - "dotenv-flow": "^3.2.0", - "feathers-hooks-common": "^5.0.6", - "feathers-mailer": "^3.1.0", - "feathers-mongodb": "^6.4.1", - "helmet": "^4.6.0", - "mongodb": "^4.2.2", - "mongodb-core": "^3.2.7", - "nodemailer-smtp-transport": "^2.7.4", - "serve-favicon": "^2.5.0", - "trim": "^1.0.1", - "ts-eager": "^2.0.2", - "uuidv4": "^6.2.12", - "winston": "^3.3.3" - }, - "devDependencies": { - "@types/app-root-path": "^1.2.4", - "@types/bcrypt": "^5.0.0", - "@types/compression": "^1.7.2", - "@types/cors": "^2.8.12", - "@types/dotenv-flow": "^3.2.0", - "@types/jest": "^27.0.3", - "@types/jsonwebtoken": "^8.5.6", - "@types/mongodb": "^4.0.7", - "@types/morgan": "^1.9.3", - "@types/nodemailer-smtp-transport": "^2.7.5", - "@types/serve-favicon": "^2.5.3", - "@types/trim": "^0.1.1", - "@typescript-eslint/eslint-plugin": "^5.8.0", - "@typescript-eslint/parser": "^5.8.0", - "axios": "^0.24.0", - "eslint": "^8.6.0", - "jest": "^27.4.5", - "shx": "^0.3.3", - "ts-jest": "^27.0.7", - "ts-node-dev": "^1.1.8", - "typescript": "^4.5.4" - } -} diff --git a/Goobieverse/public/favicon.ico b/Goobieverse/public/favicon.ico deleted file mode 100644 index 7ed25a60b04996fb9f339aa73cbfd25dd394430e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5533 zcmV;O6=Ld%P)Px~S4l)cRCodHoe7jx#hJ(Hg+-u2K|lyPOhlU~Ad91h!~|sw6K7&#jHqLXlMs&@ zjTsn|=r~43qfwJlaf?TC3_?Hq;WiQWT+*&1{MNE%%XS?p)vT&iphJfaJ%xrn7ktC81Mt`5cFPwp0y#w9 z4{;jFy9)a{{`rd+FJ7N-S#hMV7=ftwQ{k^*C4_UZ$6?E9Rw0;qF!=qB9XsZ}^UgcX z+0(Mfgbz~>=yyuHdo-KOow2$QQlKbe^Zw%S@K#b%g(o~L9foKv(r^!H3($_IC_O(`Au_k^YzP@ zFaIDUu4qy@1X9CPa4U^|GnGo4pP{Y9-pFW}1M)*fqc39tef#!3s#mXGSHj{}Y})>Z zI%rK9M~8Z*)DTK45CqX1fHSZoEl?`33wt|L*S)OK`)ypA{Z(N4GCVFvBb&H%>CzSX z@=L8V_mJieEAD8QByGGzR5cHHP)6jg_Il2Qxbuo&R&ineo5h0$KMOs@6ia4 z1(}vPT#GR7g7}$ad}8^*-=l6R5)m`28Jq)!JB;DOw;1V>}4{ zgUZ{ow22#23V`|4F4bVO$fIe>nLT)pQh?3g05*HtSQcfd^BAf-nL@glg0?eKE?|9p z#nAHh+8&g5X7xmX-D`z`Af91fJw0w=2z-|=U8dkzVeBTn1M3+!g`J(h7?-*|MgC#b zb)_k%D{JI$X%n5t6>JI?2y4cjaQ$I`^P_;hm_<&<9kCjE?NxM)O9M=o6Yk|`^xzK8 zU#yFA^6u{4yU#`_3r$%ne>QESbGd>{K?0!{XtQ^d0mhNF+>K)O-|TUa+QrvvCf)R}JZ|1E9S!x3_Ea~6mS zhQX5*>Z`T@m`o43B^O5JNORh0r)iIoHg%@>JOMVlxO-$eQ@I4vMw`Q3yLNpAMvGDy z+caz+R|FM)j*+1?BPmnOS()}YX{X2FYQ}B?>(N1AjF7UP z);2aaHlbQ;L0|27gJU$)>WwbknwyhAm9p++4=7i5MO>TOLRRcxkyPELPX5F;@REiEP@ z*S~kbD&u~I&hjewhDX6*;q^C(QE5s=r^7w~o@Y&YC(xEsK(d;w0%4eao7`ub5`HH} zCLFt$L%tzY^qXOf5yqp?|1m6%Jk6y%l?~$&q_LiigYRZX2j6&3CYTsR4r!7&mXqat`ur?Qts#EJY-mbkJ-F{4bS@CAm!NM@d| znsAyoR3JR5IgW~Jx^iaq#*G_wc9p21!Eeg~U^=5DWACAngPp|awpwDAVoDyR(}C|` z{g*Se?Y~8F2^9#7=D(1^KZ;-~n7D4;x;;^a#6f57)dg6T>bMAuy@+>v54{w50orZQ zqyThhL47$6oMAi|B@N*Dm5Ce5Xg}YCeQ&MrOL-U_nx?Kc^wnrpx|U7UFgkSa5@n_m z|1}fY1wnjKRy17Q7_K$(v3uE+{)COJUTAuQ0K7K?BI5;1#t30_e3wj}9bzhSH-3M( zIeMHfd!duT5!$1mYf5+)%wK8+)5=!j%Nm_pe`h-;;2puJ|C|duHz*LDx%et2bzRRx zQSz-ImNbD#VgUaumk5FUBCc12 zj%SEl1CC2W=#Dg-cyDTQgfN#he+r|=N#pEq88}{bJn>(|5l}}LmYXIJgrV~(-w>YW z2NLqoNu=rqU$XdU_U+sEYvOYe$U;9;f*!C=b?otXMkr}2urClj3?qTRFp}_fB>Ila zoRkZuG{6BI4Xk<6s4@;21L;0MF0QLjoO zkK2`xdVkFN(GXEaB4qGvMhMFdbAK>+0@08mfv@K?T~&lY(6BB8Th!_3OLQcCsoTj^ zEW=B@j!X__L?le0qu4eclo}L=)m}CzLo7Z7qNyNh1FY%?0sbyFL)+u9@KuC$Mr$C+ z`+RF@5{mzWd)I({q znFFNx&uj^jilfu7u)L|KCj(C)RL~a)6=<{xlk(GJ&J%AnI#vs}J&0SR+dt^pRAQ|k z3s5r?xd&;j-8kENk2LmWr7w_bDt59dpdthUCSMiAe{Blio|L>y4Q2oE>gU#W5gAt_NdB*jDOHJBCd<&TP zUdrJKb8TC@L7|1SyG=}@bvB%YpXW0D0O(%h!PD>AY|B5YfOR~@jb7Gjz} z5YWz~@v=idB@2dSskop(^ixc8v9Iy`X8sW{I;3>yr=+~<;8rs;bgYF~=ZK1-cP2hU zz@!ads@>Ba;+Chbng3cbgmtX0mx9f>=iy$-h1O{NA7CrmfHkeHQHAR?Lel1HdRO8j z1Wb|l9|&MYyLRn7?F#=c(rTo5>k0mf7C;ajv(YXFfKTQsp|ez8`zW{#m5n;g^D~y) zLqoTZ;sIW5sJtl+zm82ZG_6#cs3Izf?tj`LP?AM8l}{TsNG)x|B!oWlAvfP(tY~G? zU&o@qJ|WzBu|)uB>o>QbP13T`1VUxg66M9AM2P{qGNP#W^$iAb*|abnCLR$cC=e#6 zowR@YB94F(7YMC69eW_ys*L8pq-ZAw<6M|DFF1dtyI}R(8hKUi&B?-15%zL|L6f~Ha@*>DnmfKFkgs$tXK zQi1J#&afnmnh0Dk>drbMuHwjnwq7fM)tZxu@E1WE!$0{=s0Pvlb?p8mxNfo7psz(x z{j%Vik|nO2qrAvEb72$1H^6?q}9QJ0YxPN%JAkmga=T zRU%E_-+)WjGfoC>S7HUCDa+AQ$>Vc$&tXP|KENLjcc-sQAUd7SFyy%#qK>qW2*a)l*gljd~@<#}I;kS?d#s5GL6UcIYJo7P|r_%kL*9C7oR|&H> z5~%EJwgNQ3IW`=(pL>j-f$43_7fc@#J_!92qT?RIS&f~|b?!wK@CI41ayGy}*Cock zjp=r|!k$0~EQaXjY0>?}3dC)gwssV&$kL5I!TO<@*oxSxyCH6*3lDLZD zP3IbI^K-)Px4kbAcI+?NL{$9iHk`0u>ft6XaHI-LC>s;TQxtnTl;5`0Wj&qFe-h*i z1Y#qRe>R-P=r;^aI1w-VlFkT0IP_y-JyIYxCjzDfO!+)dkNd33JDsq%PO5lcAPOLm zsd8ng{4x4h5{E;{{pAGI+dP473q9-D6q1jULsT4RefVihpq;1LaGF4fn2GftZ_xnX zw271OgTWIBz_(&OKMVbqB(N0AvtPe{9jL>lrko`xE)6D}K0@>=o1W;Omf}C3d3&JE z5Vs#?qnmi*eheP(bd>dOTiiHG!LM7lZsRD&>r^U_`uH=FD?d0wlxUKRM4DA?+qV5z z7f!+jf)mJgMvJ#&*YQdcSc>6^)~at^eqhSbkDX01Vb7Z(5Jrh^+}GLwhrT;0AjR-ygpVcv38svP!ydI~2t*mE_#GyrHi$QyaI9W>m7At?fzuIt z#0exdI&Lxzw};Vjp9%ZkP=V+e8=)-qQ}Hi~7$s+!aGc(w#SHCa7fvSxT7 zjE)K5wzJG+wi)i^A+79CfuvI0;!;wW%p-)8I8M=*!3WSrr2T1x?8P22M*jefw)5K( z;7!=OqACz6kklxdV={6oKlD2$9E+Ecs)gK*ilgu!Q37EfH3@-e?eh%yZ>4i>aN(dE zDG()Os9lfa8bi-aZ|7jFpW=?mR$KrFD zD^GH?GP#;_y%+iLYmcqiH3VT?1ipuDI}pAqpDX5^1tKQ=`_bzW%Bzkc%yfIFUsUMr zXk~EqZ&@`}$+X8`kpiJ(j-)-NWBoP->pkkohoR^8*Uy|wat%9FVXYkvEiVBDi~%P zSg1zh-C!8?1=s?H?g&FG+KT|zsSpIN+d>^-SIk`q8^3G8Hxo?W8Yi5DHKmWiX5DWn zZI~%YASy@;6(^RT0l?d?V1OON(0v)8S2|j;F6FbMSNCBK!t_FmJ@}|kJYs$v}JVOr@480&P0$8Yw zl=(>jH+Td~4j0=nX1@NK-B0#$KWj8+(@9z{g<}w}> zzexp#8(Q98M&gH5b}53;RdQPdv?lLDN|b@R=wHk(qPU^9NHF~bVT^<3P84i(UjC9C zr*Gc8dEbT&8)o$B(?^=n*?{&oav;&aycpNJckdp={gE^UsjTGJ;wT-z7avAW5Wl=? zS!81UvNpfk`Xv0nPf=Er(hpi*PN#gRNEUwfqDP2d(&0Oc9|2H{y?w~0cdXYzYov#L z-K5XiOa8~P>U}ffd_t<9&SmPoopn)a{&^NK_52fS=uk$+GnQVg-}U5T%Sp@6+Ho9O z(G(g{!U>wi4DmbYRjNq7YN0yN!C--<4)fBc6GFHOMdAj^4sLqYF(&aT;7#RtY*7}0 zxCWArPEWcI!FAQ6!v5%EKjkt*aYH$(i{6Bm>*EtZ_yA|V{u75mw3H0 zSwNcq!fhbjNDp0&KQGl%3o}2XeiG%>lXn>QT&z^_NDUKS`Zr1U^6$AHN&RvJ$>TAx z3&d3`weXLnp~gZR%od6vDyD$JGukN*#rp#`)+ fS9<5lsoMVwztWhI*cqZ+00000NkvXXu0mjf9$Six diff --git a/Goobieverse/public/index.html b/Goobieverse/public/index.html deleted file mode 100644 index c5879ef8..00000000 --- a/Goobieverse/public/index.html +++ /dev/null @@ -1,75 +0,0 @@ - - - - Goobieverse - - - - - -
- - - -
- - diff --git a/Goobieverse/src/app.hooks.ts b/Goobieverse/src/app.hooks.ts deleted file mode 100644 index 8ac90480..00000000 --- a/Goobieverse/src/app.hooks.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Application hooks that run for every service -// Don't remove this comment. It's needed to format import lines nicely. - -export default { - before: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, - - after: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, - - error: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } -}; diff --git a/Goobieverse/src/app.ts b/Goobieverse/src/app.ts deleted file mode 100644 index abacf34d..00000000 --- a/Goobieverse/src/app.ts +++ /dev/null @@ -1,73 +0,0 @@ -import path from 'path'; -import favicon from 'serve-favicon'; -import compress from 'compression'; -import helmet from 'helmet'; -import cors from 'cors'; - -import feathers from '@feathersjs/feathers'; -import express from '@feathersjs/express'; -import socketio from '@feathersjs/socketio'; -import { publicRoutes } from './routes/publicRoutes'; -import config from './appconfig'; -import { Application } from './declarations'; -import logger from './logger'; -import middleware from './middleware'; -import services from './services'; -import appHooks from './app.hooks'; -import channels from './channels'; -import { HookContext as FeathersHookContext } from '@feathersjs/feathers'; -import authentication from './authentication'; -import mongodb from './mongodb'; -// Don't remove this comment. It's needed to format import lines nicely. - -const app: Application = express(feathers()); -export type HookContext = { app: Application } & FeathersHookContext; - -// Enable security, CORS, compression, favicon and body parsing -app.use(helmet({ - contentSecurityPolicy: false -})); -app.use(cors()); -app.use(compress()); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); - -//set Public folder path -app.set('public',config.server.publicPath); - -//page favicon -app.use(favicon(path.join(app.settings.public, 'favicon.ico'))); - - -// routes -app.use('/', publicRoutes); -// Host the public folder -app.use('/', express.static(app.settings.public)); - - -// Set up Plugins and providers -app.configure(express.rest()); -app.configure(socketio()); - -app.set('host', config.server.local ? config.server.hostName + ':' + config.server.port : config.server.hostName); -app.set('port',config.server.port); -app.set('paginate',config.server.paginate); -app.set('authentication', config.authentication); - -app.configure(mongodb); - -// Configure other middleware (see `middleware/index.ts`) -app.configure(middleware); -app.configure(authentication); -// Set up our services (see `services/index.ts`) -app.configure(services); -// Set up event channels (see channels.ts) -app.configure(channels); - -// Configure a middleware for 404s and the error handler -app.use(express.notFound()); -app.use(express.errorHandler({ logger } as any)); - -app.hooks(appHooks); - -export default app; diff --git a/Goobieverse/src/appconfig.ts b/Goobieverse/src/appconfig.ts deleted file mode 100644 index edf68708..00000000 --- a/Goobieverse/src/appconfig.ts +++ /dev/null @@ -1,155 +0,0 @@ -import dotenv from 'dotenv-flow'; -import appRootPath from 'app-root-path'; -import { IsNullOrEmpty, getMyExternalIPAddress } from './utils/Misc'; -import fs from 'fs'; - -if (globalThis.process?.env.APP_ENV === 'development') { - // const fs = require('fs'); - if ( - !fs.existsSync(appRootPath.path + '/.env') && - !fs.existsSync(appRootPath.path + '/.env.local') - ) { - const fromEnvPath = appRootPath.path + '/.env.local.default'; - const toEnvPath = appRootPath.path + '/.env.local'; - fs.copyFileSync(fromEnvPath, toEnvPath, fs.constants.COPYFILE_EXCL); - } -} - -dotenv.config({ - path: appRootPath.path, - silent: true, -}); - -/** - * Server - */ - -const server = { - local: process.env.LOCAL === 'true', - hostName: process.env.SERVER_HOST, - port: process.env.PORT ?? 3030, - paginate: { - default: 10, - max: 100, - }, - publicPath: process.env.PUBLIC_PATH, - version: process.env.SERVER_VERSION ?? '', -}; - -const email = { - host: process.env.SMTP_HOST ?? 'smtp.gmail.com', - port: process.env.SMTP_PORT ?? '465', - secure: process.env.SMTP_SECURE ?? true, - auth: { - user: process.env.SMTP_USER ?? 'khilan.odan@gmail.com', - pass: process.env.SMTP_PASS ?? 'blackhawk143', - } -}; - -/** - * Metaverse Server - */ - -const metaverseServer = { - listen_host: process.env.LISTEN_HOST ?? '0.0.0.0', - listen_port: process.env.LISTEN_PORT ?? 9400, - metaverseInfoAdditionFile: process.env.METAVERSE_INFO_File ?? '', - session_timeout_minutes: 5, - heartbeat_seconds_until_offline: 5 * 60, // seconds until non-heartbeating user is offline - domain_seconds_until_offline: 10 * 60, // seconds until non-heartbeating domain is offline - domain_seconds_check_if_online: 2 * 60, // how often to check if a domain is online - handshake_request_expiration_minutes: 1, // minutes that a handshake friend request is active - connection_request_expiration_minutes: 60 * 24 * 4, // 4 days - friend_request_expiration_minutes: 60 * 24 * 4, // 4 days - base_admin_account:process.env.ADMIN_ACCOUNT ?? 'Goobieverse', - place_current_timeout_minutes: 5, // minutes until current place info is stale - place_inactive_timeout_minutes: 60, // minutes until place is considered inactive - place_check_last_activity_seconds: (3 * 60) - 5, // seconds between checks for Place lastActivity updates - email_verification_timeout_minutes: process.env.EMAIL_VERIFICATION_TIME, - enable_account_email_verification: process.env.ENABLE_ACCOUNT_VERIFICATION ?? 'true', - email_verification_email_body: '../verificationEmail.html', -}; - -/** - * Authentication - */ -const authentication = { - entity: 'user', - service: 'auth', - secret: process.env.AUTH_SECRET ?? 'testing', - authStrategies: ['jwt', 'local'], - jwtOptions: { - expiresIn: '60 days', - }, - local: { - usernameField: 'username', - passwordField: 'password', - }, - bearerToken: { - numBytes: 16, - }, - oauth: { - redirect: '/', - auth0: { - key: '', - secret: '', - subdomain: '', - scope: ['profile', 'openid', 'email'], - }, - }, -}; - -/** - * Metaverse - */ - -const metaverse = { - metaverseName: process.env.METAVERSE_NAME ?? '', - metaverseNickName: process.env.METAVERSE_NICK_NAME ?? '', - metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? '', // if empty, set to self - defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? '', // if empty, set to self - dashboardUrl: process.env.DASHBOARD_URL - -}; - -if ( - IsNullOrEmpty(metaverse.metaverseServerUrl) || - IsNullOrEmpty(metaverse.defaultIceServerUrl) -) { - getMyExternalIPAddress().then((ipAddress) => { - if (IsNullOrEmpty(metaverse.metaverseServerUrl)) { - const newUrl = `http://${ipAddress}:${metaverseServer.listen_port.toString()}/`; - metaverse.metaverseServerUrl = newUrl; - } - if (IsNullOrEmpty(metaverse.defaultIceServerUrl)) { - metaverse.defaultIceServerUrl = ipAddress; - } - }); -} - - -const dbCollections = { - domains : 'domains', - accounts : 'accounts', - places : 'places', - tokens : 'tokens', - requests:'requests', - email:'email' -}; - - -/** - * Full config - */ - -const config = { - deployStage: process.env.DEPLOY_STAGE, - authentication, - server, - metaverse, - metaverseServer, - dbCollections, - email -}; - -export default config; diff --git a/Goobieverse/src/authentication.ts b/Goobieverse/src/authentication.ts deleted file mode 100644 index 409fa474..00000000 --- a/Goobieverse/src/authentication.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { ServiceAddons } from '@feathersjs/feathers'; -import { AuthenticationService, JWTStrategy } from '@feathersjs/authentication'; -import { LocalStrategy } from '@feathersjs/authentication-local'; -import { expressOauth } from '@feathersjs/authentication-oauth'; - -import { Application } from './declarations'; - -declare module './declarations' { - interface ServiceTypes { - 'authentication': AuthenticationService & ServiceAddons; - } -} -export default function(app: Application): void { - const authentication = new AuthenticationService(app); - - authentication.register('jwt', new JWTStrategy()); - authentication.register('local', new LocalStrategy()); - - app.use('/authentication', authentication); - app.configure(expressOauth()); -} diff --git a/Goobieverse/src/channels.ts b/Goobieverse/src/channels.ts deleted file mode 100644 index 44c54c0f..00000000 --- a/Goobieverse/src/channels.ts +++ /dev/null @@ -1,65 +0,0 @@ -import '@feathersjs/transport-commons'; -import { HookContext } from '@feathersjs/feathers'; -import { Application } from './declarations'; - -export default function(app: Application): void { - if(typeof app.channel !== 'function') { - // If no real-time functionality has been configured just return - return; - } - - app.on('connection', (connection: any): void => { - // On a new real-time connection, add it to the anonymous channel - app.channel('anonymous').join(connection); - }); - - app.on('login', (authResult: any, { connection }: any): void => { - // connection can be undefined if there is no - // real-time connection, e.g. when logging in via REST - if(connection) { - // Obtain the logged in user from the connection - // const user = connection.user; - - // The connection is no longer anonymous, remove it - app.channel('anonymous').leave(connection); - - // Add it to the authenticated user channel - app.channel('authenticated').join(connection); - - // Channels can be named anything and joined on any condition - - // E.g. to send real-time events only to admins use - // if(user.isAdmin) { app.channel('admins').join(connection); } - - // If the user has joined e.g. chat rooms - // if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(connection)); - - // Easily organize users by email and userid for things like messaging - // app.channel(`emails/${user.email}`).join(connection); - // app.channel(`userIds/${user.id}`).join(connection); - } - }); - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - app.publish((data: any, hook: HookContext) => { - // Here you can add event publishers to channels set up in `channels.ts` - // To publish only for a specific event use `app.publish(eventname, () => {})` - - console.log('Publishing all events to all authenticated users. See `channels.ts` and https://docs.feathersjs.com/api/channels.html for more information.'); // eslint-disable-line - - // e.g. to publish all service events to all authenticated users use - return app.channel('authenticated'); - }); - - // Here you can also add service specific event publishers - // e.g. the publish the `users` service `created` event to the `admins` channel - // app.service('users').publish('created', () => app.channel('admins')); - - // With the userid and email organization from above you can easily select involved users - // app.service('messages').publish(() => { - // return [ - // app.channel(`userIds/${data.createdBy}`), - // app.channel(`emails/${data.recipientEmail}`) - // ]; - // }); -} diff --git a/Goobieverse/src/controllers/PublicRoutesController.ts b/Goobieverse/src/controllers/PublicRoutesController.ts deleted file mode 100644 index eb575f96..00000000 --- a/Goobieverse/src/controllers/PublicRoutesController.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { MetaverseInfoModel } from './../interfaces/MetaverseInfo'; -import config from '../appconfig'; -import {IsNotNullOrEmpty, readInJSON } from '../utils/Misc'; - -export const PublicRoutesController = ()=>{ - const metaverseInfo = async(req:any,res:any) => { - const response:MetaverseInfoModel = { - metaverse_name:config.metaverse.metaverseName, - metaverse_nick_name: config.metaverse.metaverseNickName, - ice_server_url: config.metaverse.defaultIceServerUrl , - metaverse_url: config.metaverse.metaverseServerUrl, - metaverse_server_version:{ - version_tag:config.server.version - } - }; - try { - const additionUrl: string = config.metaverseServer.metaverseInfoAdditionFile; - if (IsNotNullOrEmpty(additionUrl)) { - const additional = await readInJSON(additionUrl); - if (IsNotNullOrEmpty(additional)) { - response.metaverse_server_version = additional; - } - } - } - catch (err) { - //console.error(`procMetaverseInfo: exception reading additional info file: ${err}`); - } - res.status(200).json(response); - }; - return {metaverseInfo}; -}; \ No newline at end of file diff --git a/Goobieverse/src/dbservice/DatabaseService.ts b/Goobieverse/src/dbservice/DatabaseService.ts deleted file mode 100644 index 3d75c93f..00000000 --- a/Goobieverse/src/dbservice/DatabaseService.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Service, } from 'feathers-mongodb'; -import { Application } from '../declarations'; -import { HookContext, Paginated, Id, } from '@feathersjs/feathers'; -import { DatabaseServiceOptions } from './DatabaseServiceOptions'; -import { Db, Collection, Document, Filter } from 'mongodb'; -import { IsNotNullOrEmpty, IsNullOrEmpty } from '../utils/Misc'; -import { VKeyedCollection } from '../utils/vTypes'; -import { messages } from '../utils/messages'; - -export class DatabaseService extends Service { - app?: Application; - db?: Db; - context?: HookContext; - constructor( - options: Partial, - app?: Application, - context?: HookContext - ) { - super(options); - this.app = app; - this.context = context; - this.loadDatabase(); - } - - async loadDatabase() { - if (IsNotNullOrEmpty(this.app) && this.app) { - this.db = await this.app.get('mongoClient'); - } else if (IsNotNullOrEmpty(this.context) && this.context) { - this.db = await this.context.app.get('mongoClient'); - } - } - - async getDatabase(): Promise { - if (IsNullOrEmpty(this.db)) { - await this.loadDatabase(); - } - if (this.db) { - return this.db; - } - throw new Error(messages.common_messages_db_error); - } - - async getService(tableName: string): Promise> { - this.Model = await (await this.getDatabase()).collection(tableName); - return this.Model; - } - - async getData(tableName: string, id: Id): Promise { - await this.getService(tableName); - return super.get(id); - } - - async findData( - tableName: string, - filter?: Filter - ): Promise | any[]> { - await this.getService(tableName); - if (filter) { - return await super.find(filter); - } else { - return await super.find(); - } - } - - async findDataToArray( - tableName: string, - filter?: Filter - ): Promise { - await this.getService(tableName); - const data = await this.findData(tableName, filter); - if (data instanceof Array) { - return data; - } else { - return data.data; - } - } - - async patchData( - tableName: string, - id: Id, - data: VKeyedCollection - ): Promise { - console.log(tableName + ' ' + id); - await this.getService(tableName); - return await super.patch(id, data); - } - - async deleteData( - tableName: string, - id: Id, - filter?: Filter - ): Promise { - await this.getService(tableName); - return await super.remove(id, filter); - } - - async deleteMultipleData( - tableName: string, - filter: Filter - ): Promise { - await this.getService(tableName); - return await super.remove(null, filter); - } - - async CreateData(tableName: string, data: any): Promise { - await this.getService(tableName); - return await super.create(data); - } -} diff --git a/Goobieverse/src/dbservice/DatabaseServiceOptions.ts b/Goobieverse/src/dbservice/DatabaseServiceOptions.ts deleted file mode 100644 index d8c00fa4..00000000 --- a/Goobieverse/src/dbservice/DatabaseServiceOptions.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { MongoDBServiceOptions } from 'feathers-mongodb'; - -export interface DatabaseServiceOptions extends MongoDBServiceOptions {} diff --git a/Goobieverse/src/declarations.d.ts b/Goobieverse/src/declarations.d.ts deleted file mode 100644 index 56550834..00000000 --- a/Goobieverse/src/declarations.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Application as ExpressFeathers } from '@feathersjs/express'; - -// A mapping of service names to types. Will be extended in service files. -export interface ServiceTypes {} -// The application instance type that will be used everywhere else -export type Application = ExpressFeathers; diff --git a/Goobieverse/src/hooks/checkAccess.ts b/Goobieverse/src/hooks/checkAccess.ts deleted file mode 100644 index 2fa0c5df..00000000 --- a/Goobieverse/src/hooks/checkAccess.ts +++ /dev/null @@ -1,189 +0,0 @@ -import { AccountModel } from '../interfaces/AccountModel'; -import { HookContext } from '@feathersjs/feathers'; -import { IsNotNullOrEmpty } from '../utils/Misc'; -import { Perm } from '../utils/Perm'; -import { isAdmin } from '../utils/Utils'; -import config from '../appconfig'; -import { HTTPStatusCode } from '../utils/response'; -import { Availability } from '../utils/sets/Availability'; -import { SArray } from '../utils/vTypes'; -import { DatabaseService } from '../dbservice/DatabaseService'; -import { messages } from '../utils/messages'; - -export default (collection: string, pRequiredAccess: Perm[]) => { - return async (context: HookContext): Promise => { - const dbService = new DatabaseService({}, undefined, context); - const loginUser = context.params.user; - - const entryDataArray = await dbService.findDataToArray(collection, { - query: { id: context.id }, - }); - let canAccess = false; - - let pTargetEntity: any; - if (IsNotNullOrEmpty(entryDataArray)) { - pTargetEntity = entryDataArray[0]; - } - - if (IsNotNullOrEmpty(pTargetEntity) && pTargetEntity) { - for (const perm of pRequiredAccess) { - switch (perm) { - case Perm.ALL: - canAccess = true; - break; - case Perm.PUBLIC: - // The target entity is publicly visible - // Mostly AccountEntities that must have an 'availability' field - if (pTargetEntity?.hasOwnProperty('availability')) { - if ( - pTargetEntity.availability.includes( - Availability.ALL - ) - ) { - canAccess = true; - } - } - break; - case Perm.DOMAIN: - // requestor is a domain and it's account is the domain's sponsoring account - if (loginUser) { - if ( - pTargetEntity.hasOwnProperty('sponsorAccountId') - ) { - canAccess = - loginUser.id === - (pTargetEntity as any).sponsorAccountId; - } else { - // Super special case where domain doesn't have a sponsor but has an api_key. - // In this case, the API_KEY is put in the accountId field of the DOMAIN scoped AuthToken - if (pTargetEntity.hasOwnProperty('apiKey')) { - canAccess = - loginUser.id === - (pTargetEntity as any).apiKey; - } - } - } - break; - case Perm.OWNER: - // The requestor wants to be the same account as the target entity - if (loginUser && pTargetEntity.hasOwnProperty('id')) { - canAccess = - loginUser.id === (pTargetEntity as any).id; - } - if ( - loginUser && - !canAccess && - pTargetEntity.hasOwnProperty('accountId') - ) { - canAccess = - loginUser.id === - (pTargetEntity as any).accountId; - } - break; - case Perm.FRIEND: - // The requestor is a 'friend' of the target entity - if ( - loginUser && - pTargetEntity.hasOwnProperty('friends') - ) { - const targetFriends: string[] = ( - pTargetEntity as AccountModel - ).friends; - if (targetFriends) { - canAccess = SArray.hasNoCase( - targetFriends, - loginUser.username - ); - } - } - break; - case Perm.CONNECTION: - // The requestor is a 'connection' of the target entity - if ( - loginUser && - pTargetEntity.hasOwnProperty('connections') - ) { - const targetConnections: string[] = ( - pTargetEntity as AccountModel - ).connections; - if (targetConnections) { - canAccess = SArray.hasNoCase( - targetConnections, - loginUser.username - ); - } - } - break; - case Perm.ADMIN: - // If the authToken is an account, has access if admin - if (loginUser) { - canAccess = isAdmin(loginUser as AccountModel); - } - break; - case Perm.SPONSOR: - // Requestor is a regular account and is the sponsor of the domain - if (loginUser) { - if ( - pTargetEntity.hasOwnProperty('sponsorAccountId') - ) { - canAccess = - loginUser.id === - (pTargetEntity as any).sponsorAccountId; - } - } - break; - case Perm.MANAGER: - // See if requesting account is in the list of managers of this entity - if (loginUser) { - if (pTargetEntity.hasOwnProperty('managers')) { - const managers: string[] = ( - pTargetEntity as any - ).managers; - if ( - managers && - managers.includes( - loginUser.username.toLowerCase() - ) - ) { - canAccess = true; - } - } - } - break; - case Perm.DOMAINACCESS: - // Target entity has a domain reference and verify the requestor is able to reference that domain - if ( - loginUser && - pTargetEntity.hasOwnProperty('domainId') - ) { - const aDomains = await dbService.findDataToArray( - config.dbCollections.domains, - { - query: { - id: (pTargetEntity as any).domainId, - }, - } - ); - if (IsNotNullOrEmpty(aDomains)) { - canAccess = - aDomains[0].sponsorAccountId === - loginUser.id; - } - } - break; - } - if (canAccess) break; - } - } else { - context.statusCode = HTTPStatusCode.NotFound; - throw new Error(messages.common_messages_target_account_notfound); - } - - if (!canAccess) { - context.statusCode = HTTPStatusCode.Unauthorized; - throw new Error(messages.common_messages_unauthorized); - } - - return context; - }; -}; diff --git a/Goobieverse/src/hooks/isHasAuthToken.ts b/Goobieverse/src/hooks/isHasAuthToken.ts deleted file mode 100644 index 661f5746..00000000 --- a/Goobieverse/src/hooks/isHasAuthToken.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { HookContext } from '@feathersjs/feathers'; - -export default (): any => { - return (context:HookContext): boolean => { - return !!(context.params.headers?.authorization&& context.params.headers.authorization !== 'null'&& context.params.headers.authorization !== ''); - }; -}; \ No newline at end of file diff --git a/Goobieverse/src/hooks/requestFail.ts b/Goobieverse/src/hooks/requestFail.ts deleted file mode 100644 index 2cea5410..00000000 --- a/Goobieverse/src/hooks/requestFail.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { HookContext } from '@feathersjs/feathers'; -import { Response } from '../utils/response'; - -export default () => { - return async (context: HookContext): Promise => { - context.result = Response.error(context?.error?.message); - return context; - }; -}; diff --git a/Goobieverse/src/hooks/requestSuccess.ts b/Goobieverse/src/hooks/requestSuccess.ts deleted file mode 100644 index 01fc2854..00000000 --- a/Goobieverse/src/hooks/requestSuccess.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { HookContext } from '@feathersjs/feathers'; -import { Response } from '../utils/response'; - -export default () => { - return async (context: HookContext): Promise => { - context.result = Response.success(context.result); - return context; - }; -}; \ No newline at end of file diff --git a/Goobieverse/src/index.ts b/Goobieverse/src/index.ts deleted file mode 100644 index ae1c9afc..00000000 --- a/Goobieverse/src/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import logger from './logger'; -import app from './app'; - -const port = app.get('port'); -const server = app.listen(port); - -process.on('unhandledRejection', (reason, p) => - logger.error('Unhandled Rejection at: Promise ', p, reason) -); - -server.on('listening', () => - logger.info('Feathers application started on http://%s:%d', app.get('host'), port) -); diff --git a/Goobieverse/src/interfaces/AccountModel.ts b/Goobieverse/src/interfaces/AccountModel.ts deleted file mode 100755 index f5a1d48b..00000000 --- a/Goobieverse/src/interfaces/AccountModel.ts +++ /dev/null @@ -1,42 +0,0 @@ -export interface AccountModel { - id: string; - username: string; - email: string; - accountSettings: string; // JSON of client settings - imagesHero: string; - imagesThumbnail: string; - imagesTiny: string; - password: string; - - locationConnected: boolean; - locationPath: string; // "/floatX,floatY,floatZ/floatX,floatY,floatZ,floatW" - locationPlaceId: string; // uuid of place - locationDomainId: string; // uuid of domain located in - locationNetworkAddress: string; - locationNetworkPort: number; - locationNodeId: string; // sessionId - availability: string[]; // contains 'none', 'friends', 'connections', 'all' - - connections: string[]; - friends: string[]; - locker: any; // JSON blob stored for user from server - profileDetail: any; // JSON blob stored for user from server - - // User authentication - // passwordHash: string; - // passwordSalt: string; - sessionPublicKey: string; // PEM public key generated for this session - accountEmailVerified: boolean; // default true if not present - - // Old stuff - xmppPassword: string; - discourseApiKey: string; - walletId: string; - - // Admin stuff - // ALWAYS USE functions in Roles class to manipulate this list of roles - roles: string[]; // account roles (like 'admin') - IPAddrOfCreator: string; // IP address that created this account - whenCreated: Date; // date of account creation - timeOfLastHeartbeat: Date; // when we last heard from this user -} diff --git a/Goobieverse/src/interfaces/DomainModel.ts b/Goobieverse/src/interfaces/DomainModel.ts deleted file mode 100755 index d624d64c..00000000 --- a/Goobieverse/src/interfaces/DomainModel.ts +++ /dev/null @@ -1,38 +0,0 @@ -export interface DomainModel { - id: string; // globally unique domain identifier - name: string; // domain name/label - visibility: string; // visibility of this entry in general domain lists - publicKey: string; // DomainServers's public key in multi-line PEM format - apiKey: string; // Access key if a temp domain - sponsorAccountId: string; // The account that gave this domain an access key - iceServerAddr: string; // IP address of ICE server being used by this domain - - // Information that comes in via heartbeat - version: string; // DomainServer's build version (like "K3") - protocol: string; // Protocol version - networkAddr: string; // reported network address - networkPort: string; // reported network address - networkingMode: string; // one of "full", "ip", "disabled" - restricted: boolean; // 'true' if restricted to users with accounts - numUsers: number; // total number of logged in users - anonUsers: number; // number of anonymous users - hostnames: string[]; // User segmentation - - // More information that's metadata that's passed in PUT domain - capacity: number; // Total possible users - description: string; // Short description of domain - contactInfo: string; // domain contact information - thumbnail: string; // thumbnail image of domain - images: string[]; // collection of images for the domain - maturity: string; // Maturity rating - restriction: string; // Access restrictions ("open") - managers: string[]; // Usernames of people who are domain admins - tags: string[]; // Categories for describing the domain - - // admin stuff - iPAddrOfFirstContact: string; // IP address that registered this domain - whenCreated: Date; // What the variable name says - active: boolean; // domain is heartbeating - timeOfLastHeartbeat: Date; // time of last heartbeat - lastSenderKey: string; // a key identifying the sender -} diff --git a/Goobieverse/src/interfaces/MetaverseInfo.ts b/Goobieverse/src/interfaces/MetaverseInfo.ts deleted file mode 100644 index 02b64af6..00000000 --- a/Goobieverse/src/interfaces/MetaverseInfo.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface MetaverseInfoModel { - metaverse_name: string; - metaverse_nick_name: string; - metaverse_url: string; - ice_server_url: string; - metaverse_server_version: any; -} diff --git a/Goobieverse/src/interfaces/PlaceModel.ts b/Goobieverse/src/interfaces/PlaceModel.ts deleted file mode 100755 index fcf2ce2a..00000000 --- a/Goobieverse/src/interfaces/PlaceModel.ts +++ /dev/null @@ -1,28 +0,0 @@ -export interface PlaceModel { - id: string; // globally unique place identifier - name: string; // Human friendly name of the place - displayName: string; // Human friendly name of the place - description: string; // Human friendly description of the place - visibility: string; // visibility of this Place in general Place lists - maturity: string; // maturity level of the place (see Sets/Maturity.ts) - tags: string[]; // tags defining the string content - domainId: string; // domain the place is in - managers: string[]; // Usernames of people who are domain admins - path: string; // address within the domain: "optional-domain/x,y,z/x,y,z,x" - thumbnail: string; // thumbnail for place - images: string[]; // images for the place - - // A Place can have a beacon that updates current state and information - // If current information is not supplied, attendance defaults to domain's - currentAttendance: number; // current attendance at the Place - currentImages: string[]; // images at the session - currentInfo: string; // JSON information about the session - currentLastUpdateTime: Date; // time that the last session information was updated - currentAPIKeyTokenId: string; // API key for updating the session information - - // admin stuff - iPAddrOfFirstContact: string; // IP address that registered this place - whenCreated: Date; // What the variable name says - // 'lastActivity' is computed by Places.initPlaces and used for aliveness checks - lastActivity: Date; // newest of currentLastUpdateTime and Domain.timeOfLastHeartbeat -} diff --git a/Goobieverse/src/interfaces/RequestModal.ts b/Goobieverse/src/interfaces/RequestModal.ts deleted file mode 100644 index 8b840094..00000000 --- a/Goobieverse/src/interfaces/RequestModal.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface RequestEntity { - id: string; - requestType: string; - - // requestor and target - requestingAccountId: string; - targetAccountId: string; - - // administration - expirationTime: Date; - whenCreated: Date; - - // requestType == HANDSHAKE - requesterNodeId: string; - targetNodeId: string; - requesterAccepted: boolean; - targetAccepted: boolean; - - // requestType == VERIFYEMAIL - // 'requestingAccountId' is the account being verified - verificationCode: string; // the code we're waiting for -} diff --git a/Goobieverse/src/logger.ts b/Goobieverse/src/logger.ts deleted file mode 100644 index a533961a..00000000 --- a/Goobieverse/src/logger.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createLogger, format, transports } from 'winston'; - -// Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston -const logger = createLogger({ - // To see more detailed errors, change this to 'debug' - level: 'info', - format: format.combine( - format.splat(), - format.simple() - ), - transports: [ - new transports.Console() - ], -}); - -export default logger; diff --git a/Goobieverse/src/middleware/index.ts b/Goobieverse/src/middleware/index.ts deleted file mode 100644 index 0bad39e0..00000000 --- a/Goobieverse/src/middleware/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Application } from '../declarations'; -// Don't remove this comment. It's needed to format import lines nicely. - -// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function -export default function (app: Application): void {} diff --git a/Goobieverse/src/mongodb.ts b/Goobieverse/src/mongodb.ts deleted file mode 100644 index 97cda256..00000000 --- a/Goobieverse/src/mongodb.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { MongoClient } from 'mongodb'; -import { Application } from './declarations'; -import { IsNotNullOrEmpty } from './utils/Misc'; -export default function (app: Application): void { - let connection = ''; - if(IsNotNullOrEmpty(process.env.DATABASE_URL)){ - connection = process.env.DATABASE_URL || ''; - }else{ - const userSpec = `${process.env.DB_USER}:${process.env.DB_PASSWORD}`; - const hostSpec = `${process.env.DB_HOST}:${process.env.DB_PORT}`; - let optionsSpec = ''; - if (process.env.DB_AUTHDB !== 'admin') { - optionsSpec += `?authSource=${process.env.DB_AUTHDB}`; - } - connection = `mongodb://${userSpec}@${hostSpec}/${optionsSpec}`; - } - const database = process.env.DB_NAME; - const mongoClient = MongoClient.connect(connection).then(client => client.db(database)); - app.set('mongoClient', mongoClient); -} diff --git a/Goobieverse/src/responsebuilder/accountsBuilder.ts b/Goobieverse/src/responsebuilder/accountsBuilder.ts deleted file mode 100644 index fea1af45..00000000 --- a/Goobieverse/src/responsebuilder/accountsBuilder.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { DomainModel } from '../interfaces/DomainModel'; -import { AccountModel } from '../interfaces/AccountModel'; -import { buildLocationInfo } from './placesBuilder'; -import { VKeyedCollection } from '../utils/vTypes'; -import { isAdmin, isEnabled, createSimplifiedPublicKey } from '../utils/Utils'; -// Return the limited "user" info.. used by /api/v1/users -export async function buildUserInfo(pAccount: AccountModel): Promise { - return { - accountId: pAccount.id, - id: pAccount.id, - username: pAccount.username, - images: await buildImageInfo(pAccount), - location: await buildLocationInfo(pAccount), - }; -} - -export async function buildImageInfo(pAccount: AccountModel): Promise { - const ret: VKeyedCollection = {}; - - if (pAccount.imagesTiny) ret.tiny = pAccount.imagesTiny; - if (pAccount.imagesHero) ret.hero = pAccount.imagesHero; - if (pAccount.imagesThumbnail) ret.thumbnail = pAccount.imagesThumbnail; - return ret; -} - -// Return the block of account information. -// Used by several of the requests to return the complete account information. -export async function buildAccountInfo( - pAccount: AccountModel -): Promise { - return { - accountId: pAccount.id, - id: pAccount.id, - username: pAccount.username, - email: pAccount.email, - administrator: isAdmin(pAccount), - enabled: isEnabled(pAccount), - roles: pAccount.roles, - availability: pAccount.availability, - public_key: createSimplifiedPublicKey(pAccount.sessionPublicKey), - images: { - hero: pAccount.imagesHero, - tiny: pAccount.imagesTiny, - thumbnail: pAccount.imagesThumbnail, - }, - profile_detail: pAccount.profileDetail, - location: await buildLocationInfo(pAccount), - friends: pAccount.friends, - connections: pAccount.connections, - when_account_created: pAccount.whenCreated?.toISOString(), - when_account_created_s: pAccount.whenCreated?.getTime().toString(), - time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat?.getTime().toString(), - }; -} - -// Return the block of account information used as the account 'profile'. -// Anyone can fetch a profile (if 'availability' is 'any') so not all info is returned -export async function buildAccountProfile( - pAccount: AccountModel, - aDomain?: DomainModel -): Promise { - return { - accountId: pAccount.id, - id: pAccount.id, - username: pAccount.username, - images: { - hero: pAccount.imagesHero, - tiny: pAccount.imagesTiny, - thumbnail: pAccount.imagesThumbnail, - }, - profile_detail: pAccount.profileDetail, - location: await buildLocationInfo(pAccount, aDomain), - when_account_created: pAccount.whenCreated?.toISOString(), - when_account_created_s: pAccount.whenCreated?.getTime().toString(), - time_of_last_heartbeat: pAccount.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pAccount.timeOfLastHeartbeat?.getTime().toString(), - }; -} \ No newline at end of file diff --git a/Goobieverse/src/responsebuilder/domainsBuilder.ts b/Goobieverse/src/responsebuilder/domainsBuilder.ts deleted file mode 100644 index 842582bb..00000000 --- a/Goobieverse/src/responsebuilder/domainsBuilder.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { Visibility } from '../utils/sets/Visibility'; -import { DomainModel } from '../interfaces/DomainModel'; -import { createSimplifiedPublicKey } from '../utils/Utils'; -import { buildPlacesForDomain } from './placesBuilder'; -import { Maturity } from '../utils/sets/Maturity'; -// A smaller, top-level domain info block -export async function buildDomainInfo(pDomain: DomainModel): Promise { - return { - id: pDomain.id, - domainId: pDomain.id, - name: pDomain.name, - visibility: pDomain.visibility ?? Visibility.OPEN, - capacity: pDomain.capacity, - sponsorAccountId: pDomain.sponsorAccountId, - label: pDomain.name, - network_address: pDomain.networkAddr, - network_port: pDomain.networkPort, - ice_server_address: pDomain.iceServerAddr, - version: pDomain.version, - protocol_version: pDomain.protocol, - active: pDomain.active ?? false, - time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), - num_users: pDomain.numUsers, - }; -} - -// Return a structure with the usual domain information. -export async function buildDomainInfoV1(pDomain: DomainModel): Promise { - return { - domainId: pDomain.id, - id: pDomain.id, // legacy - name: pDomain.name, - visibility: pDomain.visibility ?? Visibility.OPEN, - world_name: pDomain.name, // legacy - label: pDomain.name, // legacy - public_key: pDomain.publicKey? createSimplifiedPublicKey(pDomain.publicKey): undefined, - owner_places: await buildPlacesForDomain(pDomain), - sponsor_account_id: pDomain.sponsorAccountId, - ice_server_address: pDomain.iceServerAddr, - version: pDomain.version, - protocol_version: pDomain.protocol, - network_address: pDomain.networkAddr, - network_port: pDomain.networkPort, - automatic_networking: pDomain.networkingMode, - restricted: pDomain.restricted, - num_users: pDomain.numUsers, - anon_users: pDomain.anonUsers, - total_users: pDomain.numUsers, - capacity: pDomain.capacity, - description: pDomain.description, - maturity: pDomain.maturity ?? Maturity.UNRATED, - restriction: pDomain.restriction, - managers: pDomain.managers, - tags: pDomain.tags, - meta: { - capacity: pDomain.capacity, - contact_info: pDomain.contactInfo, - description: pDomain.description, - images: pDomain.images, - managers: pDomain.managers, - restriction: pDomain.restriction, - tags: pDomain.tags, - thumbnail: pDomain.thumbnail, - world_name: pDomain.name, - }, - users: { - num_anon_users: pDomain.anonUsers, - num_users: pDomain.numUsers, - user_hostnames: pDomain.hostnames, - }, - time_of_last_heartbeat: pDomain.timeOfLastHeartbeat?.toISOString(), - time_of_last_heartbeat_s: pDomain.timeOfLastHeartbeat?.getTime().toString(), - last_sender_key: pDomain.lastSenderKey, - addr_of_first_contact: pDomain.iPAddrOfFirstContact, - when_domain_entry_created: pDomain.whenCreated?.toISOString(), - when_domain_entry_created_s: pDomain.whenCreated?.getTime().toString(), - }; -} - \ No newline at end of file diff --git a/Goobieverse/src/responsebuilder/placesBuilder.ts b/Goobieverse/src/responsebuilder/placesBuilder.ts deleted file mode 100644 index 76eed347..00000000 --- a/Goobieverse/src/responsebuilder/placesBuilder.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { AccountModel } from './../interfaces/AccountModel'; -import { IsNotNullOrEmpty,IsNullOrEmpty } from '../utils/Misc'; -import { buildDomainInfo } from './domainsBuilder'; -import { DomainModel } from '../interfaces/DomainModel'; -import { isOnline } from '../utils/Utils'; -import { PlaceModel } from '../interfaces/PlaceModel'; -import { Visibility } from '../utils/sets/Visibility'; -import { Maturity } from '../utils/sets/Maturity'; -// The returned location info has many options depending on whether -// the account has set location and/or has an associated domain. -// Return a structure that represents the target account's domain - -export async function buildLocationInfo(pAcct: AccountModel,aDomain?: DomainModel): Promise { - let ret: any = {}; - if (pAcct.locationDomainId) { - if (IsNotNullOrEmpty(aDomain) && aDomain) { - ret = { - root: { - domain: await buildDomainInfo(aDomain), - }, - path: pAcct.locationPath, - }; - } else { - // The domain doesn't have an ID - ret = { - root: { - domain: { - network_address: pAcct.locationNetworkAddress, - network_port: pAcct.locationNetworkPort, - }, - }, - }; - } - } - ret.node_id = pAcct.locationNodeId; - ret.online = isOnline(pAcct); - return ret; -} - -// Return an object with the formatted place information -// Pass the PlaceModel and the place's domain if known. -export async function buildPlaceInfo(pPlace: PlaceModel,pDomain?: DomainModel): Promise { - const ret = await buildPlaceInfoSmall(pPlace, pDomain); - - // if the place points to a domain, add that information also - if (IsNotNullOrEmpty(pDomain) && pDomain) { - ret.domain = await buildDomainInfo(pDomain); - } - return ret; -} - - -function getAddressString(pPlace: PlaceModel,aDomain?: DomainModel): string { -// Compute and return the string for the Places's address. -// The address is of the form "optional-domain/x,y,z/x,y,z,w". -// If the domain is missing, the domain-server's network address is added - let addr = pPlace.path ?? '/0,0,0/0,0,0,1'; - - // If no domain/address specified in path, build addr using reported domain IP/port - const pieces = addr.split('/'); - if (pieces[0].length === 0) { - if (IsNotNullOrEmpty(aDomain) && aDomain) { - if (IsNotNullOrEmpty(aDomain.networkAddr)) { - let domainAddr = aDomain.networkAddr; - if (IsNotNullOrEmpty(aDomain.networkPort)) { - domainAddr = aDomain.networkAddr + ':' + aDomain.networkPort; - } - addr = domainAddr + addr; - } - } - } - return addr; -} - - -// Return the basic information block for a Place -export async function buildPlaceInfoSmall(pPlace: PlaceModel,pDomain?: DomainModel): Promise { - const ret = { - placeId: pPlace.id, - id: pPlace.id, - name: pPlace.name, - displayName: pPlace.displayName, - visibility: pPlace.visibility ?? Visibility.OPEN, - address: getAddressString(pPlace,pDomain), - path: pPlace.path, - description: pPlace.description, - maturity: pPlace.maturity ?? Maturity.UNRATED, - tags: pPlace.tags, - managers: await getManagers(pPlace,pDomain), - thumbnail: pPlace.thumbnail, - images: pPlace.images, - current_attendance: pPlace.currentAttendance ?? 0, - current_images: pPlace.currentImages, - current_info: pPlace.currentInfo, - current_last_update_time: pPlace.currentLastUpdateTime?.toISOString(), - current_last_update_time_s: pPlace.currentLastUpdateTime?.getTime().toString(), - last_activity_update: pPlace.lastActivity?.toISOString(), - last_activity_update_s: pPlace.lastActivity?.getTime().toString(), - }; - return ret; -} - - -async function getManagers(pPlace: PlaceModel,aDomain?: DomainModel): Promise { - if(IsNullOrEmpty(pPlace.managers)) { - pPlace.managers = []; - //uncomment after complete Accounts Places api - /* - if (aDomain) { - const aAccount = await Accounts.getAccountWithId(aDomain.sponsorAccountId); - if (aAccount) { - pPlace.managers = [ aAccount.username ]; - } - } - await Places.updateEntityFields(pPlace, { 'managers': pPlace.managers }) - */ - } - return pPlace.managers; -} - - -// Return an array of Places names that are associated with the passed domain -export async function buildPlacesForDomain(pDomain: DomainModel): Promise { - const ret: any[] = []; - //uncomment after complete Places api - /* for await (const aPlace of Places.enumerateAsync(new GenericFilter({ domainId: pDomain.id }))) { - ret.push(await buildPlaceInfoSmall(aPlace, pDomain)); - }*/ - return ret; -} \ No newline at end of file diff --git a/Goobieverse/src/responsebuilder/tokensBuilder.ts b/Goobieverse/src/responsebuilder/tokensBuilder.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/Goobieverse/src/routes/publicRoutes.ts b/Goobieverse/src/routes/publicRoutes.ts deleted file mode 100644 index 6d2f24f5..00000000 --- a/Goobieverse/src/routes/publicRoutes.ts +++ /dev/null @@ -1,9 +0,0 @@ -import express from 'express'; -import { PublicRoutesController } from '../controllers/PublicRoutesController'; - -const publicRoutes = express.Router(); - -publicRoutes.get('/metaverse_info',PublicRoutesController().metaverseInfo); - -export { publicRoutes}; - \ No newline at end of file diff --git a/Goobieverse/src/services/accounts/accounts.class.ts b/Goobieverse/src/services/accounts/accounts.class.ts deleted file mode 100644 index db64af00..00000000 --- a/Goobieverse/src/services/accounts/accounts.class.ts +++ /dev/null @@ -1,173 +0,0 @@ - -import { DatabaseServiceOptions } from './../../dbservice/DatabaseServiceOptions'; -import { Params, Id, NullableId } from '@feathersjs/feathers'; -import { Application } from '../../declarations'; -import { DatabaseService } from './../../dbservice/DatabaseService'; -import config from '../../appconfig'; -import { AccountModel } from '../../interfaces/AccountModel'; -import { buildAccountInfo } from '../../responsebuilder/accountsBuilder'; -import { IsNotNullOrEmpty } from '../../utils/Misc'; -import { isAdmin,dateWhenNotOnline,couldBeDomainId,validateEmail } from '../../utils/Utils'; -import { messages } from '../../utils/messages'; -import { VKeyedCollection,SArray } from '../../utils/vTypes'; - -export class Accounts extends DatabaseService { - constructor(options: Partial, app: Application) { - super(options,app); - } - - async find(params?: Params): Promise { - const loginUserId = params?.user?.id ?? ''; - const perPage = parseInt(params?.query?.per_page) || 10; - const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; - - // Passed the request, get the filter parameters from the query. - // Here we pre-process the parameters to make the DB query construction quicker. - // filter=connections|friends|all - // status=online|domainId - // search=wildcardSearchString - // The administrator can specify an account to limit requests to - // acct = account id - //asAdmin=true: if logged in account is administrator, list all accounts. Value is optional. - let asAdmin = params?.query?.asAdmin === 'true'?true:false; - const filter:string[] = (params?.query?.filter ?? '').split(','); - const status:string = params?.query?.status ?? ''; - const targetAccount = params?.query?.account ?? ''; - - const filterQuery: any = {}; - - if(asAdmin && IsNotNullOrEmpty(params?.user) && isAdmin(params?.user as AccountModel)){ - asAdmin = true; - }else{ - asAdmin =false; - } - - if(filter.length > 0){ - if(!filter.includes('all')){ - if(filter.includes('friends') && (params?.user?.friends??[]).length > 0){ - filterQuery.friends = {$in: params?.user?.friends}; - } - if(filter.includes('connections') && (params?.user?.connections??[]).length > 0){ - filterQuery.connections = {$in: params?.user?.connections}; - } - } - } - - if(IsNotNullOrEmpty(status)){ - if (status ==='online'){ - filterQuery.timeOfLastHeartbeat = {$gte:dateWhenNotOnline()}; - }else if(couldBeDomainId(status)){ - filterQuery.locationDomainId = status; - } - } - - if(!asAdmin){ - filterQuery.id = loginUserId; - }else if(IsNotNullOrEmpty(targetAccount)){ - filterQuery.id = targetAccount; - } - - const accountData = await this.findData(config.dbCollections.accounts,{ - query:{ - ...filterQuery, - $skip: skip, - $limit: perPage - }, - }); - - let accountsList:AccountModel[] = []; - - if(accountData instanceof Array){ - accountsList = accountData as Array; - }else{ - accountsList = accountData.data as Array; - } - - const accounts: Array = []; - - (accountsList as Array)?.forEach(async (element) => { - - accounts.push(await buildAccountInfo(element)); - }); - return Promise.resolve({ accounts }); - } - - async get(id: Id): Promise { - const objAccount = await this.getData(config.dbCollections.accounts,id); - if(IsNotNullOrEmpty(objAccount)){ - const account = await buildAccountInfo(objAccount); - return Promise.resolve({ account }); - } else { - throw new Error(messages.common_messages_target_account_notfound); - } - - } - - async patch(id: NullableId, data: any, params: Params): Promise { - if(IsNotNullOrEmpty(id) && id){ - if(IsNotNullOrEmpty(params?.user) && isAdmin(params?.user as AccountModel) || id === params?.user?.id){ - const valuesToSet = data.accounts??{}; - const updates: VKeyedCollection = {}; - if(IsNotNullOrEmpty(valuesToSet.email)) { - if(!validateEmail(valuesToSet.email)){ - throw new Error(messages.common_messages_email_validation_error); - } - const accountData = await this.findDataToArray(config.dbCollections.accounts,{query:{email: valuesToSet.email}}); - if(accountData.length>0 && accountData[0].id !== id){ - throw new Error(messages.common_messages_user_email_link_error); - } - updates.email = valuesToSet.email; - } - if(IsNotNullOrEmpty(valuesToSet.public_key)) { - updates.public_key = valuesToSet.public_key; - } - - if (valuesToSet.hasOwnProperty('images')) { - if (IsNotNullOrEmpty(valuesToSet.images.hero)) { - updates.imagesHero = valuesToSet.images.hero; - } - - if (IsNotNullOrEmpty(valuesToSet.images.tiny)) { - updates.imagesTiny = valuesToSet.images.tiny; - } - - if (IsNotNullOrEmpty(valuesToSet.images.thumbnail)) { - updates.imagesThumbnail = valuesToSet.images.thumbnail; - } - } - await this.patchData(config.dbCollections.accounts,id,updates); - return Promise.resolve(); - }else{ - throw new Error(messages.common_messages_unauthorized); - } - } else { - throw new Error(messages.common_messages_target_account_notfound); - } - } - - async remove(id: NullableId): Promise { - if(IsNotNullOrEmpty(id) && id){ - const account = await this.getData(config.dbCollections.accounts,id); - - if(IsNotNullOrEmpty(account)){ - this.deleteData(config.dbCollections.accounts,id); - const accounts:AccountModel[] = await this.findDataToArray(config.dbCollections.accounts, {query:{$or:[{connections:{$in: [account.username] }},{friends:{$in:[account.username] }}]}}); - - for(const element of accounts){ - SArray.remove(element.connections,account.username); - SArray.remove(element.friends,account.username); - await super.patchData(config.dbCollections.accounts,element.id,{connections:element.connections,friends:element.friends}); - } - - await this.deleteMultipleData(config.dbCollections.domains,{query:{sponsorAccountId:account.id}}); - await this.deleteMultipleData(config.dbCollections.places,{query:{accountId:account.id}}); - - return Promise.resolve(); - }else{ - throw new Error(messages.common_messages_target_account_notfound); - } - }else{ - throw new Error(messages.common_messages_target_account_notfound); - } - } -} diff --git a/Goobieverse/src/services/accounts/accounts.hooks.ts b/Goobieverse/src/services/accounts/accounts.hooks.ts deleted file mode 100644 index b416f3c6..00000000 --- a/Goobieverse/src/services/accounts/accounts.hooks.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as feathersAuthentication from '@feathersjs/authentication'; -const { authenticate } = feathersAuthentication.hooks; -import requestSuccess from '../../hooks/requestSuccess'; -import requestFail from '../../hooks/requestFail'; -import checkAccessToAccount from '../../hooks/checkAccess'; -import config from '../../appconfig'; -import {Perm} from '../../utils/Perm'; -import isHasAuthToken from '../../hooks/isHasAuthToken'; -import { iff } from 'feathers-hooks-common'; -import { disallow } from 'feathers-hooks-common'; - -export default { - before: { - all: [], - find: [authenticate('jwt')], - get: [iff(isHasAuthToken(),authenticate('jwt')),checkAccessToAccount(config.dbCollections.accounts,[Perm.PUBLIC,Perm.OWNER,Perm.ADMIN])], - create: [disallow()], - update: [disallow()], - patch: [authenticate('jwt')], - remove: [authenticate('jwt'),checkAccessToAccount(config.dbCollections.accounts,[Perm.ADMIN])] - }, - - after: { - all: [requestSuccess()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, - - error: { - all: [requestFail()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } -}; diff --git a/Goobieverse/src/services/accounts/accounts.service.ts b/Goobieverse/src/services/accounts/accounts.service.ts deleted file mode 100644 index 1bdf1a33..00000000 --- a/Goobieverse/src/services/accounts/accounts.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Initializes the `accounts` service on path `/accounts` -import { ServiceAddons } from '@feathersjs/feathers'; -import { Application } from '../../declarations'; -import { Accounts } from './accounts.class'; -import hooks from './accounts.hooks'; - -// Add this service to the service type index -declare module '../../declarations' { - interface ServiceTypes { - 'accounts': Accounts & ServiceAddons; - } -} - -export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - id:'id', - multi:['remove'] - }; - - // Initialize our service with any options it requires - app.use('/accounts', new Accounts(options, app)); - - //app.use('/accounts/:accountId/field/:fieldName', app.service('accounts')); - // Get our initialized service so that we can register hooks - const service = app.service('accounts'); - service.hooks(hooks); -} diff --git a/Goobieverse/src/services/auth/auth.class.ts b/Goobieverse/src/services/auth/auth.class.ts deleted file mode 100644 index ce6fc722..00000000 --- a/Goobieverse/src/services/auth/auth.class.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Db } from 'mongodb'; -import { Service, MongoDBServiceOptions } from 'feathers-mongodb'; -import { Application } from '../../declarations'; - -export class Auth extends Service { - constructor(options: Partial, app: Application) { - super(options); - const client: Promise = app.get('mongoClient'); - client.then((db) => { - this.Model = db.collection('accounts'); - }); - } -} diff --git a/Goobieverse/src/services/auth/auth.hooks.ts b/Goobieverse/src/services/auth/auth.hooks.ts deleted file mode 100644 index 006e7f9d..00000000 --- a/Goobieverse/src/services/auth/auth.hooks.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { HooksObject } from '@feathersjs/feathers'; -import * as feathersAuthentication from '@feathersjs/authentication'; -import * as local from '@feathersjs/authentication-local'; -import { disallow } from 'feathers-hooks-common'; -const { authenticate } = feathersAuthentication.hooks; -const { protect } = local.hooks; - -export default { - before: { - all: [], - find: [ authenticate('jwt') ], - get: [ authenticate('jwt') ], - create: [disallow('external')], - update: [disallow('external')], - patch: [disallow('external')], - remove: [ disallow('external')] - }, - - after: { - all: [protect('password')], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, - - error: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, -} as HooksObject; diff --git a/Goobieverse/src/services/auth/auth.service.ts b/Goobieverse/src/services/auth/auth.service.ts deleted file mode 100644 index 6311c26e..00000000 --- a/Goobieverse/src/services/auth/auth.service.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Initializes the `users` service on path `/users` -import { ServiceAddons } from '@feathersjs/feathers'; -import { Application } from '../../declarations'; -import { Auth } from './auth.class'; -import hooks from './auth.hooks'; - -// Add this service to the service type index -declare module '../../declarations' { - interface ServiceTypes { - auth: Auth & ServiceAddons; - } -} - -export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - }; - - // Initialize our service with any options it requires - app.use('/auth', new Auth(options, app)); - - // Get our initialized service so that we can register hooks - const service = app.service('auth'); - - service.hooks(hooks); -} diff --git a/Goobieverse/src/services/connections/connections.class.ts b/Goobieverse/src/services/connections/connections.class.ts deleted file mode 100644 index 31a252cc..00000000 --- a/Goobieverse/src/services/connections/connections.class.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { MongoDBServiceOptions } from 'feathers-mongodb'; -import { DatabaseService } from './../../dbservice/DatabaseService'; -import { Application } from '../../declarations'; -import config from '../../appconfig'; -import { Response } from '../../utils/response'; -import { isValidObject } from '../../utils/Misc'; - -export class Connections extends DatabaseService { - //eslint-disable-next-line @typescript-eslint/no-unused-vars - constructor(options: Partial, app: Application) { - super(options, app); - this.app = app; - } - - async create(data: any, params?: any): Promise { - if (data && data.username) { - const userData: any = await this.getData(config.dbCollections.accounts, params.user.id); - - userData.connections.push(data.username); - const addUserData = await this.patchData(config.dbCollections.accounts,params.user.id,userData); - if (isValidObject(addUserData)) { - return Promise.resolve({}); - } else { - return Response.error('cannot add connections this way'); - } - } else { - return Response.error('Badly formed request'); - } - } - - -} diff --git a/Goobieverse/src/services/connections/connections.hooks.ts b/Goobieverse/src/services/connections/connections.hooks.ts deleted file mode 100644 index 4b49e80e..00000000 --- a/Goobieverse/src/services/connections/connections.hooks.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { HooksObject } from '@feathersjs/feathers'; -import * as authentication from '@feathersjs/authentication'; -import requestFail from '../../hooks/requestFail'; -import requestSuccess from '../../hooks/requestSuccess'; - -const { authenticate } = authentication.hooks; - -export default { - before: { - all: [authenticate('jwt')], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, - - after: { - all: [requestSuccess()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, - - error: { - all: [requestFail()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } -} as HooksObject; diff --git a/Goobieverse/src/services/connections/connections.service.ts b/Goobieverse/src/services/connections/connections.service.ts deleted file mode 100644 index 7c808902..00000000 --- a/Goobieverse/src/services/connections/connections.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Initializes the `connections` service on path `/connections` -import { ServiceAddons } from '@feathersjs/feathers'; -import { Application } from '../../declarations'; -import { Connections } from './connections.class'; -import hooks from './connections.hooks'; - -// Add this service to the service type index -declare module '../../declarations' { - interface ServiceTypes { - 'connections': Connections & ServiceAddons; - } -} - -export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - id:'id' - }; - - // Initialize our service with any options it requires - app.use('/connections', new Connections(options, app)); - - // Get our initialized service so that we can register hooks - const service = app.service('connections'); - - service.hooks(hooks); -} diff --git a/Goobieverse/src/services/email/email.class.ts b/Goobieverse/src/services/email/email.class.ts deleted file mode 100644 index 33ea265a..00000000 --- a/Goobieverse/src/services/email/email.class.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { DatabaseService } from './../../dbservice/DatabaseService'; -import { DatabaseServiceOptions } from './../../dbservice/DatabaseServiceOptions'; -import { Application } from '../../declarations'; -export class Email extends DatabaseService { - //eslint-disable-next-line @typescript-eslint/no-unused-vars - constructor(options: Partial, app: Application) { - super(options,app); - } -} diff --git a/Goobieverse/src/services/email/email.hooks.ts b/Goobieverse/src/services/email/email.hooks.ts deleted file mode 100644 index 71a766e7..00000000 --- a/Goobieverse/src/services/email/email.hooks.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { HooksObject } from '@feathersjs/feathers'; - -export default { - before: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, - - after: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, - - error: { - all: [], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } -} as HooksObject; diff --git a/Goobieverse/src/services/email/email.service.ts b/Goobieverse/src/services/email/email.service.ts deleted file mode 100644 index c238f3f3..00000000 --- a/Goobieverse/src/services/email/email.service.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Initializes the `email` service on path `/email` -import { ServiceAddons } from '@feathersjs/feathers'; -import { Application } from '../../declarations'; -import { Email } from './email.class'; -import hooks from './email.hooks'; -import config from '../../appconfig'; -import smtpTransport from 'nodemailer-smtp-transport'; -import Mailer from 'feathers-mailer'; - - -// Add this service to the service type index -declare module '../../declarations' { - interface ServiceTypes { - 'email': Email & ServiceAddons; - } -} - -export default function (app: Application): void { - const event = Mailer(smtpTransport(config.email)); - app.use('email', event); - - const service = app.service('email'); - - service.hooks(hooks); -} diff --git a/Goobieverse/src/services/friends/friends.class.ts b/Goobieverse/src/services/friends/friends.class.ts deleted file mode 100644 index beaf666d..00000000 --- a/Goobieverse/src/services/friends/friends.class.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { MongoDBServiceOptions } from 'feathers-mongodb'; -import { DatabaseService } from './../../dbservice/DatabaseService'; -import { Application } from '../../declarations'; -import config from '../../appconfig'; -import { Response } from '../../utils/response'; - - -export class Friends extends DatabaseService { - //eslint-disable-next-line @typescript-eslint/no-unused-vars - constructor(options: Partial, app: Application) { - super(options, app); - this.app = app; - } - - async create(data: any, params?: any): Promise { - if (data && data.username) { - const ParticularUserData: any = await this.findData(config.dbCollections.accounts, { query: { id: params.user.id } }); - if (ParticularUserData.data[0].connections.includes(data.username)) { - const newParticularUserData = ParticularUserData.data[0]; - newParticularUserData.friends.push(data.username); - await this.patchData(config.dbCollections.accounts, params.user.id,newParticularUserData); - } else { - return Response.error('cannot add friend who is not a connection'); - } - } else { - return Response.error('Badly formed request'); - } - } - - async find(params?: any): Promise { - if (params.user.friends) { - const friends = params.user.friends; - return Promise.resolve({ friends }); - } else { - throw new Error('No friend found'); - } - } - - async remove(id: string, params?: any): Promise { - if (params.user.friends) { - const ParticularUserData: any = await this.findData(config.dbCollections.accounts, { query: { id: params.user.id } }); - const friends = ParticularUserData.data[0].friends.filter(function (value:string) { - return value !== id; - }); - ParticularUserData.data[0].friends = friends; - const newParticularUserData = ParticularUserData.data[0]; - await this.patchData(config.dbCollections.accounts,params.user.id,newParticularUserData); - } else { - throw new Error('Not logged in'); - } - } - -} diff --git a/Goobieverse/src/services/friends/friends.hooks.ts b/Goobieverse/src/services/friends/friends.hooks.ts deleted file mode 100644 index b91b71ce..00000000 --- a/Goobieverse/src/services/friends/friends.hooks.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { HooksObject } from '@feathersjs/feathers'; -import * as authentication from '@feathersjs/authentication'; -import requestFail from '../../hooks/requestFail'; -import requestSuccess from '../../hooks/requestSuccess'; - -const { authenticate } = authentication.hooks; - -export default { - before: { - all: [authenticate('jwt')], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, - - after: { - all: [requestSuccess()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, - - error: { - all: [requestFail()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, -} as HooksObject; diff --git a/Goobieverse/src/services/friends/friends.service.ts b/Goobieverse/src/services/friends/friends.service.ts deleted file mode 100644 index 9c3117c3..00000000 --- a/Goobieverse/src/services/friends/friends.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Initializes the `friends` service on path `/friends` -import { ServiceAddons } from '@feathersjs/feathers'; -import { Application } from '../../declarations'; -import { Friends } from './friends.class'; -import hooks from './friends.hooks'; - -// Add this service to the service type index -declare module '../../declarations' { - interface ServiceTypes { - friends: Friends & ServiceAddons; - } -} - -export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - id:'id' - }; - - // Initialize our service with any options it requires - app.use('/friends', new Friends(options, app)); - - // Get our initialized service so that we can register hooks - const service = app.service('friends'); - - service.hooks(hooks); -} diff --git a/Goobieverse/src/services/index.ts b/Goobieverse/src/services/index.ts deleted file mode 100644 index 83238d5e..00000000 --- a/Goobieverse/src/services/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Application } from '../declarations'; -import profiles from './profiles/profiles.service'; -// Don't remove this comment. It's needed to format import lines nicely. -import users from './users/users.service'; -import friends from './friends/friends.service'; -import auth from './auth/auth.service'; -import email from './email/email.service'; -import connections from './connections/connections.service'; - -import accounts from './accounts/accounts.service'; - -export default function (app: Application): void { - app.configure(auth); - app.configure(users); - app.configure(friends); - app.configure(profiles); - app.configure(accounts); - app.configure(email); - app.configure(connections); -} diff --git a/Goobieverse/src/services/profiles/profiles.class.ts b/Goobieverse/src/services/profiles/profiles.class.ts deleted file mode 100644 index baebc707..00000000 --- a/Goobieverse/src/services/profiles/profiles.class.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { DatabaseServiceOptions } from './../../dbservice/DatabaseServiceOptions'; -import { DatabaseService } from './../../dbservice/DatabaseService'; -import { DomainModel } from './../../interfaces/DomainModel'; -import { AccountModel } from '../../interfaces/AccountModel'; -import config from '../../appconfig'; -import { Availability } from '../../utils/sets/Availability'; -import { Params, Id } from '@feathersjs/feathers'; -import { Application } from '../../declarations'; -import { buildAccountProfile } from '../../responsebuilder/accountsBuilder'; -import { IsNotNullOrEmpty } from '../../utils/Misc'; -import { messages } from '../../utils/messages'; - -export class Profiles extends DatabaseService { - constructor(options: Partial, app: Application) { - super(options, app); - } - - async find(params?: Params): Promise { - const perPage = parseInt(params?.query?.per_page) || 10; - const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; - - const accountData = await this.findData(config.dbCollections.accounts, { - query: { - $or: [ - { availability: undefined }, - { availability: Availability.ALL }, - ], - $skip: skip, - $limit: perPage, - }, - }); - - let accounts: AccountModel[] = []; - - if (accountData instanceof Array) { - accounts = accountData as Array; - } else { - accounts = accountData.data as Array; - } - - const domainIds = (accounts as Array) - ?.map((item) => item.locationDomainId) - .filter( - (value, index, self) => - self.indexOf(value) === index && value !== undefined - ); - - const domains = await this.findDataToArray( - config.dbCollections.domains, - { query: { id: { $in: domainIds } } } - ); - - const profiles: Array = []; - - (accounts as Array)?.forEach(async (element) => { - let domainModel: DomainModel | undefined; - for (const domain of domains) { - if (domain && domain.id === element.locationDomainId) { - domainModel = domain; - break; - } - } - profiles.push(await buildAccountProfile(element, domainModel)); - }); - return Promise.resolve({ profiles }); - } - - async get(id: Id): Promise { - const account = await this.getData(config.dbCollections.accounts, id); - - if (IsNotNullOrEmpty(account)) { - const domains = await this.findDataToArray( - config.dbCollections.domains, - { id: { $eq: account.locationDomainId } } - ); - let domainModel: any; - if (IsNotNullOrEmpty(domains)) { - domainModel = domains[0]; - } - const profile = await buildAccountProfile(account, domainModel); - return Promise.resolve({ profile }); - } else { - throw new Error(messages.common_messages_target_profile_notfound); - } - } -} diff --git a/Goobieverse/src/services/profiles/profiles.hooks.ts b/Goobieverse/src/services/profiles/profiles.hooks.ts deleted file mode 100644 index aa41b209..00000000 --- a/Goobieverse/src/services/profiles/profiles.hooks.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { disallow } from 'feathers-hooks-common'; -import checkAccessToAccount from '../../hooks/checkAccess'; -import requestFail from '../../hooks/requestFail'; -import requestSuccess from '../../hooks/requestSuccess'; -import {Perm} from '../../utils/Perm'; -import config from '../../appconfig'; -import { iff } from 'feathers-hooks-common'; -import isHasAuthToken from '../../hooks/isHasAuthToken'; -import * as feathersAuthentication from '@feathersjs/authentication'; -const { authenticate } = feathersAuthentication.hooks; - - -export default { - before: { - all: [iff(isHasAuthToken(),authenticate('jwt'))], - find: [], - get: [checkAccessToAccount(config.dbCollections.accounts,[Perm.PUBLIC,Perm.OWNER,Perm.ADMIN])], - create: [disallow()], - update: [disallow()], - patch: [disallow()], - remove: [disallow()] - }, - - after: { - all: [requestSuccess()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - }, - - error: { - all: [requestFail()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [] - } -}; diff --git a/Goobieverse/src/services/profiles/profiles.service.ts b/Goobieverse/src/services/profiles/profiles.service.ts deleted file mode 100644 index 13f86647..00000000 --- a/Goobieverse/src/services/profiles/profiles.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Initializes the `profiles` service on path `/profiles` -import { ServiceAddons } from '@feathersjs/feathers'; -import { Application } from '../../declarations'; -import { Profiles } from './profiles.class'; -import hooks from './profiles.hooks'; - -// Add this service to the service type index -declare module '../../declarations' { - interface ServiceTypes { - 'profiles': Profiles & ServiceAddons; - } -} - -export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - id:'id', - }; - - // Initialize our service with any options it requires - app.use('/profiles', new Profiles(options, app)); - - // Get our initialized service so that we can register hooks - const service = app.service('profiles'); - - service.hooks(hooks); -} diff --git a/Goobieverse/src/services/users/users.class.ts b/Goobieverse/src/services/users/users.class.ts deleted file mode 100644 index a5f00f06..00000000 --- a/Goobieverse/src/services/users/users.class.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { DatabaseService } from './../../dbservice/DatabaseService'; -import { MongoDBServiceOptions } from 'feathers-mongodb'; -import { Application } from '../../declarations'; -import config from '../../appconfig'; -import { Params } from '@feathersjs/feathers'; -import { AccountModel } from '../../interfaces/AccountModel'; -import { GenUUID } from '../../utils/Misc'; -import { Roles } from '../../utils/sets/Roles'; -import { IsNullOrEmpty, isValidObject } from '../../utils/Misc'; -import { SArray } from '../../utils/vTypes'; -import { sendEmail } from '../../utils/mail'; -import path from 'path'; -import fsPromises from 'fs/promises'; - -export class Users extends DatabaseService { - app: Application; - //eslint-disable-next-line @typescript-eslint/no-unused-vars - constructor(options: Partial, app: Application) { - super(options, app); - this.app = app; - } - - async create(data: AccountModel): Promise { - if (data.username && data.email && data.password) { - const username: string = data.username; - const email: string = data.email; - if (username) { - const accountsName: AccountModel[] = await this.findDataToArray( - config.dbCollections.accounts, - { query: { username: username } } - ); - const name = (accountsName as Array)?.map( - (item) => item.username - ); - if (!name.includes(username)) { - const accountsEmail: AccountModel[] = - await this.findDataToArray( - config.dbCollections.accounts, - { query: { email: email } } - ); - const emailAddress = ( - accountsEmail as Array - )?.map((item) => item.email); - if (!emailAddress.includes(email)) { - const id = GenUUID(); - const roles = [Roles.USER]; - const friends: string[] = []; - const connections: string[] = []; - const whenCreated = new Date(); - const accountIsActive = true; - const accountWaitingVerification = false; - const accounts = await this.CreateData( - config.dbCollections.accounts, - { - ...data, - id: id, - roles: roles, - whenCreated: whenCreated, - friends: friends, - connections: connections, - accountIsActive: accountIsActive, - accountWaitingVerification: - accountWaitingVerification, - } - ); - if (isValidObject(accounts)) { - const emailToValidate = data.email; - const emailRegexp = - /^[a-zA-Z0-9.!#$%&'+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)$/; - if (emailRegexp.test(emailToValidate)) { - try { - const adminAccountName = - config.metaverseServer[ - 'base_admin_account' - ]; - if ( - accounts.username === adminAccountName - ) { - if (IsNullOrEmpty(accounts.roles)) - accounts.roles = []; - SArray.add(accounts.roles, Roles.ADMIN); - } - - const verificationURL = - config.metaverse['metaverseServerUrl'] + - `/api/v1/account/verify/email?a=${accounts.id}&v=${accounts.id}`; - const metaverseName = - config.metaverse['metaverseName']; - const shortMetaverseName = - config.metaverse['metaverseNickName']; - const verificationFile = path.join( - __dirname, - '../..', - config.metaverseServer[ - 'email_verification_email_body' - ] - ); - - let emailBody = await fsPromises.readFile( - verificationFile, - 'utf-8' - ); - emailBody = emailBody - .replace( - 'VERIFICATION_URL', - verificationURL - ) - .replace( - 'METAVERSE_NAME', - metaverseName - ) - .replace( - 'SHORT_METAVERSE_NAME', - shortMetaverseName - ); - - const email = { - from: 'khilan.odan@gmail.com', - to: accounts.email, - subject: `${shortMetaverseName} account verification`, - html: emailBody, - }; - await sendEmail( - this.app, - email - ); - - return Promise.resolve({ - accountId: accounts.id, - username: accounts.username, - accountIsActive: - accounts.accountIsActive, - accountWaitingVerification: - accounts.accountWaitingVerification, - }); - } catch (error: any) { - throw new Error( - 'Exception adding user: ' + error - ); - } - } else { - throw new Error('Send valid Email address'); - } - } else { - throw new Error('Could not create account'); - } - } else { - throw new Error('Email already exists'); - } - } else { - throw new Error('Account already exists'); - } - } else { - throw new Error('Badly formatted username'); - } - } else { - throw new Error('Badly formatted request'); - } - } - - async find(params?: Params): Promise { - const perPage = parseInt(params?.query?.per_page) || 10; - const skip = ((parseInt(params?.query?.page) || 1) - 1) * perPage; - - const user = await this.findDataToArray(config.dbCollections.accounts, { - query: { - accountIsActive: true, - $select: ['username', 'accountId'], - $skip: skip, - $limit: perPage, - }, - }); - - return Promise.resolve({ user }); - } -} diff --git a/Goobieverse/src/services/users/users.hooks.ts b/Goobieverse/src/services/users/users.hooks.ts deleted file mode 100644 index 36fabba4..00000000 --- a/Goobieverse/src/services/users/users.hooks.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { HooksObject } from '@feathersjs/feathers'; -import * as local from '@feathersjs/authentication-local'; -import requestFail from '../../hooks/requestFail'; -import requestSuccess from '../../hooks/requestSuccess'; -// import { Perm } from '../../utils/Perm'; -// import checkAccessToAccount from '../../hooks/checkAccessToAccount'; -import * as authentication from '@feathersjs/authentication'; - -const { authenticate } = authentication.hooks; -const { hashPassword } = local.hooks; - -export default { - before: { - all: [], - find: [authenticate('jwt')], - get: [], - create: [hashPassword('password')], - update: [hashPassword('password')], - patch: [], - remove: [], - }, - - after: { - all: [requestSuccess()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, - - error: { - all: [requestFail()], - find: [], - get: [], - create: [], - update: [], - patch: [], - remove: [], - }, -} as HooksObject; diff --git a/Goobieverse/src/services/users/users.service.ts b/Goobieverse/src/services/users/users.service.ts deleted file mode 100644 index 109d3047..00000000 --- a/Goobieverse/src/services/users/users.service.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Initializes the `users` service on path `/users` -import { ServiceAddons } from '@feathersjs/feathers'; -import { Application } from '../../declarations'; -import { Users } from './users.class'; -import hooks from './users.hooks'; - -// Add this service to the service type index -declare module '../../declarations' { - interface ServiceTypes { - users: Users & ServiceAddons; - } -} - -export default function (app: Application): void { - const options = { - paginate: app.get('paginate'), - id:'id' - }; - - // Initialize our service with any options it requires - app.use('/users', new Users(options, app)); - - // Get our initialized service so that we can register hooks - const service = app.service('users'); - - service.hooks(hooks); -} diff --git a/Goobieverse/src/utils/Misc.ts b/Goobieverse/src/utils/Misc.ts deleted file mode 100644 index 95477dfc..00000000 --- a/Goobieverse/src/utils/Misc.ts +++ /dev/null @@ -1,142 +0,0 @@ -import fs from 'fs'; -import http from 'http'; -import https from 'https'; -import os from 'os'; -import { v4 as uuidv4 } from 'uuid'; - -// Return 'true' if the passed value is null or empty -export function IsNullOrEmpty(pVal: any): boolean { - return ( - typeof pVal === 'undefined' || - pVal === null || - (typeof pVal === 'string' && String(pVal).length === 0) - ); -} - -export function GenUUID(): string { - return uuidv4(); -} - -// Return 'true' if the passed value is not null or empty -export function IsNotNullOrEmpty(pVal: any): boolean { - return !IsNullOrEmpty(pVal); -} - -// Utility routine that reads in JSON content from either an URL or a filename. -// Returns the parsed JSON object or 'undefined' if any errors. -export async function readInJSON(pFilenameOrURL: string): Promise { - let configBody: string; - if (pFilenameOrURL.startsWith('http://')) { - configBody = await httpRequest(pFilenameOrURL); - } else { - if (pFilenameOrURL.startsWith('https://')) { - configBody = await httpsRequest(pFilenameOrURL); - } else { - try { - // We should technically sanitize this filename but if one can change the environment - // or config file variables, the app is already poned. - configBody = fs.readFileSync(pFilenameOrURL, 'utf-8'); - } catch (err) { - configBody = ''; - console.debug( - `readInJSON: failed read of user config file ${pFilenameOrURL}: ${err}` - ); - } - } - } - if (IsNotNullOrEmpty(configBody)) { - return JSON.parse(configBody); - } - return undefined; -} - -// Do a simple https GET and return the response as a string -export async function httpsRequest(pUrl: string): Promise { - return new Promise((resolve, reject) => { - https - .get(pUrl, (resp: any) => { - let data = ''; - resp.on('data', (chunk: string) => { - data += chunk; - }); - resp.on('end', () => { - resolve(data); - }); - }) - .on('error', (err: any) => { - reject(err); - }); - }); -} - -// Do a simple http GET and return the response as a string -export async function httpRequest(pUrl: string): Promise { - return new Promise((resolve, reject) => { - http - .get(pUrl, (resp: any) => { - let data = ''; - resp.on('data', (chunk: string) => { - data += chunk; - }); - resp.on('end', () => { - resolve(data); - }); - }) - .on('error', (err: any) => { - reject(err); - }); - }); -} - -let myExternalAddr: string; -export async function getMyExternalIPAddress(): Promise { - if (IsNotNullOrEmpty(myExternalAddr)) { - return Promise.resolve(myExternalAddr); - } - return new Promise((resolve, reject) => { - httpsRequest('https://api.ipify.org') - .then((resp) => { - myExternalAddr = resp; - resolve(myExternalAddr); - }) - .catch(() => { - // Can't get it that way for some reason. Ask our interface - const networkInterfaces = os.networkInterfaces(); - // { 'lo1': [ info, info ], 'eth0': [ info, info ]} where 'info' could be v4 and v6 addr infos - - let addressv4 = ''; - let addressv6 = ''; - - Object.keys(networkInterfaces).forEach((dev) => { - networkInterfaces[dev]?.filter((details) => { - if (details.family === 'IPv4' && details.internal === false) { - addressv4 = details.address; - } - if (details.family === 'IPv6' && details.internal === false) { - addressv6 = details.address; - } - }); - }); - let address = ''; - if (IsNullOrEmpty(addressv4)) { - address = addressv6; - } else { - address = addressv6; - } - - if (IsNullOrEmpty(address)) { - reject('No address found'); - } - myExternalAddr = address.toString(); - resolve(myExternalAddr); - }); - }); -} - -export const isValidArray = (arr: []) => { - return arr && Array.isArray(arr) && arr.length > 0; -}; - -export const isValidObject = (obj: object) => { - return obj && Object.keys(obj).length > 0; -}; diff --git a/Goobieverse/src/utils/Perm.ts b/Goobieverse/src/utils/Perm.ts deleted file mode 100644 index 5e31f941..00000000 --- a/Goobieverse/src/utils/Perm.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2020 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -// Permission codes: -// 'all': any one -// 'domain': the requesting authToken is for a domain and the sponsor account matches -// 'owner': the requesting account is the owner of the target account -// 'friend': the requesting account is a friend of the target account -// 'connection': the requesting account is a connection of the target account -// 'admin': the requesting account has 'admin' privilages -// 'sponsor': the requesting account is the sponsor of the traget domain -// 'domainaccess': the target entity has a domain and requesting account must be sponsor -export class Perm { - public static NONE = 'none'; - public static ALL = 'all'; - public static PUBLIC = 'public'; // target account is publicly visible - public static DOMAIN = 'domain'; // check against .sponsorId - public static OWNER = 'owner'; // check against .id or .accountId - public static FRIEND = 'friend'; // check member of .friends - public static CONNECTION = 'connection';// check member of .connections - public static ADMIN = 'admin'; // check if isAdmin - public static SPONSOR = 'sponsor'; // check against .sponsorAccountId - public static MANAGER = 'manager'; // check against .managers - public static DOMAINACCESS = 'domainaccess'; // check that entity's domain has access -} - diff --git a/Goobieverse/src/utils/Utils.ts b/Goobieverse/src/utils/Utils.ts deleted file mode 100644 index 557deb56..00000000 --- a/Goobieverse/src/utils/Utils.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { AccountModel } from './../interfaces/AccountModel'; -import { SArray } from './vTypes'; -import { Roles } from './sets/Roles'; -import config from '../appconfig'; - - -// The legacy interface returns public keys as a stripped PEM key. -// "stripped" in that the bounding "BEGIN" and "END" lines have been removed. -// This routine returns a stripped key string from a properly PEM formatted public key string. -export function createSimplifiedPublicKey(pPubKey: string): string { - let keyLines: string[] = []; - if (pPubKey) { - keyLines = pPubKey.split('\n'); - keyLines.shift(); // Remove the "BEGIN" first line - while (keyLines.length > 1 - && ( keyLines[keyLines.length-1].length < 1 || keyLines[keyLines.length-1].includes('END PUBLIC KEY') ) ) { - keyLines.pop(); // Remove the "END" last line - } - } - return keyLines.join(''); // Combine all lines into one long string -} - -// getter property that is 'true' if the user is a grid administrator -export function isAdmin(pAcct: AccountModel): boolean { - return SArray.has(pAcct.roles, Roles.ADMIN); -} -// Any logic to test of account is active -// Currently checks if account email is verified or is legacy -// account (no 'accountEmailVerified' variable) -export function isEnabled(pAcct: AccountModel): boolean { - return pAcct.accountEmailVerified ?? true; -} - - -export function isOnline(pAcct: AccountModel): boolean { - if (pAcct && pAcct.timeOfLastHeartbeat) { - return ( - Date.now().valueOf() - pAcct.timeOfLastHeartbeat.valueOf() < - config.metaverseServer.heartbeat_seconds_until_offline * 1000 - ); - } - return false; -} - -export function couldBeDomainId(pId: string): boolean { - return pId.length === 36; -} - -// Return the ISODate when an account is considered offline -export function dateWhenNotOnline(): Date { - const whenOffline = new Date(Date.now() - config.metaverseServer.heartbeat_seconds_until_offline); - return whenOffline; -} - -export function validateEmail(email:string):boolean{ - const emailRegexp = /^[a-zA-Z0-9.!#$%&'+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)$/; - return emailRegexp.test(email); -} \ No newline at end of file diff --git a/Goobieverse/src/utils/mail.ts b/Goobieverse/src/utils/mail.ts deleted file mode 100644 index c68f20df..00000000 --- a/Goobieverse/src/utils/mail.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Application } from '../declarations'; -import { BadRequest } from '@feathersjs/errors'; - -export async function sendEmail(app: Application, email: any): Promise { - if (email.to) { - email.html = email.html.replace(/&/g, '&'); - try { - const abc = await app - .service('email') - .create(email) - .then(function (result) { - return result; - }); - return abc; - } catch (error: any) { - return Promise.reject(new BadRequest(error)); - } - } -} \ No newline at end of file diff --git a/Goobieverse/src/utils/messages.ts b/Goobieverse/src/utils/messages.ts deleted file mode 100644 index 5782c003..00000000 --- a/Goobieverse/src/utils/messages.ts +++ /dev/null @@ -1,15 +0,0 @@ -export const messages = { - common_messages_db_error: 'Database loading fail.', - common_messages_error: 'Something went wrong please try again later.', - common_messages_record_available: 'Record is available.', - common_messages_record_not_available: 'Record is not available.', - common_messages_records_available: 'Records are available.', - common_messages_records_not_available: 'Records are not available.', - common_messages_record_added_failed:'Failed to add record!', - common_messages_target_profile_notfound:'Target profile not found', - common_messages_target_account_notfound:'Target account not found', - common_messages_user_email_link_error:'email already exists for another account', - common_messages_unauthorized:'Unauthorized', - common_messages_email_validation_error:'Invalid email address' - -}; diff --git a/Goobieverse/src/utils/response.ts b/Goobieverse/src/utils/response.ts deleted file mode 100644 index d8bd30fb..00000000 --- a/Goobieverse/src/utils/response.ts +++ /dev/null @@ -1,17 +0,0 @@ -export const Response = { - success : (data: any,additionalFields?:any) => { - return {status: 'success',data: data,...additionalFields}; - }, - error:(message: string,additionalFields?:any) => { - return { status: 'failure', message: message,...additionalFields}; - } -}; - -export enum HTTPStatusCode { - OK = 200, - Found = 302, - BadRequest = 400, - Unauthorized = 401, - Forbidden = 403, - NotFound = 404, -} diff --git a/Goobieverse/src/utils/sets/Availability.ts b/Goobieverse/src/utils/sets/Availability.ts deleted file mode 100644 index 26d71346..00000000 --- a/Goobieverse/src/utils/sets/Availability.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2020 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -export class Availability { - public static NONE = 'none'; // no one can see me - public static FRIENDS = 'friends'; // available to friends - public static CONNECTIONS= 'connections'; // available to connections - public static ALL = 'all'; // available to all - - // See if the passed availability code is a known availability token - static async KnownAvailability(pAvailability: string): Promise { - return [ Availability.NONE,Availability.FRIENDS,Availability.CONNECTIONS,Availability.ALL].includes(pAvailability); - } -} - diff --git a/Goobieverse/src/utils/sets/Maturity.ts b/Goobieverse/src/utils/sets/Maturity.ts deleted file mode 100644 index aac1b9fc..00000000 --- a/Goobieverse/src/utils/sets/Maturity.ts +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2020 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -export class Maturity { - public static UNRATED = 'unrated'; - public static EVERYONE = 'everyone'; - public static TEEN = 'teen'; - public static MATURE = 'mature'; - public static ADULT = 'adult'; - - static MaturityCategories = [ Maturity.UNRATED, - Maturity.EVERYONE, - Maturity.TEEN, - Maturity.MATURE, - Maturity.ADULT - ]; - - static KnownMaturity(pMaturity: string): boolean { - return this.MaturityCategories.includes(pMaturity); - } -} - diff --git a/Goobieverse/src/utils/sets/Roles.ts b/Goobieverse/src/utils/sets/Roles.ts deleted file mode 100644 index 24a1bd55..00000000 --- a/Goobieverse/src/utils/sets/Roles.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2020 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -// Class to manage the manipulations on roles that accounts can have -export class Roles { - // at the moment, the only role is 'admin' - public static ADMIN = 'admin'; // someone who has metaverse-server admin - public static USER = 'user'; // a 'user' or 'person' - - // See if the passed role code is a known role token - static async KnownRole(pScope: string): Promise { - return [ Roles.ADMIN, Roles.USER ].includes(pScope); - } -} diff --git a/Goobieverse/src/utils/sets/Visibility.ts b/Goobieverse/src/utils/sets/Visibility.ts deleted file mode 100644 index cd7d60d4..00000000 --- a/Goobieverse/src/utils/sets/Visibility.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2021 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -export class Visibility { - public static OPEN = 'open'; - public static FRIENDS = 'friends'; - public static CONNECTIONS = 'connections'; - public static GROUP = 'group'; - public static PRIVATE = 'private'; - - static VisibilityCategories = [ - Visibility.OPEN, - Visibility.FRIENDS, - Visibility.CONNECTIONS, - Visibility.GROUP, - Visibility.PRIVATE - ]; - - static KnownVisibility(pVisibility: string): boolean { - return this.VisibilityCategories.includes(pVisibility); - } -} - diff --git a/Goobieverse/src/utils/vTypes.ts b/Goobieverse/src/utils/vTypes.ts deleted file mode 100755 index 1615f543..00000000 --- a/Goobieverse/src/utils/vTypes.ts +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2020 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -'use strict'; - -import { IsNullOrEmpty, IsNotNullOrEmpty } from './Misc'; - -// An object that is used as a keyed collection of objects. -// The key is always a string -export interface VKeyedCollection { - [ key: string]: any -} - -export interface VKeyValue { - [ key: string]: string -} - -// String array. -// Several structures are an array of strings (TokenScope, AccountRoles, ...). -export class SArray { - static has(pArray: string[], pCheck: string): boolean { - return IsNullOrEmpty(pArray) ? false : pArray.includes(pCheck); - } - - static hasNoCase(pArray: string[], pCheck: string): boolean { - const pCheckLower = pCheck.toLowerCase(); - if (IsNotNullOrEmpty(pArray)) { - for (const ent of pArray) { - if (ent.toLowerCase() === pCheckLower) { - return true; - } - } - } - return false; - } - - static add(pArray: string[], pAdd: string): boolean { - let added = false; - if (typeof(pAdd) === 'string') { - if (! pArray.includes(pAdd)) { - pArray.push(pAdd); - added = true; - } - } - return added; - } - - static remove(pArray: string[], pRemove: string): void { - const idx = pArray.indexOf(pRemove); - if (idx >= 0) { - pArray.splice(idx, 1); - } - } -} \ No newline at end of file diff --git a/Goobieverse/test/app.test.ts b/Goobieverse/test/app.test.ts deleted file mode 100644 index cf6216ef..00000000 --- a/Goobieverse/test/app.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Server } from 'http'; -import url from 'url'; -import axios from 'axios'; - -import app from '../src/app'; - -const port = app.get('port') || 8998; -const getUrl = (pathname?: string): string => url.format({ - hostname: app.get('host') || 'localhost', - protocol: 'http', - port, - pathname -}); - -describe('Feathers application tests (with jest)', () => { - let server: Server; - - beforeAll(done => { - server = app.listen(port); - server.once('listening', () => done()); - }); - - afterAll(done => { - server.close(done); - }); - - it('starts and shows the index page', async () => { - expect.assertions(1); - - const { data } = await axios.get(getUrl()); - - expect(data.indexOf('')).not.toBe(-1); - }); - - describe('404', () => { - it('shows a 404 HTML page', async () => { - expect.assertions(2); - - try { - await axios.get(getUrl('path/to/nowhere'), { - headers: { - 'Accept': 'text/html' - } - }); - } catch (error: any) { - const { response } = error; - - expect(response.status).toBe(404); - expect(response.data.indexOf('')).not.toBe(-1); - } - }); - - it('shows a 404 JSON error without stack trace', async () => { - expect.assertions(4); - - try { - await axios.get(getUrl('path/to/nowhere')); - } catch (error: any) { - const { response } = error; - - expect(response.status).toBe(404); - expect(response.data.code).toBe(404); - expect(response.data.message).toBe('Page not found'); - expect(response.data.name).toBe('NotFound'); - } - }); - }); -}); diff --git a/Goobieverse/test/authentication.test.ts b/Goobieverse/test/authentication.test.ts deleted file mode 100644 index e4be0f99..00000000 --- a/Goobieverse/test/authentication.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import app from '../src/app'; - -describe('authentication', () => { - it('registered the authentication service', () => { - expect(app.service('authentication')).toBeTruthy(); - }); - - describe('local strategy', () => { - const userInfo = { - email: 'someone@example.com', - password: 'supersecret' - }; - - beforeAll(async () => { - try { - await app.service('users').create(userInfo); - } catch (error) { - // Do nothing, it just means the user already exists and can be tested - } - }); - - it('authenticates user and creates accessToken', async () => { - const { user, accessToken } = await app.service('authentication').create({ - strategy: 'local', - ...userInfo - }, {}); - - expect(accessToken).toBeTruthy(); - expect(user).toBeTruthy(); - }); - }); -}); diff --git a/Goobieverse/test/services/accounts.test.ts b/Goobieverse/test/services/accounts.test.ts deleted file mode 100644 index 52e4e2f4..00000000 --- a/Goobieverse/test/services/accounts.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import app from '../../src/app'; - -describe('\'accounts\' service', () => { - it('registered the service', () => { - const service = app.service('accounts'); - expect(service).toBeTruthy(); - }); -}); diff --git a/Goobieverse/test/services/connections.test.ts b/Goobieverse/test/services/connections.test.ts deleted file mode 100644 index bfef87ad..00000000 --- a/Goobieverse/test/services/connections.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import app from '../../src/app'; - -describe('\'connections\' service', () => { - it('registered the service', () => { - const service = app.service('connections'); - expect(service).toBeTruthy(); - }); -}); diff --git a/Goobieverse/test/services/email.test.ts b/Goobieverse/test/services/email.test.ts deleted file mode 100644 index 61421438..00000000 --- a/Goobieverse/test/services/email.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import app from '../../src/app'; - -describe('\'email\' service', () => { - it('registered the service', () => { - const service = app.service('email'); - expect(service).toBeTruthy(); - }); -}); diff --git a/Goobieverse/test/services/friends.test.ts b/Goobieverse/test/services/friends.test.ts deleted file mode 100644 index 55c8b625..00000000 --- a/Goobieverse/test/services/friends.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import app from '../../src/app'; - -describe('\'friends\' service', () => { - it('registered the service', () => { - const service = app.service('friends'); - expect(service).toBeTruthy(); - }); -}); diff --git a/Goobieverse/test/services/metaverse_info.test.ts b/Goobieverse/test/services/metaverse_info.test.ts deleted file mode 100644 index f0682157..00000000 --- a/Goobieverse/test/services/metaverse_info.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import app from '../../src/app'; - -describe('\'metaverse_info\' service', () => { - it('registered the service', () => { - const service = app.service('metaverse_info'); - expect(service).toBeTruthy(); - }); -}); diff --git a/Goobieverse/test/services/profiles.test.ts b/Goobieverse/test/services/profiles.test.ts deleted file mode 100644 index 634f87e8..00000000 --- a/Goobieverse/test/services/profiles.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import app from '../../src/app'; - -describe('\'profiles\' service', () => { - it('registered the service', () => { - const service = app.service('profiles'); - expect(service).toBeTruthy(); - }); -}); diff --git a/Goobieverse/test/services/users.test.ts b/Goobieverse/test/services/users.test.ts deleted file mode 100644 index ff54c796..00000000 --- a/Goobieverse/test/services/users.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import app from '../../src/app'; - -describe('\'users\' service', () => { - it('registered the service', () => { - const service = app.service('users'); - expect(service).toBeTruthy(); - }); -}); diff --git a/Goobieverse/tsconfig.json b/Goobieverse/tsconfig.json deleted file mode 100644 index 70dd6b8a..00000000 --- a/Goobieverse/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "compilerOptions": { - "target": "es2018", - "module": "commonjs", - "outDir": "./lib", - "rootDir": "./src", - "strict": true, - "esModuleInterop": true - }, - "exclude": [ - "test" - ] -} diff --git a/Goobieverse/types.d.ts b/Goobieverse/types.d.ts deleted file mode 100644 index 0a5888aa..00000000 --- a/Goobieverse/types.d.ts +++ /dev/null @@ -1,5 +0,0 @@ - - -declare module 'feathers-mailer' { - export default function Mailer(transport: any, defaults?: any): any; -} diff --git a/Goobieverse/verificationEmail.html b/Goobieverse/verificationEmail.html deleted file mode 100644 index 0d98d273..00000000 --- a/Goobieverse/verificationEmail.html +++ /dev/null @@ -1,18 +0,0 @@ -
-

- You have created an account in the METAVERSE_NAME metaverse. -

- -

- Please verify the account email by following this link: -

-

-

- See you in the virtual world! -

-

- -- SHORT_METAVERSE_NAME admin -

-
\ No newline at end of file diff --git a/Metaverse_v2 b/Metaverse_v2 new file mode 160000 index 00000000..ae54efaa --- /dev/null +++ b/Metaverse_v2 @@ -0,0 +1 @@ +Subproject commit ae54efaae6f96548bf24bb177aa743d59cc441f9 diff --git a/package-lock.json b/package-lock.json index 57c1c0f0..ac165dcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,8 +1,2157 @@ { "name": "iamus-metaverse-server", "version": "2.4.10", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "iamus-metaverse-server", + "version": "2.4.10", + "license": "Apache-2.0", + "dependencies": { + "cors": "^2.8.5", + "debug": "~2.6.9", + "deepmerge": "^4.2.2", + "express": "~4.16.1", + "fs-extra": "^9.1.0", + "glob": "^7.1.7", + "http-errors": "~1.6.3", + "loglevel": "^1.7.1", + "module-alias": "^2.2.2", + "mongodb": "^3.6.6", + "morgan": "~1.9.1", + "multer": "^1.4.2", + "nodemailer": "^6.6.0", + "ts-eager": "^2.0.2", + "unique-names-generator": "^4.5.0", + "uuid": "^8.3.2", + "winston": "^3.3.3" + }, + "devDependencies": { + "@types/cors": "^2.8.10", + "@types/debug": "^4.1.5", + "@types/express": "^4.17.11", + "@types/glob": "^7.1.3", + "@types/http-errors": "^1.8.0", + "@types/mongodb": "^3.6.12", + "@types/morgan": "^1.9.2", + "@types/multer": "^1.4.5", + "@types/node": "^14.14.44", + "@types/nodemailer": "^6.4.1", + "@types/uuid": "^8.3.0", + "npm-run-all": "^4.1.5", + "tslint": "^6.1.3", + "typescript": "^3.9.9" + }, + "engines": { + "node": ">= 14.18.1", + "npm": ">= 6.14.15" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "node_modules/@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz", + "integrity": "sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bson": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz", + "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.34", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", + "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.10", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.10.tgz", + "integrity": "sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ==", + "dev": true + }, + "node_modules/@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", + "integrity": "sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.18", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz", + "integrity": "sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", + "integrity": "sha512-2aoSC4UUbHDj2uCsCxcG/vRMXey/m17bC7UwitVm5hn22nI8O8Y9iDpA76Orc+DWkQ4zZrOKEshCqR/jSuXAHA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "node_modules/@types/mongodb": { + "version": "3.6.12", + "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.12.tgz", + "integrity": "sha512-49aEzQD5VdHPxyd5dRyQdqEveAg9LanwrH8RQipnMuulwzKmODXIZRp0umtxi1eBUfEusRkoy8AVOMr+kVuFog==", + "dev": true, + "dependencies": { + "@types/bson": "*", + "@types/node": "*" + } + }, + "node_modules/@types/morgan": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.2.tgz", + "integrity": "sha512-edtGMEdit146JwwIeyQeHHg9yID4WSolQPxpEorHmN3KuytuCHyn2ELNr5Uxy8SerniFbbkmgKMrGM933am5BQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/multer": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.5.tgz", + "integrity": "sha512-9b/0a8JyrR0r2nQhL73JR86obWL7cogfX12augvlrvcpciCo/hkvEsgu80Z4S2g2DHGVXHr8pUIi1VhqFJ8Ufw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "14.14.44", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.44.tgz", + "integrity": "sha512-+gaugz6Oce6ZInfI/tK4Pq5wIIkJMEJUu92RB3Eu93mtj4wjjjz9EB5mLp5s1pSsLXdC/CPut/xF20ZzAQJbTA==", + "dev": true + }, + "node_modules/@types/nodemailer": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.1.tgz", + "integrity": "sha512-8081UY/0XTTDpuGqCnDc8IY+Q3DSg604wB3dBH0CaZlj4nZWHWuxtZ3NRZ9c9WUrz1Vfm6wioAUnqL3bsh49uQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", + "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "node_modules/@types/serve-static": { + "version": "1.13.9", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", + "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/async": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz", + "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dependencies": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bson": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz", + "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "dependencies": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/busboy/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/busboy/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/busboy/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/color": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", + "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "dependencies": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/color-string": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz", + "integrity": "sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/colorspace": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz", + "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==", + "dependencies": { + "color": "3.0.x", + "text-hex": "1.0.x" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/denque": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz", + "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "dependencies": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/dicer/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/dicer/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/dicer/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.11.23", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.11.23.tgz", + "integrity": "sha512-iaiZZ9vUF5wJV8ob1tl+5aJTrwDczlvGP0JoMmnpC2B0ppiMCu8n8gmy5ZTGl5bcG081XBVn+U+jP+mPFm5T5Q==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "dependencies": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, + "node_modules/fecha": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", + "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" + }, + "node_modules/finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/glob": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", + "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "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/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "node_modules/is-callable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/logform": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz", + "integrity": "sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg==", + "dependencies": { + "colors": "^1.2.1", + "fast-safe-stringify": "^2.0.4", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "triple-beam": "^1.3.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/loglevel": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.1.tgz", + "integrity": "sha512-Hesni4s5UkWkwCGJMQGAh71PaLUmKFM60dHvq0zi/vDhhrzuk+4GgNbTXJ12YYQJn6ZKBDNIjYcuQGKudvqrIw==", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "bin": { + "mime": "cli.js" + } + }, + "node_modules/mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dependencies": { + "mime-db": "1.44.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/module-alias": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz", + "integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q==" + }, + "node_modules/mongodb": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.6.tgz", + "integrity": "sha512-WlirMiuV1UPbej5JeCMqE93JRfZ/ZzqE7nJTwP85XzjAF4rRSeq2bGCb1cjfoHLOF06+HxADaPGqT0g3SbVT1w==", + "dependencies": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "optional-require": "^1.0.2", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4" + }, + "optionalDependencies": { + "saslprep": "^1.0.0" + }, + "peerDependenciesMeta": { + "aws4": { + "optional": true + }, + "bson-ext": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "mongodb-extjson": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/morgan": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", + "dependencies": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/multer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz", + "integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/nodemailer": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.0.tgz", + "integrity": "sha512-ikSMDU1nZqpo2WUPE0wTTw/NGGImTkwpJKDIFPZT+YvvR9Sj+ze5wzu95JHkBMglQLoG2ITxU21WukCC/XsFkg==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/optional-require": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.0.3.tgz", + "integrity": "sha512-RV2Zp2MY2aeYK5G+B/Sps8lW5NHAzE5QClbFP15j+PWmP+T9PxlJXBOOLoSAdgwFvS4t0aMR4vpedMkbHfh0nA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dependencies": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shell-quote": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz", + "integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==", + "dev": true + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz", + "integrity": "sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "node_modules/triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "node_modules/ts-eager": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ts-eager/-/ts-eager-2.0.2.tgz", + "integrity": "sha512-xzFPL2z7mgLs0brZXaIHTm91Pjl/Cuu9AMKprgSuK+kIS2LjiG8fqqg4eqz3tgBy9OIdupb9w55pr7ea3JBB+Q==", + "dependencies": { + "esbuild": "^0.11.20", + "source-map-support": "^0.5.19" + }, + "bin": { + "ts-eager": "bin/ts-eager.sh", + "ts-eager-paths": "bin/ts-eager-paths.sh" + }, + "engines": { + "node": ">=12.18.4" + } + }, + "node_modules/tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "node_modules/typescript": { + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz", + "integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unique-names-generator": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.5.0.tgz", + "integrity": "sha512-GaiWvo3rKIHi1SyYP8/9cDrPMPSwEiYDUo2p0NQHeCHDXzLn8P6p8bttSS3lX7HHS3Yl5vnw/ulybO4GN85CgQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/winston": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz", + "integrity": "sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw==", + "dependencies": { + "@dabh/diagnostics": "^2.0.2", + "async": "^3.1.0", + "is-stream": "^2.0.0", + "logform": "^2.2.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.4.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston-transport": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz", + "integrity": "sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw==", + "dependencies": { + "readable-stream": "^2.3.7", + "triple-beam": "^1.2.0" + }, + "engines": { + "node": ">= 6.4.0" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + } + }, "dependencies": { "@babel/code-frame": { "version": "7.10.4", @@ -1460,6 +3609,14 @@ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, "string.prototype.padend": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz", @@ -1490,14 +3647,6 @@ "es-abstract": "^1.17.5" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", From 6b17405098070bc091a8d258c0349c5fc9a856b1 Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Wed, 11 May 2022 20:27:51 +0530 Subject: [PATCH 018/128] added v2 --- vircadia_meraverse_v2 | 1 + 1 file changed, 1 insertion(+) create mode 160000 vircadia_meraverse_v2 diff --git a/vircadia_meraverse_v2 b/vircadia_meraverse_v2 new file mode 160000 index 00000000..ae54efaa --- /dev/null +++ b/vircadia_meraverse_v2 @@ -0,0 +1 @@ +Subproject commit ae54efaae6f96548bf24bb177aa743d59cc441f9 From cc0487db529ad1207d5b254b91ef9471d277cd9c Mon Sep 17 00:00:00 2001 From: Rakesh Ghasadiya Date: Wed, 11 May 2022 20:31:28 +0530 Subject: [PATCH 019/128] remove old folders --- Metaverse_v2 | 1 - vircadia_meraverse_v2 | 1 - vircadia_meraverse_v2_api/.dockerignore | 4 + vircadia_meraverse_v2_api/.editorconfig | 13 + vircadia_meraverse_v2_api/.env.local.default | 93 + vircadia_meraverse_v2_api/.eslintignore | 1 + vircadia_meraverse_v2_api/.eslintrc.json | 24 + vircadia_meraverse_v2_api/.gitignore | 116 ++ vircadia_meraverse_v2_api/.prettierrc | 3 + .../.vscode/settings.json | 5 + vircadia_meraverse_v2_api/Dockerfile | 20 + vircadia_meraverse_v2_api/README.md | 85 + .../blockchain/.eslintignore | 4 + .../blockchain/.eslintrc.js | 24 + .../blockchain/.gitignore | 10 + .../blockchain/.npmignore | 3 + .../blockchain/.prettierignore | 5 + .../blockchain/.prettierrc | 1 + .../blockchain/.solhint.json | 7 + .../blockchain/.solhintignore | 1 + .../blockchain/README.md | 46 + .../blockchain/contracts/GooERC20.sol | 64 + .../blockchain/deploy/00_deploy_contracts.ts | 19 + .../blockchain/hardhat.config.ts | 59 + .../blockchain/hardhat_contracts.json | 812 ++++++++ .../blockchain/interfaces/contractTypes.ts | 94 + .../blockchain/package.json | 49 + .../blockchain/scripts/deploy.ts | 31 + .../blockchain/test/index.ts | 19 + .../blockchain/tsconfig.json | 14 + .../build_scripts/createVersion.js | 31 + .../conf.d/vircadia_conf.conf | 26 + .../conf.d/vircadia_web_conf.conf | 43 + vircadia_meraverse_v2_api/docker-compose.yml | 41 + vircadia_meraverse_v2_api/docs/.nojekyll | 1 + vircadia_meraverse_v2_api/docs/README.md | 73 + .../accounts_accounts_class.Accounts.md | 203 ++ ...hievement_achievement_class.Achievement.md | 182 ++ ...chievement_items_class.AchievementItems.md | 214 ++ .../docs/classes/auth_auth_class.Auth.md | 40 + ...nnections_connections_class.Connections.md | 155 ++ .../classes/current_current_class.Current.md | 79 + .../classes/domains_domains_class.Domains.md | 196 ++ .../docs/classes/email_email_class.Email.md | 40 + .../classes/friends_friends_class.Friends.md | 156 ++ ...a_init_master_data_class.InitMasterData.md | 792 ++++++++ ...item_inventory_item_class.InventoryItem.md | 225 +++ ...entory_transfer_class.InventoryTransfer.md | 78 + ...ntory_userInventory_class.UserInventory.md | 218 +++ ..._handler_item_handler_class.ItemHandler.md | 221 +++ .../location_location_class.Location.md | 117 ++ .../media_avatar_avatar_class.Avatar.md | 161 ++ ...r_file_browser_class.FileBrowserService.md | 213 ++ ...ce_static_resource_class.StaticResource.md | 187 ++ ...ad_media_upload_media_class.UploadMedia.md | 219 +++ ...ickup_item_pickup_item_class.PickupItem.md | 169 ++ .../docs/classes/place_place_class.Place.md | 247 +++ .../profiles_profiles_class.Profiles.md | 112 ++ .../classes/quest_apis_npc_npc_class.Npc.md | 893 +++++++++ ...s_quest_item_quest_item_class.QuestItem.md | 383 ++++ .../quest_apis_quest_quest_class.Quest.md | 375 ++++ ...word_reset_password_class.ResetPassword.md | 126 ++ .../reset_user_reset_user_class.ResetUser.md | 798 ++++++++ .../rewards_reward_class.RewardItem.md | 212 ++ .../docs/classes/users_users_class.Users.md | 130 ++ vircadia_meraverse_v2_api/docs/modules.md | 38 + .../docs/modules/accounts_accounts_class.md | 9 + .../modules/achievement_achievement_class.md | 9 + ...hievement_items_achievement_items_class.md | 9 + .../docs/modules/auth_auth_class.md | 9 + .../modules/connections_connections_class.md | 9 + .../docs/modules/current_current_class.md | 9 + .../docs/modules/domains_domains_class.md | 9 + .../docs/modules/email_email_class.md | 9 + .../docs/modules/friends_friends_class.md | 9 + .../generate_goobie_generate_goobie_class.md | 9 + ...init_master_data_init_master_data_class.md | 9 + ...ory_inventory_item_inventory_item_class.md | 9 + ...ntory_transfer_inventory_transfer_class.md | 9 + ...ntory_userInventory_userInventory_class.md | 9 + .../item_handler_item_handler_class.md | 9 + .../docs/modules/location_location_class.md | 9 + .../docs/modules/media_avatar_avatar_class.md | 9 + .../media_file_browser_file_browser_class.md | 9 + ...a_static_resource_static_resource_class.md | 9 + .../media_upload_media_upload_media_class.md | 9 + .../modules/pickup_item_pickup_item_class.md | 9 + .../docs/modules/place_place_class.md | 9 + .../docs/modules/profiles_profiles_class.md | 9 + .../docs/modules/quest_apis_npc_npc_class.md | 9 + .../quest_apis_quest_item_quest_item_class.md | 9 + .../modules/quest_apis_quest_quest_class.md | 9 + .../reset_password_reset_password_class.md | 9 + .../modules/reset_user_reset_user_class.md | 9 + .../docs/modules/rewards_reward_class.md | 9 + .../docs/modules/users_users_class.md | 9 + vircadia_meraverse_v2_api/goobie/body/0.png | Bin 0 -> 31132 bytes .../goobie/left_arm/0.png | Bin 0 -> 7352 bytes .../goobie/left_eye/0.png | Bin 0 -> 17759 bytes .../goobie/left_pupil/0.png | Bin 0 -> 10365 bytes vircadia_meraverse_v2_api/goobie/mouth/0.png | Bin 0 -> 11451 bytes .../goobie/right_arm/0.png | Bin 0 -> 7210 bytes .../goobie/right_eye/0.png | Bin 0 -> 17069 bytes .../goobie/right_pupil/0.png | Bin 0 -> 9650 bytes vircadia_meraverse_v2_api/jest.config.js | 10 + .../mailtemplates/resetPasswordOtp.html | 10 + .../mailtemplates/verificationEmail.html | 18 + .../master_data/inventory_items.json | 48 + .../master_data/npc.json | 52 + .../master_data/quest_items.json | 114 ++ vircadia_meraverse_v2_api/package.json | 127 ++ vircadia_meraverse_v2_api/public/favicon.ico | Bin 0 -> 5533 bytes vircadia_meraverse_v2_api/public/index.html | 75 + .../public/verificationEmailFailure.html | 10 + .../public/verificationEmailSuccess.html | 10 + vircadia_meraverse_v2_api/src/app.hooks.ts | 50 + vircadia_meraverse_v2_api/src/app.ts | 90 + vircadia_meraverse_v2_api/src/appconfig.ts | 284 +++ .../src/authentication.ts | 41 + vircadia_meraverse_v2_api/src/channels.ts | 81 + .../src/common/AccountFields.ts | 408 ++++ .../src/common/DomainFields.ts | 399 ++++ .../src/common/Monitoring/CounterStat.ts | 40 + .../src/common/Monitoring/EventHistogram.ts | 94 + .../src/common/Monitoring/Histogram.ts | 20 + .../src/common/Monitoring/Monitoring.ts | 67 + .../src/common/Monitoring/Stat.ts | 88 + .../src/common/Monitoring/StatsMetaverse.ts | 177 ++ .../src/common/Monitoring/StatsOs.ts | 97 + .../src/common/Monitoring/StatsServer.ts | 58 + .../src/common/Monitoring/ValueHistogram.ts | 114 ++ .../src/common/Monitoring/ValueStat.ts | 46 + .../src/common/PlaceFields.ts | 412 ++++ .../src/common/dbservice/DatabaseService.ts | 157 ++ .../dbservice/DatabaseServiceOptions.ts | 17 + .../src/common/interfaces/AccountInterface.ts | 71 + .../src/common/interfaces/Achievement.ts | 32 + .../src/common/interfaces/AuthToken.ts | 27 + .../src/common/interfaces/AvatarInterface.ts | 31 + .../src/common/interfaces/DomainInterface.ts | 54 + .../src/common/interfaces/FileContentType.ts | 22 + .../common/interfaces/Inventoryinterface.ts | 88 + .../src/common/interfaces/ItemHandler.ts | 36 + .../src/common/interfaces/MetaverseInfo.ts | 23 + .../src/common/interfaces/PlaceInterface.ts | 45 + .../src/common/interfaces/QuestInterface.ts | 34 + .../common/interfaces/QuestItemInterface.ts | 61 + .../src/common/interfaces/RequestInterface.ts | 40 + .../interfaces/ResetPasswordInterface.ts | 25 + .../src/common/interfaces/RewardInterface.ts | 32 + .../common/interfaces/UploadAssetInterface.ts | 40 + .../src/common/interfaces/npcInterface.ts | 36 + .../src/common/interfaces/storageProvider.ts | 164 ++ .../responsebuilder/AchievementBuilder.ts | 42 + .../responsebuilder/ItemHandlerBuilder.ts | 35 + .../common/responsebuilder/accountsBuilder.ts | 163 ++ .../common/responsebuilder/domainsBuilder.ts | 105 + .../responsebuilder/inventoryBuilder.ts | 45 + .../src/common/responsebuilder/npcBuilder.ts | 25 + .../common/responsebuilder/placesBuilder.ts | 174 ++ .../common/responsebuilder/questBuilder.ts | 25 + .../responsebuilder/questItemBuilder.ts | 25 + .../common/responsebuilder/responseBuilder.ts | 49 + .../src/common/sets/Availability.ts | 32 + .../src/common/sets/Maturity.ts | 35 + .../src/common/sets/RequestType.ts | 23 + .../src/common/sets/Restriction.ts | 31 + .../src/common/sets/Roles.ts | 27 + .../src/common/sets/Visibility.ts | 35 + .../src/common/types/crypto-js/index.d.ts | 15 + .../src/common/types/dauria/index.d.ts | 15 + .../src/common/types/feathers-blob/index.d.ts | 15 + .../src/common/types/fs-blob-store/index.d.ts | 15 + .../src/common/types/merge-images/index.d.ts | 15 + .../src/common/types/s3-blob-store/index.d.ts | 15 + .../src/controllers/PublicRoutesController.ts | 49 + .../src/declarations.d.ts | 20 + .../src/hooks/addOldToken.ts | 53 + .../blockchain/connectToGooTokenContract.ts | 55 + .../src/hooks/checkAccess.ts | 209 ++ .../src/hooks/isAdminUser.ts | 31 + .../src/hooks/isHasAuthToken.ts | 27 + .../src/hooks/media/add-uri-to-file.ts | 52 + .../src/hooks/media/create-static-resource.ts | 87 + .../src/hooks/media/make-s3-files-public.ts | 22 + .../src/hooks/media/reformat-upload-result.ts | 38 + .../hooks/media/set-loggedin-user-in-body.ts | 44 + .../hooks/media/set-loggedin-user-in-query.ts | 30 + .../src/hooks/media/upload-thumbnail.ts | 36 + .../hooks/media/validatePresignedRequest.ts | 94 + .../src/hooks/requestFail.ts | 30 + .../src/hooks/requestSuccess.ts | 26 + .../src/hooks/updateLastOnline.ts | 42 + .../src/hooks/userPermission.ts | 44 + .../src/hooks/validateSignature.ts | 30 + vircadia_meraverse_v2_api/src/index.ts | 33 + vircadia_meraverse_v2_api/src/logger.ts | 27 + .../src/middleware/index.ts | 21 + .../middleware/processPendingTransaction.ts | 64 + vircadia_meraverse_v2_api/src/mongodb.ts | 43 + .../src/routes/publicRoutes.ts | 24 + .../account-field/account-field.class.ts | 220 +++ .../account-field/account-field.hooks.ts | 64 + .../account-field/account-field.joi.ts | 43 + .../account-field/account-field.service.ts | 48 + .../account-publickey.class.ts | 126 ++ .../account-publickey.hooks.ts | 59 + .../account-publickey.joi.ts | 36 + .../account-publickey.service.ts | 66 + .../account-tokens/account-tokens.class.ts | 177 ++ .../account-tokens/account-tokens.hooks.ts | 60 + .../account-tokens/account-tokens.joi.ts | 41 + .../account-tokens/account-tokens.service.ts | 46 + .../src/services/accounts/accounts.class.ts | 409 ++++ .../src/services/accounts/accounts.hooks.ts | 82 + .../src/services/accounts/accounts.joi.ts | 78 + .../src/services/accounts/accounts.service.ts | 65 + .../achievement-items.class.ts | 223 +++ .../achievement-items.hooks.ts | 65 + .../achievement-items.service.ts | 42 + .../achievement-items/achivement-item.joi.ts | 52 + .../services/achievement/achievement.class.ts | 210 ++ .../services/achievement/achievement.hooks.ts | 73 + .../achievement/achievement.service.ts | 41 + .../services/achievement/achivement.joi.ts | 46 + .../src/services/auth/auth.class.ts | 34 + .../src/services/auth/auth.hooks.ts | 59 + .../src/services/auth/auth.service.ts | 43 + .../src/services/auth/auth.utils.ts | 32 + .../services/connections/connections.class.ts | 177 ++ .../services/connections/connections.hooks.ts | 61 + .../services/connections/connections.joi.ts | 43 + .../connections/connections.service.ts | 43 + .../src/services/current/current.class.ts | 100 + .../src/services/current/current.hooks.ts | 59 + .../src/services/current/current.joi.ts | 25 + .../src/services/current/current.service.ts | 43 + .../domains-fields/domains-field.class.ts | 215 ++ .../domains-fields/domains-field.hooks.ts | 61 + .../domains-fields/domains-field.joi.ts | 43 + .../domains-fields/domains-field.service.ts | 45 + .../domains-publickey.class.ts | 190 ++ .../domains-publickey.hooks.ts | 61 + .../domains-publickey.joi.ts | 41 + .../domains-publickey.service.ts | 65 + .../src/services/domains/domains.class.ts | 261 +++ .../src/services/domains/domains.hooks.ts | 70 + .../src/services/domains/domains.joi.ts | 74 + .../src/services/domains/domains.service.ts | 44 + .../domains_temp/domains-temp.class.ts | 142 ++ .../domains_temp/domains-temp.hooks.ts | 54 + .../domains_temp/domains-temp.service.ts | 63 + .../src/services/email/email.class.ts | 30 + .../src/services/email/email.hooks.ts | 50 + .../src/services/email/email.service.ts | 40 + .../src/services/explore/explore.class.ts | 184 ++ .../src/services/explore/explore.hooks.ts | 64 + .../src/services/explore/explore.joi.ts | 47 + .../src/services/explore/explore.service.ts | 44 + .../src/services/friends/friends.class.ts | 139 ++ .../src/services/friends/friends.hooks.ts | 56 + .../src/services/friends/friends.joi.ts | 21 + .../src/services/friends/friends.service.ts | 43 + .../src/services/index.ts | 101 + .../init-master-data.class.ts | 71 + .../init-master-data.hooks.ts | 34 + .../init-master-data.service.ts | 24 + .../inventory-item/inventory-item.class.ts | 248 +++ .../inventory-item/inventory-item.hooks.ts | 67 + .../inventory-item/inventory-item.joi.ts | 91 + .../inventory-item/inventory-item.service.ts | 44 + .../inventory-transfer.class.ts | 178 ++ .../inventory-transfer.hooks.ts | 57 + .../inventory-transfer.joi.ts | 26 + .../inventory-transfer.service.ts | 42 + .../src/services/inventory/services.ts | 27 + .../userInventory_ordering.class.ts | 63 + .../userInventory_ordering.hooks.ts | 59 + .../userInventory_ordering.service.ts | 44 + .../userInventory/userInventory.class.ts | 353 ++++ .../userInventory/userInventory.hooks.ts | 75 + .../userInventory/userInventory.joi.ts | 56 + .../userInventory/userInventory.service.ts | 44 + .../item-handler/item-handler.class.ts | 310 +++ .../item-handler/item-handler.hooks.ts | 68 + .../services/item-handler/item-handler.joi.ts | 37 + .../item-handler/item-handler.service.ts | 41 + .../src/services/location/location.class.ts | 150 ++ .../src/services/location/location.hooks.ts | 59 + .../src/services/location/location.joi.ts | 34 + .../src/services/location/location.service.ts | 43 + .../services/media/avatar/avatar-helper.ts | 138 ++ .../src/services/media/avatar/avatar.class.ts | 100 + .../src/services/media/avatar/avatar.hooks.ts | 52 + .../services/media/avatar/avatar.service.ts | 45 + .../media/file-browser/file-browser.class.ts | 123 ++ .../media/file-browser/file-browser.hooks.ts | 50 + .../file-browser/file-browser.service.ts | 41 + .../src/services/media/services.ts | 22 + .../static-resource/static-resource.class.ts | 107 + .../static-resource/static-resource.hooks.ts | 84 + .../static-resource.service.ts | 50 + .../media/storageprovider/getCachedAsset.ts | 25 + .../media/storageprovider/local.storage.ts | 234 +++ .../media/storageprovider/s3.storage.ts | 384 ++++ .../media/storageprovider/storageprovider.ts | 27 + .../media/upload-media/upload-asset.hooks.ts | 61 + .../upload-media/upload-asset.service.ts | 195 ++ .../media/upload-media/upload-media.class.ts | 66 + .../media/upload-media/upload-media.hooks.ts | 72 + .../upload-media/upload-media.service.ts | 94 + .../services/pickup-item/pickup-item.class.ts | 116 ++ .../services/pickup-item/pickup-item.hooks.ts | 57 + .../services/pickup-item/pickup-item.joi.ts | 22 + .../pickup-item/pickup-item.service.ts | 41 + .../src/services/place/place.class.ts | 372 ++++ .../src/services/place/place.hooks.ts | 92 + .../src/services/place/place.joi.ts | 64 + .../src/services/place/place.service.ts | 43 + .../place/places-fields/place-field.class.ts | 217 ++ .../place/places-fields/place-field.hooks.ts | 64 + .../place/places-fields/place-field.joi.ts | 43 + .../places-fields/place-field.service.ts | 45 + .../src/services/profiles/profiles.class.ts | 157 ++ .../src/services/profiles/profiles.hooks.ts | 66 + .../src/services/profiles/profiles.joi.ts | 35 + .../src/services/profiles/profiles.service.ts | 43 + .../src/services/quest_apis/npc/npc.class.ts | 253 +++ .../src/services/quest_apis/npc/npc.hooks.ts | 68 + .../src/services/quest_apis/npc/npc.joi.ts | 79 + .../services/quest_apis/npc/npc.service.ts | 41 + .../quest_apis/quest-item/quest-item.class.ts | 552 ++++++ .../quest_apis/quest-item/quest-item.hooks.ts | 59 + .../quest_apis/quest-item/quest-item.joi.ts | 98 + .../quest-item/quest-item.service.ts | 41 + .../services/quest_apis/quest/quest.class.ts | 421 ++++ .../services/quest_apis/quest/quest.hooks.ts | 69 + .../services/quest_apis/quest/quest.joi.ts | 73 + .../quest_apis/quest/quest.service.ts | 42 + .../src/services/quest_apis/services.ts | 20 + .../reset-password/reset-password.class.ts | 171 ++ .../reset-password/reset-password.hooks.ts | 61 + .../reset-password/reset-password.joi.ts | 33 + .../reset-password/reset-password.service.ts | 42 + .../services/reset-user/reset-user.class.ts | 47 + .../services/reset-user/reset-user.hooks.ts | 38 + .../services/reset-user/reset-user.service.ts | 28 + .../src/services/rewards/reward.class.ts | 430 ++++ .../src/services/rewards/reward.hooks.ts | 61 + .../src/services/rewards/reward.joi.ts | 29 + .../src/services/rewards/reward.service.ts | 44 + .../send_verify_mail.class.ts | 191 ++ .../send_verify_mail.hooks.ts | 59 + .../send_verify_mail.joi.ts | 36 + .../send_verify_mail.service.ts | 44 + .../stats/category/stat-category.class.ts | 81 + .../stats/category/stat-category.hooks.ts | 61 + .../stats/category/stat-category.joi.ts | 35 + .../stats/category/stat-category.service.ts | 44 + .../services/stats/list/stat-list.class.ts | 72 + .../services/stats/list/stat-list.hooks.ts | 54 + .../services/stats/list/stat-list.service.ts | 45 + .../src/services/stats/services.ts | 21 + .../src/services/stats/stat/stat.class.ts | 82 + .../src/services/stats/stat/stat.hooks.ts | 61 + .../src/services/stats/stat/stat.joi.ts | 35 + .../src/services/stats/stat/stat.service.ts | 44 + .../src/services/strategies/custom-oauth.ts | 47 + .../src/services/strategies/facebook.ts | 95 + .../src/services/strategies/google.ts | 77 + .../token-transfer/token-transfer.class.ts | 97 + .../token-transfer/token-transfer.hooks.ts | 65 + .../token-transfer/token-transfer.joi.ts | 26 + .../token-transfer/token-transfer.service.ts | 66 + .../src/services/users/users.class.ts | 403 ++++ .../src/services/users/users.hooks.ts | 71 + .../src/services/users/users.joi.ts | 63 + .../src/services/users/users.service.ts | 43 + .../services/verify_user/verify_user.class.ts | 95 + .../services/verify_user/verify_user.hooks.ts | 61 + .../verify_user/verify_user.service.ts | 44 + .../src/utils/CommonKnownContentTypes.ts | 34 + .../src/utils/InventoryUtils.ts | 26 + vircadia_meraverse_v2_api/src/utils/Misc.ts | 178 ++ vircadia_meraverse_v2_api/src/utils/Perm.ts | 38 + .../src/utils/Permissions.ts | 190 ++ vircadia_meraverse_v2_api/src/utils/Tokens.ts | 133 ++ vircadia_meraverse_v2_api/src/utils/Utils.ts | 187 ++ .../src/utils/Validators.ts | 265 +++ .../src/utils/constants.ts | 26 + .../src/utils/fileUtils.ts | 21 + .../src/utils/get-basic-mimetype.ts | 36 + vircadia_meraverse_v2_api/src/utils/mail.ts | 34 + .../src/utils/messages.ts | 162 ++ .../src/utils/response.ts | 33 + vircadia_meraverse_v2_api/src/utils/vTypes.ts | 64 + .../test/authentication.test.ts | 1740 +++++++++++++++++ .../test/services/accounts.test.ts | 8 + .../test/services/achievement-items.test.ts | 8 + .../test/services/achievement.test.ts | 8 + .../test/services/connections.test.ts | 8 + .../test/services/current.test.ts | 8 + .../test/services/domains.test.ts | 8 + .../test/services/email.test.ts | 8 + .../test/services/friends.test.ts | 8 + .../test/services/init-master-data.test.ts | 8 + .../test/services/inventory-item.test.ts | 8 + .../test/services/inventory-transfer.test.ts | 8 + .../test/services/item-handler.test.ts | 8 + .../test/services/location.test.ts | 8 + .../test/services/npc.test.ts | 8 + .../test/services/pickup-item.test.ts | 8 + .../test/services/place.test.ts | 8 + .../test/services/profiles.test.ts | 8 + .../test/services/quest-item.test.ts | 8 + .../test/services/quest.test.ts | 8 + .../test/services/reset-password.test.ts | 8 + .../test/services/reset-user.test.ts | 8 + .../test/services/reward.test.ts | 8 + .../test/services/token-transfer.test.ts | 349 ++++ .../test/services/users.test.ts | 8 + vircadia_meraverse_v2_api/tsconfig.json | 17 + vircadia_meraverse_v2_api/types.d.ts | 7 + 423 files changed, 35430 insertions(+), 2 deletions(-) delete mode 160000 Metaverse_v2 delete mode 160000 vircadia_meraverse_v2 create mode 100644 vircadia_meraverse_v2_api/.dockerignore create mode 100644 vircadia_meraverse_v2_api/.editorconfig create mode 100644 vircadia_meraverse_v2_api/.env.local.default create mode 100644 vircadia_meraverse_v2_api/.eslintignore create mode 100644 vircadia_meraverse_v2_api/.eslintrc.json create mode 100644 vircadia_meraverse_v2_api/.gitignore create mode 100644 vircadia_meraverse_v2_api/.prettierrc create mode 100644 vircadia_meraverse_v2_api/.vscode/settings.json create mode 100644 vircadia_meraverse_v2_api/Dockerfile create mode 100644 vircadia_meraverse_v2_api/README.md create mode 100644 vircadia_meraverse_v2_api/blockchain/.eslintignore create mode 100644 vircadia_meraverse_v2_api/blockchain/.eslintrc.js create mode 100644 vircadia_meraverse_v2_api/blockchain/.gitignore create mode 100644 vircadia_meraverse_v2_api/blockchain/.npmignore create mode 100644 vircadia_meraverse_v2_api/blockchain/.prettierignore create mode 100644 vircadia_meraverse_v2_api/blockchain/.prettierrc create mode 100644 vircadia_meraverse_v2_api/blockchain/.solhint.json create mode 100644 vircadia_meraverse_v2_api/blockchain/.solhintignore create mode 100644 vircadia_meraverse_v2_api/blockchain/README.md create mode 100644 vircadia_meraverse_v2_api/blockchain/contracts/GooERC20.sol create mode 100644 vircadia_meraverse_v2_api/blockchain/deploy/00_deploy_contracts.ts create mode 100644 vircadia_meraverse_v2_api/blockchain/hardhat.config.ts create mode 100644 vircadia_meraverse_v2_api/blockchain/hardhat_contracts.json create mode 100644 vircadia_meraverse_v2_api/blockchain/interfaces/contractTypes.ts create mode 100644 vircadia_meraverse_v2_api/blockchain/package.json create mode 100644 vircadia_meraverse_v2_api/blockchain/scripts/deploy.ts create mode 100644 vircadia_meraverse_v2_api/blockchain/test/index.ts create mode 100644 vircadia_meraverse_v2_api/blockchain/tsconfig.json create mode 100644 vircadia_meraverse_v2_api/build_scripts/createVersion.js create mode 100644 vircadia_meraverse_v2_api/conf.d/vircadia_conf.conf create mode 100644 vircadia_meraverse_v2_api/conf.d/vircadia_web_conf.conf create mode 100644 vircadia_meraverse_v2_api/docker-compose.yml create mode 100644 vircadia_meraverse_v2_api/docs/.nojekyll create mode 100644 vircadia_meraverse_v2_api/docs/README.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/accounts_accounts_class.Accounts.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/achievement_achievement_class.Achievement.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/achievement_items_achievement_items_class.AchievementItems.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/auth_auth_class.Auth.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/connections_connections_class.Connections.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/current_current_class.Current.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/domains_domains_class.Domains.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/email_email_class.Email.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/friends_friends_class.Friends.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/init_master_data_init_master_data_class.InitMasterData.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/inventory_inventory_item_inventory_item_class.InventoryItem.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/inventory_inventory_transfer_inventory_transfer_class.InventoryTransfer.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/inventory_userInventory_userInventory_class.UserInventory.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/item_handler_item_handler_class.ItemHandler.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/location_location_class.Location.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/media_avatar_avatar_class.Avatar.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/media_file_browser_file_browser_class.FileBrowserService.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/media_static_resource_static_resource_class.StaticResource.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/media_upload_media_upload_media_class.UploadMedia.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/pickup_item_pickup_item_class.PickupItem.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/place_place_class.Place.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/profiles_profiles_class.Profiles.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/quest_apis_npc_npc_class.Npc.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/quest_apis_quest_item_quest_item_class.QuestItem.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/quest_apis_quest_quest_class.Quest.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/reset_password_reset_password_class.ResetPassword.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/reset_user_reset_user_class.ResetUser.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/rewards_reward_class.RewardItem.md create mode 100644 vircadia_meraverse_v2_api/docs/classes/users_users_class.Users.md create mode 100644 vircadia_meraverse_v2_api/docs/modules.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/accounts_accounts_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/achievement_achievement_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/achievement_items_achievement_items_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/auth_auth_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/connections_connections_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/current_current_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/domains_domains_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/email_email_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/friends_friends_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/generate_goobie_generate_goobie_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/init_master_data_init_master_data_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/inventory_inventory_item_inventory_item_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/inventory_inventory_transfer_inventory_transfer_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/inventory_userInventory_userInventory_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/item_handler_item_handler_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/location_location_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/media_avatar_avatar_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/media_file_browser_file_browser_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/media_static_resource_static_resource_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/media_upload_media_upload_media_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/pickup_item_pickup_item_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/place_place_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/profiles_profiles_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/quest_apis_npc_npc_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/quest_apis_quest_item_quest_item_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/quest_apis_quest_quest_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/reset_password_reset_password_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/reset_user_reset_user_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/rewards_reward_class.md create mode 100644 vircadia_meraverse_v2_api/docs/modules/users_users_class.md create mode 100644 vircadia_meraverse_v2_api/goobie/body/0.png create mode 100644 vircadia_meraverse_v2_api/goobie/left_arm/0.png create mode 100644 vircadia_meraverse_v2_api/goobie/left_eye/0.png create mode 100644 vircadia_meraverse_v2_api/goobie/left_pupil/0.png create mode 100644 vircadia_meraverse_v2_api/goobie/mouth/0.png create mode 100644 vircadia_meraverse_v2_api/goobie/right_arm/0.png create mode 100644 vircadia_meraverse_v2_api/goobie/right_eye/0.png create mode 100644 vircadia_meraverse_v2_api/goobie/right_pupil/0.png create mode 100644 vircadia_meraverse_v2_api/jest.config.js create mode 100644 vircadia_meraverse_v2_api/mailtemplates/resetPasswordOtp.html create mode 100644 vircadia_meraverse_v2_api/mailtemplates/verificationEmail.html create mode 100644 vircadia_meraverse_v2_api/master_data/inventory_items.json create mode 100644 vircadia_meraverse_v2_api/master_data/npc.json create mode 100644 vircadia_meraverse_v2_api/master_data/quest_items.json create mode 100644 vircadia_meraverse_v2_api/package.json create mode 100644 vircadia_meraverse_v2_api/public/favicon.ico create mode 100644 vircadia_meraverse_v2_api/public/index.html create mode 100644 vircadia_meraverse_v2_api/public/verificationEmailFailure.html create mode 100644 vircadia_meraverse_v2_api/public/verificationEmailSuccess.html create mode 100644 vircadia_meraverse_v2_api/src/app.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/app.ts create mode 100644 vircadia_meraverse_v2_api/src/appconfig.ts create mode 100644 vircadia_meraverse_v2_api/src/authentication.ts create mode 100644 vircadia_meraverse_v2_api/src/channels.ts create mode 100644 vircadia_meraverse_v2_api/src/common/AccountFields.ts create mode 100644 vircadia_meraverse_v2_api/src/common/DomainFields.ts create mode 100644 vircadia_meraverse_v2_api/src/common/Monitoring/CounterStat.ts create mode 100644 vircadia_meraverse_v2_api/src/common/Monitoring/EventHistogram.ts create mode 100644 vircadia_meraverse_v2_api/src/common/Monitoring/Histogram.ts create mode 100644 vircadia_meraverse_v2_api/src/common/Monitoring/Monitoring.ts create mode 100644 vircadia_meraverse_v2_api/src/common/Monitoring/Stat.ts create mode 100644 vircadia_meraverse_v2_api/src/common/Monitoring/StatsMetaverse.ts create mode 100644 vircadia_meraverse_v2_api/src/common/Monitoring/StatsOs.ts create mode 100644 vircadia_meraverse_v2_api/src/common/Monitoring/StatsServer.ts create mode 100644 vircadia_meraverse_v2_api/src/common/Monitoring/ValueHistogram.ts create mode 100644 vircadia_meraverse_v2_api/src/common/Monitoring/ValueStat.ts create mode 100644 vircadia_meraverse_v2_api/src/common/PlaceFields.ts create mode 100644 vircadia_meraverse_v2_api/src/common/dbservice/DatabaseService.ts create mode 100644 vircadia_meraverse_v2_api/src/common/dbservice/DatabaseServiceOptions.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/AccountInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/Achievement.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/AuthToken.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/AvatarInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/DomainInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/FileContentType.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/Inventoryinterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/ItemHandler.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/MetaverseInfo.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/PlaceInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/QuestInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/QuestItemInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/RequestInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/ResetPasswordInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/RewardInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/UploadAssetInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/npcInterface.ts create mode 100644 vircadia_meraverse_v2_api/src/common/interfaces/storageProvider.ts create mode 100644 vircadia_meraverse_v2_api/src/common/responsebuilder/AchievementBuilder.ts create mode 100644 vircadia_meraverse_v2_api/src/common/responsebuilder/ItemHandlerBuilder.ts create mode 100644 vircadia_meraverse_v2_api/src/common/responsebuilder/accountsBuilder.ts create mode 100644 vircadia_meraverse_v2_api/src/common/responsebuilder/domainsBuilder.ts create mode 100644 vircadia_meraverse_v2_api/src/common/responsebuilder/inventoryBuilder.ts create mode 100644 vircadia_meraverse_v2_api/src/common/responsebuilder/npcBuilder.ts create mode 100644 vircadia_meraverse_v2_api/src/common/responsebuilder/placesBuilder.ts create mode 100644 vircadia_meraverse_v2_api/src/common/responsebuilder/questBuilder.ts create mode 100644 vircadia_meraverse_v2_api/src/common/responsebuilder/questItemBuilder.ts create mode 100644 vircadia_meraverse_v2_api/src/common/responsebuilder/responseBuilder.ts create mode 100644 vircadia_meraverse_v2_api/src/common/sets/Availability.ts create mode 100644 vircadia_meraverse_v2_api/src/common/sets/Maturity.ts create mode 100644 vircadia_meraverse_v2_api/src/common/sets/RequestType.ts create mode 100644 vircadia_meraverse_v2_api/src/common/sets/Restriction.ts create mode 100644 vircadia_meraverse_v2_api/src/common/sets/Roles.ts create mode 100644 vircadia_meraverse_v2_api/src/common/sets/Visibility.ts create mode 100644 vircadia_meraverse_v2_api/src/common/types/crypto-js/index.d.ts create mode 100644 vircadia_meraverse_v2_api/src/common/types/dauria/index.d.ts create mode 100644 vircadia_meraverse_v2_api/src/common/types/feathers-blob/index.d.ts create mode 100644 vircadia_meraverse_v2_api/src/common/types/fs-blob-store/index.d.ts create mode 100644 vircadia_meraverse_v2_api/src/common/types/merge-images/index.d.ts create mode 100644 vircadia_meraverse_v2_api/src/common/types/s3-blob-store/index.d.ts create mode 100644 vircadia_meraverse_v2_api/src/controllers/PublicRoutesController.ts create mode 100644 vircadia_meraverse_v2_api/src/declarations.d.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/addOldToken.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/blockchain/connectToGooTokenContract.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/checkAccess.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/isAdminUser.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/isHasAuthToken.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/media/add-uri-to-file.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/media/create-static-resource.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/media/make-s3-files-public.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/media/reformat-upload-result.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/media/set-loggedin-user-in-body.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/media/set-loggedin-user-in-query.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/media/upload-thumbnail.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/media/validatePresignedRequest.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/requestFail.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/requestSuccess.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/updateLastOnline.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/userPermission.ts create mode 100644 vircadia_meraverse_v2_api/src/hooks/validateSignature.ts create mode 100644 vircadia_meraverse_v2_api/src/index.ts create mode 100644 vircadia_meraverse_v2_api/src/logger.ts create mode 100644 vircadia_meraverse_v2_api/src/middleware/index.ts create mode 100644 vircadia_meraverse_v2_api/src/middleware/processPendingTransaction.ts create mode 100644 vircadia_meraverse_v2_api/src/mongodb.ts create mode 100644 vircadia_meraverse_v2_api/src/routes/publicRoutes.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-field/account-field.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-field/account-field.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-field/account-field.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-field/account-field.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-publickey/account-publickey.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-publickey/account-publickey.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-publickey/account-publickey.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-publickey/account-publickey.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-tokens/account-tokens.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-tokens/account-tokens.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-tokens/account-tokens.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/account-tokens/account-tokens.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/accounts.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/accounts.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/accounts.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/accounts/accounts.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/achievement-items/achievement-items.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/achievement-items/achievement-items.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/achievement-items/achievement-items.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/achievement-items/achivement-item.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/achievement/achievement.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/achievement/achievement.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/achievement/achievement.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/achievement/achivement.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/auth/auth.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/auth/auth.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/auth/auth.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/auth/auth.utils.ts create mode 100644 vircadia_meraverse_v2_api/src/services/connections/connections.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/connections/connections.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/connections/connections.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/connections/connections.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/current/current.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/current/current.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/current/current.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/current/current.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains-fields/domains-field.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains-fields/domains-field.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains-fields/domains-field.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains-fields/domains-field.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains-publickey/domains-publickey.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains-publickey/domains-publickey.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains-publickey/domains-publickey.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains-publickey/domains-publickey.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains_temp/domains-temp.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains_temp/domains-temp.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/domains/domains_temp/domains-temp.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/email/email.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/email/email.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/email/email.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/explore/explore.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/explore/explore.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/explore/explore.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/explore/explore.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/friends/friends.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/friends/friends.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/friends/friends.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/friends/friends.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/index.ts create mode 100644 vircadia_meraverse_v2_api/src/services/init-master-data/init-master-data.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/init-master-data/init-master-data.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/init-master-data/init-master-data.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/inventory-item/inventory-item.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/inventory-item/inventory-item.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/inventory-item/inventory-item.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/inventory-item/inventory-item.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/inventory-transfer/inventory-transfer.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/inventory-transfer/inventory-transfer.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/inventory-transfer/inventory-transfer.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/inventory-transfer/inventory-transfer.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/services.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/userInventory-ordering/userInventory_ordering.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/userInventory-ordering/userInventory_ordering.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/userInventory-ordering/userInventory_ordering.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/userInventory/userInventory.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/userInventory/userInventory.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/userInventory/userInventory.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/inventory/userInventory/userInventory.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/item-handler/item-handler.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/item-handler/item-handler.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/item-handler/item-handler.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/item-handler/item-handler.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/location/location.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/location/location.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/location/location.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/location/location.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/avatar/avatar-helper.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/avatar/avatar.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/avatar/avatar.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/avatar/avatar.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/file-browser/file-browser.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/file-browser/file-browser.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/file-browser/file-browser.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/services.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/static-resource/static-resource.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/static-resource/static-resource.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/static-resource/static-resource.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/storageprovider/getCachedAsset.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/storageprovider/local.storage.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/storageprovider/s3.storage.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/storageprovider/storageprovider.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/upload-media/upload-asset.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/upload-media/upload-asset.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/upload-media/upload-media.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/upload-media/upload-media.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/media/upload-media/upload-media.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/pickup-item/pickup-item.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/pickup-item/pickup-item.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/pickup-item/pickup-item.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/pickup-item/pickup-item.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/place/place.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/place/place.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/place/place.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/place/place.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/place/places-fields/place-field.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/place/places-fields/place-field.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/place/places-fields/place-field.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/place/places-fields/place-field.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/profiles/profiles.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/profiles/profiles.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/profiles/profiles.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/profiles/profiles.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/npc/npc.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/npc/npc.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/npc/npc.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/npc/npc.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/quest-item/quest-item.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/quest-item/quest-item.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/quest-item/quest-item.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/quest-item/quest-item.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/quest/quest.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/quest/quest.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/quest/quest.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/quest/quest.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/quest_apis/services.ts create mode 100644 vircadia_meraverse_v2_api/src/services/reset-password/reset-password.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/reset-password/reset-password.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/reset-password/reset-password.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/reset-password/reset-password.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/reset-user/reset-user.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/reset-user/reset-user.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/reset-user/reset-user.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/rewards/reward.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/rewards/reward.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/rewards/reward.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/rewards/reward.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/send_verification_mail/send_verify_mail.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/send_verification_mail/send_verify_mail.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/send_verification_mail/send_verify_mail.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/send_verification_mail/send_verify_mail.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/category/stat-category.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/category/stat-category.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/category/stat-category.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/category/stat-category.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/list/stat-list.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/list/stat-list.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/list/stat-list.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/services.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/stat/stat.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/stat/stat.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/stat/stat.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/stats/stat/stat.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/strategies/custom-oauth.ts create mode 100644 vircadia_meraverse_v2_api/src/services/strategies/facebook.ts create mode 100644 vircadia_meraverse_v2_api/src/services/strategies/google.ts create mode 100644 vircadia_meraverse_v2_api/src/services/token-transfer/token-transfer.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/token-transfer/token-transfer.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/token-transfer/token-transfer.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/token-transfer/token-transfer.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/users/users.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/users/users.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/users/users.joi.ts create mode 100644 vircadia_meraverse_v2_api/src/services/users/users.service.ts create mode 100644 vircadia_meraverse_v2_api/src/services/verify_user/verify_user.class.ts create mode 100644 vircadia_meraverse_v2_api/src/services/verify_user/verify_user.hooks.ts create mode 100644 vircadia_meraverse_v2_api/src/services/verify_user/verify_user.service.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/CommonKnownContentTypes.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/InventoryUtils.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/Misc.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/Perm.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/Permissions.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/Tokens.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/Utils.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/Validators.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/constants.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/fileUtils.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/get-basic-mimetype.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/mail.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/messages.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/response.ts create mode 100644 vircadia_meraverse_v2_api/src/utils/vTypes.ts create mode 100644 vircadia_meraverse_v2_api/test/authentication.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/accounts.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/achievement-items.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/achievement.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/connections.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/current.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/domains.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/email.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/friends.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/init-master-data.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/inventory-item.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/inventory-transfer.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/item-handler.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/location.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/npc.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/pickup-item.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/place.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/profiles.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/quest-item.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/quest.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/reset-password.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/reset-user.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/reward.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/token-transfer.test.ts create mode 100644 vircadia_meraverse_v2_api/test/services/users.test.ts create mode 100644 vircadia_meraverse_v2_api/tsconfig.json create mode 100644 vircadia_meraverse_v2_api/types.d.ts diff --git a/Metaverse_v2 b/Metaverse_v2 deleted file mode 160000 index ae54efaa..00000000 --- a/Metaverse_v2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ae54efaae6f96548bf24bb177aa743d59cc441f9 diff --git a/vircadia_meraverse_v2 b/vircadia_meraverse_v2 deleted file mode 160000 index ae54efaa..00000000 --- a/vircadia_meraverse_v2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit ae54efaae6f96548bf24bb177aa743d59cc441f9 diff --git a/vircadia_meraverse_v2_api/.dockerignore b/vircadia_meraverse_v2_api/.dockerignore new file mode 100644 index 00000000..fa171076 --- /dev/null +++ b/vircadia_meraverse_v2_api/.dockerignore @@ -0,0 +1,4 @@ +./node_module +Dockerfile +.dokerignore +docker-compose.yml \ No newline at end of file diff --git a/vircadia_meraverse_v2_api/.editorconfig b/vircadia_meraverse_v2_api/.editorconfig new file mode 100644 index 00000000..e94c68c6 --- /dev/null +++ b/vircadia_meraverse_v2_api/.editorconfig @@ -0,0 +1,13 @@ +# http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +end_of_line = crlf +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/vircadia_meraverse_v2_api/.env.local.default b/vircadia_meraverse_v2_api/.env.local.default new file mode 100644 index 00000000..a63d42d9 --- /dev/null +++ b/vircadia_meraverse_v2_api/.env.local.default @@ -0,0 +1,93 @@ +# DB variables ------------------- +DB_HOST=mongodb_container +DB_PORT=27017 +DB_NAME=tester +DB_USER=metaverse +DB_PASSWORD=nooneknowsit +DB_AUTHDB=admin +DATABASE_URL= + +# Authentication variables ------------------- +AUTH_SECRET=M7iszvSxttilkptn22GT4/NbFGY= + +# Server variables --------------- +SERVER_HOST=staging-2.digisomni.com +SERVER_PORT=3030 +SERVER_VERSION=1.1.1-20200101-abcdefg + +# General ------------------------ +LOCAL=true +#development and production +APP_ENV=development +PUBLIC_PATH=./public + +# Metaverse ------------------------ +METAVERSE_NAME=Vircadia noobie +METAVERSE_NICK_NAME=Noobie +METAVERSE_SERVER_URL=https://staging-2.digisomni.com:3030 +DEFAULT_ICE_SERVER_URL= +DASHBOARD_URL=https://dashboard.vircadia.com +LISTEN_HOST=0.0.0.0 + +LISTEN_PORT=3030 +ENABLE_ACCOUNT_VERIFICATION=false +ENABLE_ACCOUNT_VERIFICATION=true +START_LEVEL=1 + +# Client variables --------------- +APP_TITLE=Vircadia noobie +APP_LOGO=https://staging-2.digisomni.com/img/logo-1.png +APP_URL=https://staging-2.digisomni.com +APP_HOST=localhost +APP_PORT=80 + +# - Route 53 +ROUTE53_HOSTED_ZONE_ID= +ROUTE53_ACCESS_KEY_ID= +ROUTE53_ACCESS_KEY_SECRET= + +# - keys +STORAGE_AWS_ACCESS_KEY_ID=K3FVHPY6P0BMRN4A0N61 +STORAGE_AWS_ACCESS_KEY_SECRET=DtsC5maSDHdhgC9uzb6HaRXE7D2XRfodXayQj7MF +# -------------------------------- + +# - S3 +STORAGE_S3_REGION=eu-central-1 +STORAGE_S3_STATIC_RESOURCE_BUCKET=digisomni-frankfurt-1 +STORAGE_S3_AVATAR_DIRECTORY=avatars +AWS_STORAGE_PROVIDER=digisomni-frankfurt-1.eu-central-1.linodeobjects.com/digisomni-frankfurt-1/ +# Possible values: +# local - for local development, +# dev - for live development environment, +# - for production environment, +STORAGE_S3_DEV_MODE=local + + +# - Storage provider aws or local +STORAGE_PROVIDER=aws +LOCAL_STORAGE_PROVIDER=localhost:8642 +LOCAL_STORAGE_PROVIDER_PORT=8642 + +# - Email +SMTP_HOST=smtp.gmail.com +SMTP_PORT=465 +SMTP_SECURE=true +SMTP_USER=khilan.odan@gmail.com +SMTP_PASS=blackhawk143 +SMTP_EMAIL_FROM=khilan.odan@gmail.com + +# - Google Authentication Service +GOOGLE_CALLBACK_URL=https://staging-2.digisomni.com/#/auth/oauth/google +GOOGLE_CLIENT_ID=1070552537553-2oii1k8dli26a2f56hd54cu90gl7qhf5.apps.googleusercontent.com +GOOGLE_CLIENT_SECRET=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 + +# - Faceebook Authentication Service +FACEBOOK_CALLBACK_URL=https://staging-2.digisomni.com/#/auth/oauth/facebook +FACEBOOK_CLIENT_ID=233508642323081 +FACEBOOK_CLIENT_SECRET=ccb44cf012aaaf7abaf9efd1d35164e9 + +# - Blockchain +#Hardhat test account private key - replace with a real one +MINTER_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 +#Replace with real Infura node URL +ETH_RPC_URL="https://rinkeby.infura.io/v3/460f40a260564ac4a4f4b3fffb032dad" \ No newline at end of file diff --git a/vircadia_meraverse_v2_api/.eslintignore b/vircadia_meraverse_v2_api/.eslintignore new file mode 100644 index 00000000..96f476e1 --- /dev/null +++ b/vircadia_meraverse_v2_api/.eslintignore @@ -0,0 +1 @@ +blockchain \ No newline at end of file diff --git a/vircadia_meraverse_v2_api/.eslintrc.json b/vircadia_meraverse_v2_api/.eslintrc.json new file mode 100644 index 00000000..1422e6e0 --- /dev/null +++ b/vircadia_meraverse_v2_api/.eslintrc.json @@ -0,0 +1,24 @@ +{ + "root": true, + "env": { + "es6": true, + "node": true, + "jest": true + }, + "parserOptions": { + "parser": "@typescript-eslint/parser", + "ecmaVersion": 2018, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "extends": ["plugin:@typescript-eslint/recommended"], + "rules": { + "indent": ["error", 4, { "SwitchCase": 1 }], + "linebreak-style": ["error", "unix"], + "quotes": ["error", "single"], + "semi": ["error", "always"], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-interface": "off" + } +} + diff --git a/vircadia_meraverse_v2_api/.gitignore b/vircadia_meraverse_v2_api/.gitignore new file mode 100644 index 00000000..daedc7a4 --- /dev/null +++ b/vircadia_meraverse_v2_api/.gitignore @@ -0,0 +1,116 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# Commenting this out is preferred by some people, see +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- +node_modules + +# Users Environment Variables +.lock-wscript + +# IDEs and editors (shamelessly copied from @angular/cli's .gitignore) +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Others +lib/ +data/ + +.env.local +package-lock.json +goobie/temp/ \ No newline at end of file diff --git a/vircadia_meraverse_v2_api/.prettierrc b/vircadia_meraverse_v2_api/.prettierrc new file mode 100644 index 00000000..5e7c5a58 --- /dev/null +++ b/vircadia_meraverse_v2_api/.prettierrc @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/vircadia_meraverse_v2_api/.vscode/settings.json b/vircadia_meraverse_v2_api/.vscode/settings.json new file mode 100644 index 00000000..e58b7095 --- /dev/null +++ b/vircadia_meraverse_v2_api/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "prettier.singleQuote": true, + "files.insertFinalNewline": false +} + diff --git a/vircadia_meraverse_v2_api/Dockerfile b/vircadia_meraverse_v2_api/Dockerfile new file mode 100644 index 00000000..b6837f52 --- /dev/null +++ b/vircadia_meraverse_v2_api/Dockerfile @@ -0,0 +1,20 @@ +FROM ubuntu:latest +ENV DEBIAN_FRONTEND noninteractive +WORKDIR /usr/src/api +COPY package*.json ./ +RUN apt-get update -yq \ + && apt-get install curl gnupg -yq \ + && curl -sL https://deb.nodesource.com/setup_16.x | bash \ + && apt-get install nodejs -yq + + + +RUN apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev + +RUN npm install +RUN npm update +COPY ./ ./ + +#CMD ["npm","start"] +CMD ["npm","run","start"] +#CMD npm run dev diff --git a/vircadia_meraverse_v2_api/README.md b/vircadia_meraverse_v2_api/README.md new file mode 100644 index 00000000..50cfa0b0 --- /dev/null +++ b/vircadia_meraverse_v2_api/README.md @@ -0,0 +1,85 @@ +# vircadia-metaverse-v2 + +## Docker MAC + +- docker compose build +- docker compose up + OR +- docker compose up --scale api=10 + +## Docker Ubuntu + +- docker-compose build +- docker-compose up -d //() + OR +- docker-compose up --scale api=10 + +## Load Init master data + +- Using this API, load master data into the database (Quest item, Inventory Items, Npc etc...) + +``` +https://{apiUrl}/init-master-data + +``` + +-Remove cached images + +``` +docker rmi $(docker images --filter "dangling=true" -q --no-trunc) +``` + +## About + +This project uses [Feathers](http://feathersjs.com). An open source web framework for building modern real-time applications. + +## Getting Started + +Getting up and running is as easy as 1, 2, 3. + +1. Make sure you have [NodeJS](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed. +2. Install your dependencies + + ``` + cd path/to/vircadia-metaverse-v2 + npm install + cd path/to/vircadia-metaverse-v2/blockchain + npm install + ``` + +3. Start your app + + ``` + npm start + ``` + +## Testing + +Spin up a test blockchain: + +`npm run localchain` + +Then run `npm test` and all your tests in the `test/` directory will be run. + +## Scaffolding + +Feathers has a powerful command line interface. Here are a few things it can do: + +``` +$ npm install -g @feathersjs/cli # Install Feathers CLI + +$ feathers generate service # Generate a new Service +$ feathers generate hook # Generate a new Hook +$ feathers help # Show all commands +``` + +## Help + +For more information on all the things you can do with Feathers visit [docs.feathersjs.com](http://docs.feathersjs.com). + +## Generate Doc + +``` +npx typedoc expand ./src/services/**/*.class.ts +``` + diff --git a/vircadia_meraverse_v2_api/blockchain/.eslintignore b/vircadia_meraverse_v2_api/blockchain/.eslintignore new file mode 100644 index 00000000..14b0cc0a --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/.eslintignore @@ -0,0 +1,4 @@ +node_modules +artifacts +cache +coverage diff --git a/vircadia_meraverse_v2_api/blockchain/.eslintrc.js b/vircadia_meraverse_v2_api/blockchain/.eslintrc.js new file mode 100644 index 00000000..da84a8d8 --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/.eslintrc.js @@ -0,0 +1,24 @@ +module.exports = { + env: { + browser: false, + es2021: true, + mocha: true, + node: true, + }, + plugins: ["@typescript-eslint"], + extends: [ + "standard", + "plugin:prettier/recommended", + "plugin:node/recommended", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + ecmaVersion: 12, + }, + rules: { + "node/no-unsupported-features/es-syntax": [ + "error", + { ignores: ["modules"] }, + ], + }, +}; diff --git a/vircadia_meraverse_v2_api/blockchain/.gitignore b/vircadia_meraverse_v2_api/blockchain/.gitignore new file mode 100644 index 00000000..045eb76f --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/.gitignore @@ -0,0 +1,10 @@ +node_modules +.env +coverage +coverage.json +typechain + +#Hardhat files +cache +artifacts +deployments diff --git a/vircadia_meraverse_v2_api/blockchain/.npmignore b/vircadia_meraverse_v2_api/blockchain/.npmignore new file mode 100644 index 00000000..524e4871 --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/.npmignore @@ -0,0 +1,3 @@ +hardhat.config.ts +scripts +test diff --git a/vircadia_meraverse_v2_api/blockchain/.prettierignore b/vircadia_meraverse_v2_api/blockchain/.prettierignore new file mode 100644 index 00000000..f95a8d14 --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/.prettierignore @@ -0,0 +1,5 @@ +node_modules +artifacts +cache +coverage* +gasReporterOutput.json diff --git a/vircadia_meraverse_v2_api/blockchain/.prettierrc b/vircadia_meraverse_v2_api/blockchain/.prettierrc new file mode 100644 index 00000000..69a88e3b --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/vircadia_meraverse_v2_api/blockchain/.solhint.json b/vircadia_meraverse_v2_api/blockchain/.solhint.json new file mode 100644 index 00000000..a3ea1d0a --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/.solhint.json @@ -0,0 +1,7 @@ +{ + "extends": "solhint:recommended", + "rules": { + "compiler-version": ["error", "^0.8.0"], + "func-visibility": ["warn", { "ignoreConstructors": true }] + } +} diff --git a/vircadia_meraverse_v2_api/blockchain/.solhintignore b/vircadia_meraverse_v2_api/blockchain/.solhintignore new file mode 100644 index 00000000..08b25532 --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/.solhintignore @@ -0,0 +1 @@ +node_modules diff --git a/vircadia_meraverse_v2_api/blockchain/README.md b/vircadia_meraverse_v2_api/blockchain/README.md new file mode 100644 index 00000000..7ea69d6e --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/README.md @@ -0,0 +1,46 @@ +# Advanced Sample Hardhat Project + +This project demonstrates an advanced Hardhat use case, integrating other tools commonly used alongside Hardhat in the ecosystem. + +The project comes with a sample contract, a test for that contract, a sample script that deploys that contract, and an example of a task implementation, which simply lists the available accounts. It also comes with a variety of other tools, preconfigured to work with the project code. + +Try running some of the following tasks: + +```shell +npx hardhat accounts +npx hardhat compile +npx hardhat clean +npx hardhat test +npx hardhat node +npx hardhat help +REPORT_GAS=true npx hardhat test +npx hardhat coverage +npx hardhat run scripts/deploy.ts +TS_NODE_FILES=true npx ts-node scripts/deploy.ts +npx eslint '**/*.{js,ts}' +npx eslint '**/*.{js,ts}' --fix +npx prettier '**/*.{json,sol,md}' --check +npx prettier '**/*.{json,sol,md}' --write +npx solhint 'contracts/**/*.sol' +npx solhint 'contracts/**/*.sol' --fix +``` + +# Etherscan verification + +To try out Etherscan verification, you first need to deploy a contract to an Ethereum network that's supported by Etherscan, such as Ropsten. + +In this project, copy the .env.example file to a file named .env, and then edit it to fill in the details. Enter your Etherscan API key, your Ropsten node URL (eg from Alchemy), and the private key of the account which will send the deployment transaction. With a valid .env file in place, first deploy your contract: + +```shell +hardhat run --network ropsten scripts/deploy.ts +``` + +Then, copy the deployment address and paste it in to replace `DEPLOYED_CONTRACT_ADDRESS` in this command: + +```shell +npx hardhat verify --network ropsten DEPLOYED_CONTRACT_ADDRESS "Hello, Hardhat!" +``` + +# Performance optimizations + +For faster runs of your tests and scripts, consider skipping ts-node's type checking by setting the environment variable `TS_NODE_TRANSPILE_ONLY` to `1` in hardhat's environment. For more details see [the documentation](https://hardhat.org/guides/typescript.html#performance-optimizations). diff --git a/vircadia_meraverse_v2_api/blockchain/contracts/GooERC20.sol b/vircadia_meraverse_v2_api/blockchain/contracts/GooERC20.sol new file mode 100644 index 00000000..d5542803 --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/contracts/GooERC20.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20PermitUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +contract GooERC20 is + Initializable, + ERC20Upgradeable, + ERC20BurnableUpgradeable, + PausableUpgradeable, + AccessControlUpgradeable, + ERC20PermitUpgradeable, + UUPSUpgradeable +{ + bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + + constructor() initializer { + __ERC20_init("GoobieverseToken", "GOO"); + __ERC20Burnable_init(); + __Pausable_init(); + __AccessControl_init(); + __ERC20Permit_init("GoobieverseToken"); + __UUPSUpgradeable_init(); + + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + _grantRole(PAUSER_ROLE, msg.sender); + _grantRole(MINTER_ROLE, msg.sender); + _grantRole(UPGRADER_ROLE, msg.sender); + } + + function pause() public onlyRole(PAUSER_ROLE) { + _pause(); + } + + function unpause() public onlyRole(PAUSER_ROLE) { + _unpause(); + } + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { + _mint(to, amount); + } + + function _beforeTokenTransfer( + address from, + address to, + uint256 amount + ) internal override whenNotPaused { + super._beforeTokenTransfer(from, to, amount); + } + + function _authorizeUpgrade(address newImplementation) + internal + override + onlyRole(UPGRADER_ROLE) + {} +} diff --git a/vircadia_meraverse_v2_api/blockchain/deploy/00_deploy_contracts.ts b/vircadia_meraverse_v2_api/blockchain/deploy/00_deploy_contracts.ts new file mode 100644 index 00000000..33c0334c --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/deploy/00_deploy_contracts.ts @@ -0,0 +1,19 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const { deployments, getNamedAccounts, getChainId } = hre; + const { deploy } = deployments; + + const { deployer } = await getNamedAccounts(); + console.log("Deployer:", deployer); + console.log("Chain:", await getChainId()); + + await deploy("GooERC20", { + from: deployer, + gasLimit: 4000000, + log: true, + }); +}; +export default func; + diff --git a/vircadia_meraverse_v2_api/blockchain/hardhat.config.ts b/vircadia_meraverse_v2_api/blockchain/hardhat.config.ts new file mode 100644 index 00000000..bda9647a --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/hardhat.config.ts @@ -0,0 +1,59 @@ +import * as dotenv from "dotenv"; + +import { HardhatUserConfig, task } from "hardhat/config"; +import "@nomiclabs/hardhat-etherscan"; +import "@nomiclabs/hardhat-waffle"; +import "@typechain/hardhat"; +import "hardhat-deploy"; +import "hardhat-gas-reporter"; +import "solidity-coverage"; + +dotenv.config(); + +// You need to export an object to set up your config +// Go to https://hardhat.org/config/ to learn more + +const config: HardhatUserConfig = { + namedAccounts: { + deployer: { + default: 0, + }, + }, + solidity: { + version: "0.8.4", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + paths: { + deploy: "deploy", + deployments: "deployments", + imports: "imports", + }, + networks: { + hardhat: {}, + localhost: { + url: "http://localhost:8545", + }, + ropsten: { + url: process.env.ROPSTEN_URL || "", + accounts: + process.env.PRIVATE_KEY !== undefined + ? [process.env.PRIVATE_KEY] + : [], + }, + }, + gasReporter: { + enabled: process.env.REPORT_GAS !== undefined, + currency: "USD", + }, + etherscan: { + apiKey: process.env.ETHERSCAN_API_KEY, + }, +}; + +export default config; + diff --git a/vircadia_meraverse_v2_api/blockchain/hardhat_contracts.json b/vircadia_meraverse_v2_api/blockchain/hardhat_contracts.json new file mode 100644 index 00000000..d50d0734 --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/hardhat_contracts.json @@ -0,0 +1,812 @@ +{ + "31337": [ + { + "name": "localhost", + "chainId": "31337", + "contracts": { + "GooERC20": { + "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MINTER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PAUSER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UPGRADER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burnFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/vircadia_meraverse_v2_api/blockchain/interfaces/contractTypes.ts b/vircadia_meraverse_v2_api/blockchain/interfaces/contractTypes.ts new file mode 100644 index 00000000..235aae7e --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/interfaces/contractTypes.ts @@ -0,0 +1,94 @@ +/** + * #### Summary + * Describes the structure of each contract in hardhat_contracts.json + */ +export type TBasicContractData = { + address: string; + abi?: any[]; +}; + +export type TBasicContractDataConfig = { + [chainId: number]: { + chainId: number; + address: string; + }; +}; + +/** + * #### Summary + * Contracts by contract name + * - A record of contract names and their hardhat contract json + * - includes chain id + */ +export type THardhatContractDataRecord = { + [contractName: string]: { + config: TBasicContractDataConfig; + abi: any[]; + }; +}; + +/** + * #### Summary + * Contracts by contract name + * - A record of contract names and their hardhat contract json + * - includes chain id + */ +export type TExternalContractDataRecord = { + [contractName: string]: { + config: TBasicContractDataConfig; + }; +}; + +/** + * #### Summary + * Describes the structure of hardhat_contracts.json + * - {chainIds: { networkNames: {contracts} }}, contains an record of contracts + * - Used by {@link useContractLoader} + * + * @category Models + */ +export type TDeployedHardhatContractsJson = { + [chainId: string]: { + [networkName: string]: { + name: string; + chainId: string; + contracts: { + [contractName: string]: { + address: string; + abi?: any[]; + }; + }; + }; + }; +}; + +/** + * {chainId: {contract: address}}, contains an record of contracts + * #### Summary + * A type for external contracts + * - it is a record of contract names and their deployed address + * - this data type is used by {@link ContractsAppContext} to connect to external contracts + * + * @category Models + */ +export type TExternalContractsAddressMap = { + [chainId: number]: { + [contractName: string]: string; + }; +}; + +/** + * #### Summary + * Contract function information: + * - contractName + * - functionname + * - functionArgs: functionArguments, an array + * + * @category Models + */ +export type TContractFunctionInfo = { + contractName: string; + functionName: string; + functionArgs?: any[]; +}; + diff --git a/vircadia_meraverse_v2_api/blockchain/package.json b/vircadia_meraverse_v2_api/blockchain/package.json new file mode 100644 index 00000000..c724b464 --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/package.json @@ -0,0 +1,49 @@ +{ + "name": "goobieverse-blockchain", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "test", + "chain": "hardhat node --export-all hardhat_contracts.json" + }, + "author": "", + "license": "MIT", + "devDependencies": { + "@nomiclabs/hardhat-ethers": "^2.0.5", + "@nomiclabs/hardhat-etherscan": "^3.0.3", + "@nomiclabs/hardhat-waffle": "^2.0.3", + "@typechain/ethers-v5": "^7.2.0", + "@typechain/hardhat": "^2.3.1", + "@types/chai": "^4.3.0", + "@types/mocha": "^9.1.0", + "@types/node": "^12.20.47", + "@typescript-eslint/eslint-plugin": "^4.33.0", + "@typescript-eslint/parser": "^4.33.0", + "chai": "^4.3.6", + "dotenv": "^10.0.0", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.5.0", + "eslint-config-standard": "^16.0.3", + "eslint-plugin-import": "^2.25.4", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-promise": "^5.2.0", + "ethereum-waffle": "^3.4.4", + "ethers": "^5.6.2", + "hardhat": "^2.9.2", + "hardhat-deploy": "^0.11.4", + "hardhat-gas-reporter": "^1.0.8", + "prettier": "^2.6.1", + "prettier-plugin-solidity": "^1.0.0-beta.19", + "solhint": "^3.3.7", + "solidity-coverage": "^0.7.20", + "ts-node": "^10.7.0", + "typechain": "^5.2.0", + "typescript": "^4.6.3" + }, + "dependencies": { + "@openzeppelin/contracts-upgradeable": "^4.5.2" + } +} + diff --git a/vircadia_meraverse_v2_api/blockchain/scripts/deploy.ts b/vircadia_meraverse_v2_api/blockchain/scripts/deploy.ts new file mode 100644 index 00000000..ef11cfff --- /dev/null +++ b/vircadia_meraverse_v2_api/blockchain/scripts/deploy.ts @@ -0,0 +1,31 @@ +// We require the Hardhat Runtime Environment explicitly here. This is optional +// but useful for running the script in a standalone fashion through `node + Vircadia Domain Token Fetch Login + + + + + + + + diff --git a/vircadia_metaverse_v2_api/mailtemplates/domaintoken.js b/vircadia_metaverse_v2_api/mailtemplates/domaintoken.js new file mode 100644 index 00000000..a9c243cb --- /dev/null +++ b/vircadia_metaverse_v2_api/mailtemplates/domaintoken.js @@ -0,0 +1,266 @@ + +let API_ACCOUNT_LOGIN = '/oauth/token'; +let API_ACCOUNT_CREATE = '/api/v1/users'; +let API_GET_TOKEN = '/api/v1/token/new'; + +// Configuration info fetched from './config.json' +let Config = undefined; + +// Function run when the page has been loaded +document.addEventListener('DOMContentLoaded', function(evnt) { + // Read local configuration file generated by the server + FetchConfigInfo() + .then( configInfo => { + Config = configInfo; + }) + .catch( err => { + Logg(`Fetch of configuration info failed: ${err}`); + }); + + // All elements with class 'clickable' calls a function based on 'op' attribute + Array.from(document.getElementsByClassName('clickable')).forEach( nn => { + nn.addEventListener('click', evnt => { + let buttonOp = evnt.target.getAttribute('op'); + if (typeof ClickableOps[buttonOp] == 'function') { + ClickableOps[buttonOp](evnt.target); + } + }); + }); +}); + +// Return the url to the metaverse-server. +// If it cannot be extracted from the configuration file, return an empty +// string which will rely on local URL access rules. +function ServerURL() { + return (typeof(Config) === 'undefined') ? '' : Config['metaverse']['metaverse-server-url']; +}; + +let ClickableOps = { + 'createAccount': OpCreateAccount, + 'getDomainToken': OpGetDomainToken +}; +// User clicked on "Create Account" button. Read fields and try to create. +function OpCreateAccount(evnt) { + let username = document.getElementById('v-new-username').value.trim(); + let passwd = document.getElementById('v-new-password').value.trim(); + let useremail = document.getElementById('v-new-email').value.trim(); + + // Logg('username=' + username + ', email=' + useremail); + + if (username.length < 1 || passwd.length < 2 || useremail.length < 5) { + DisplayAccountError('You must specify a value for all three account fields'); + return; + } + + CreateUserAccount(username, passwd, useremail) + .then( () => { + GetUserAccessToken(username, passwd) + .then( accountTokenInfo => { + GetDomainToken(accountTokenInfo) + .then( domainToken => { + DisplayDomainToken(domainToken); + }) + .catch ( err => { + DisplayError('Could not fetch domain token: ' + err); + }); + }) + .catch ( err => { + DisplayError('Could not fetch account token: ' + err); + }); + }) + .catch ( err => { + DisplayError('Could not create account: ' + err); + }); +}; +// User clicked on "Create Domain Token". Get fields and fetch the token +function OpGetDomainToken(evnt) { + let username = document.getElementById('v-username').value.trim(); + let passwd = document.getElementById('v-password').value.trim(); + + if (username.length < 1 || passwd.length < 2) { + DisplayAccountError('You must specify a value for both username and password'); + return; + } + + GetDomainTokenWithAccount(username, passwd) + .then( domainToken => { + DisplayDomainToken(domainToken); + }) + .catch ( err => { + DisplayError('Could not fetch domain token: ' + err); + }); +}; +// Use account information to get account token and use that to get domain token +function GetDomainTokenWithAccount(pUsername, pPassword) { + return new Promise( (resolve, reject) => { + GetUserAccessToken(pUsername, pPassword) + .then( accountTokenInfo => { + GetDomainToken(accountTokenInfo) + .then( domainToken => { + Logg('Successful domain token creation'); + resolve(domainToken); + }) + .catch ( err => { + reject('Could not fetch domain token: ' + err); + }); + }) + .catch ( err => { + reject('Could not fetch account access token: ' + err); + }); + }); +}; +// Return a Promise that returns an access token for the specified account. +// The returned 'token' has multiple fields describing the token, it's type, ... +function GetUserAccessToken(pUsername, pPassword) { + return new Promise( (resolve , reject) => { + let queries = []; + queries.push(encodeURIComponent('grant_type') + '=' + encodeURIComponent('password')); + queries.push(encodeURIComponent('username') + '=' + encodeURIComponent(pUsername)); + queries.push(encodeURIComponent('password') + '=' + encodeURIComponent(pPassword)); + queries.push(encodeURIComponent('scope') + '=' + encodeURIComponent('owner')); + let queryData = queries.join('&').replace(/%20/g, '+'); + + let request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (this.readyState === XMLHttpRequest.DONE) { + if (this.status === 200) { + // Successful account access. Token in the returned JSON body. + Logg("Successful fetch of user access token"); + let response = JSON.parse(request.responseText); + resolve( { + 'token': response.access_token, + 'token_type': response.token_type, + 'scope': response.scope, + 'refresh_token': response.refresh_token + }); + } + else { + // Error doing the login + reject("Account login failed"); + } + } + }; + Logg("Starting fetch of user access token for user " + pUsername); + request.open("POST", ServerURL() + API_ACCOUNT_LOGIN); + request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); + request.send(queryData); + }); +}; +// Return a Promise that returns an access token for a domain +function GetDomainToken(pAccountTokenInfo) { + return new Promise( (resolve, reject) => { + let request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (this.readyState === XMLHttpRequest.DONE) { + if (this.status === 200) { + let response = JSON.parse(request.responseText); + if (response.status && response.status === 'success') { + // Successful fetch of new domain token using account token + Logg('Successful fetch of domain token'); + resolve(response.data.token); + } + else { + reject('Fetch of domain token failed: ' + JSON.stringify(response.data)); + } + } + else { + reject('Domain token fetch failed'); + } + } + }; + Logg('Starting fetch of domain token'); + request.open('POST', ServerURL() + API_GET_TOKEN + "?scope=domain"); + request.setRequestHeader('Authorization', + pAccountTokenInfo.token_type + ' ' + pAccountTokenInfo.token); + request.send(); + }); +}; +// Create a new user account. Does not return anything +function CreateUserAccount(pUsername, pPassword, pEmail) { + return new Promise( (resolve , reject) => { + Logg('Starting account creation request for ' + pUsername); + let requestData = JSON.stringify({ + 'user': { + 'username': pUsername, + 'password': pPassword, + 'email': pEmail + } + }); + + let request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (this.readyState === XMLHttpRequest.DONE) { + if (this.status === 200) { + let response = JSON.parse(request.responseText); + if (response.status && response.status == 'success') { + // Successful account creation + Logg('Successful account creation'); + resolve(); + } + else { + if (response.data) { + reject('Account creation failed: ' + JSON.stringify(response.data)); + } + else { + reject('Account creation failed'); + } + } + } + } + }; + request.open('POST', ServerURL() + API_ACCOUNT_CREATE); + request.setRequestHeader('Content-type', 'application/json'); + request.send(requestData); + }); +}; +// Return a Promise that resolves to the contents of the configuration file +function FetchConfigInfo() { + return new Promise( (resolve, reject) => { + let request = new XMLHttpRequest(); + request.onreadystatechange = function() { + if (this.readyState === XMLHttpRequest.DONE) { + if (this.status === 200) { + resolve(JSON.parse(request.responseText)); + } + else { + reject('Domain token fetch failed'); + } + }; + }; + Logg('Starting fetch configuration information'); + request.open('GET', './config.json'); + request.send(); + }); +}; +// Display the fetched domain token in the domain display area +function DisplayDomainToken(pToken) { + let domainArea = document.getElementById('v-token-results'); + domainArea.innerHTML = ''; + let message = document.createTextNode('Domain token = ' + pToken); + domainArea.appendChild(message); +}; +// Display an error message in the domain display area +function DisplayError(pMsg) { + let domainArea = document.getElementById('v-token-results'); + domainArea.innerHTML = ''; + let div = document.createElement('div'); + div.setAttribute('class', 'v-errorText'); + let message = document.createTextNode('ERROR: ' + pMsg); + div.appendChild(message); + domainArea.appendChild(div); +}; +// Put lines of messages into the 'DEBUGG' section of the document +function Logg(msg, classs) { + let debugg = document.getElementById('DEBUGG'); + if (debugg) { + let newline = document.createElement('div'); + newline.appendChild(document.createTextNode(msg)); + if (classs) { + newline.setAttribute('class', classs); + } + debugg.appendChild(newline); + if (debugg.childElementCount > 10) { + debugg.removeChild(debugg.firstChild); + } + } +}; diff --git a/vircadia_metaverse_v2_api/package.json b/vircadia_metaverse_v2_api/package.json index eb4a0af6..fb28eabb 100644 --- a/vircadia_metaverse_v2_api/package.json +++ b/vircadia_metaverse_v2_api/package.json @@ -46,7 +46,7 @@ "@feathersjs/authentication-local": "^4.5.11", "@feathersjs/authentication-oauth": "^4.5.11", "@feathersjs/configuration": "^4.5.11", - "@feathersjs/errors": "^4.5.12", + "@feathersjs/errors": "^4.5.15", "@feathersjs/express": "^4.5.12", "@feathersjs/feathers": "^4.5.12", "@feathersjs/socketio": "^4.5.13", @@ -77,12 +77,14 @@ "glob": "^7.2.0", "hardhat": "^2.9.3", "helmet": "^4.6.0", + "ioredis": "^5.0.6", "merge-images": "^2.0.0", "moment": "^2.29.1", "mongodb": "^4.2.2", "mongodb-core": "^3.2.7", "multer": "^1.4.4", "nodemailer-smtp-transport": "^2.4.2", + "redis-om": "^0.3.4", "replace-color": "^2.3.0", "s3-blob-store": "^4.1.1", "serve-favicon": "^2.5.0", diff --git a/vircadia_metaverse_v2_api/src/app.hooks.ts b/vircadia_metaverse_v2_api/src/app.hooks.ts index 4ee42c41..79f10f91 100644 --- a/vircadia_metaverse_v2_api/src/app.hooks.ts +++ b/vircadia_metaverse_v2_api/src/app.hooks.ts @@ -14,8 +14,10 @@ 'use strict'; +import addOldToken from './hooks/addOldToken'; // Application hooks that run for every service // Don't remove this comment. It's needed to format import lines nicely. +import storeAuthTokenInRedis from './hooks/storeAuthTokenInRedis'; export default { before: { @@ -29,10 +31,10 @@ export default { }, after: { - all: [], + all: [storeAuthTokenInRedis()], find: [], get: [], - create: [], + create: [addOldToken()], update: [], patch: [], remove: [], diff --git a/vircadia_metaverse_v2_api/src/app.ts b/vircadia_metaverse_v2_api/src/app.ts index ce433302..a15aebba 100644 --- a/vircadia_metaverse_v2_api/src/app.ts +++ b/vircadia_metaverse_v2_api/src/app.ts @@ -1,90 +1,94 @@ -// Copyright 2020 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// @ts-nocheck - may need to be at the start of file - -import path from 'path'; -import favicon from 'serve-favicon'; -import compress from 'compression'; -import helmet from 'helmet'; -import cors from 'cors'; - -import feathers from '@feathersjs/feathers'; -import express from '@feathersjs/express'; -// import socketio from '@feathersjs/socketio'; -import { publicRoutes } from './routes/publicRoutes'; -import config from './appconfig'; -import { Application } from './declarations'; -import logger from './logger'; -import middleware from './middleware'; -import services from './services'; -import appHooks from './app.hooks'; -import channels from './channels'; -import { HookContext as FeathersHookContext } from '@feathersjs/feathers'; -import authentication from './authentication'; -import mongodb from './mongodb'; - -// Don't remove this comment. It's needed to format import lines nicely. - -const app: Application = express(feathers()); -export type HookContext = { - app: Application; -} & FeathersHookContext; - -// Enable security, CORS, compression, favicon and body parsing -app.use( - helmet({ - contentSecurityPolicy: false, - }) -); -app.use(cors()); -app.use(compress()); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); -app.set('host', config.server.hostName); -app.set('port', config.server.port); -app.set('paginate', config.server.paginate); -app.set('authentication', config.authentication); -//set Public folder path -app.set('public', config.server.publicPath); - -//page favicon -app.use(favicon(path.join(app.get('public'), 'favicon.ico'))); - -// routes -app.use('/', publicRoutes); -// Host the public folder -app.use('/', express.static(app.get('public'))); - -// Set up Plugins and providers -app.configure(express.rest()); -// app.configure(socketio()); - -app.configure(mongodb); - -// Configure other middleware (see `middleware/index.ts`) -app.configure(middleware); -app.configure(authentication); -// Set up our services (see `services/index.ts`) -app.configure(services); -// Set up event channels (see channels.ts) -app.configure(channels); - -// Configure a middleware for 404s and the error handler -app.use(express.notFound()); -app.use(express.errorHandler({ logger } as any)); - -app.hooks(appHooks); - -export default app; \ No newline at end of file +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// @ts-nocheck - may need to be at the start of file + +import path from 'path'; +import favicon from 'serve-favicon'; +import compress from 'compression'; +import helmet from 'helmet'; +import cors from 'cors'; + +import feathers from '@feathersjs/feathers'; +import express from '@feathersjs/express'; +// import socketio from '@feathersjs/socketio'; +import { publicRoutes } from './routes/publicRoutes'; +import config from './appconfig'; +import { Application } from './declarations'; +import logger from './logger'; +import middleware from './middleware'; +import services from './services'; +import appHooks from './app.hooks'; +import channels from './channels'; +import { HookContext as FeathersHookContext } from '@feathersjs/feathers'; +import authentication from './authentication'; +import mongodb from './mongodb'; +import socketio from '@feathersjs/socketio'; + +// Don't remove this comment. It's needed to format import lines nicely. + +const app: Application = express(feathers()); +export type HookContext = { + app: Application; +} & FeathersHookContext; + +// Enable security, CORS, compression, favicon and body parsing +app.use( + helmet({ + contentSecurityPolicy: false, + }) +); +app.use(cors()); +app.use(compress()); +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); +app.set('host', config.server.hostName); +app.set('port', config.server.port); +app.set('paginate', config.server.paginate); +app.set('authentication', config.authentication); +//set Public folder path +app.set('public', config.server.publicPath); + +//page favicon +app.use(favicon(path.join(app.get('public'), 'favicon.ico'))); + +// routes +app.use('/', publicRoutes); +// Host the public folder +app.use('/', express.static(app.get('public'))); + +// Set up Plugins and providers +app.configure(express.rest()); +app.configure( + socketio(function (io) { + io.sockets.setMaxListeners(0); + }) +); +app.configure(mongodb); + +// Configure other middleware (see `middleware/index.ts`) +app.configure(middleware); +app.configure(authentication); +// Set up our services (see `services/index.ts`) +app.configure(services); +// Set up event channels (see channels.ts) +app.configure(channels); + +// Configure a middleware for 404s and the error handler +app.use(express.notFound()); +app.use(express.errorHandler({ logger } as any)); + +app.hooks(appHooks); + +export default app; diff --git a/vircadia_metaverse_v2_api/src/appconfig.ts b/vircadia_metaverse_v2_api/src/appconfig.ts index 8abe9e47..d73d3ab8 100644 --- a/vircadia_metaverse_v2_api/src/appconfig.ts +++ b/vircadia_metaverse_v2_api/src/appconfig.ts @@ -1,275 +1,277 @@ -// Copyright 2020 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -import dotenv from 'dotenv-flow'; -import appRootPath from 'app-root-path'; -import { IsNullOrEmpty, getMyExternalIPAddress } from './utils/Misc'; -import fs from 'fs'; - -if (globalThis.process?.env.APP_ENV === 'development') { - if ( - !fs.existsSync(appRootPath.path + '/.env') && - !fs.existsSync(appRootPath.path + '/.env.local') - ) { - const fromEnvPath = appRootPath.path + '/.env.local.default'; - const toEnvPath = appRootPath.path + '/.env.local'; - fs.copyFileSync(fromEnvPath, toEnvPath, fs.constants.COPYFILE_EXCL); - } -} - -dotenv.config({ - path: appRootPath.path, - silent: true, -}); - -/** - * Server - */ -const server = { - local: process.env.LOCAL === 'true', - hostName: process.env.SERVER_HOST, - port: process.env.PORT ?? 3030, - paginate: { - default: 10, - max: 100, - }, - isDevelopmentMode: (process.env.APP_ENV ?? 'development') === 'development', - publicPath: process.env.PUBLIC_PATH ?? './public', - version: process.env.SERVER_VERSION ?? '', - localStorageProvider: process.env.LOCAL_STORAGE_PROVIDER || '', - localStorageProviderPort: process.env.LOCAL_STORAGE_PROVIDER_PORT || '', - storageProvider: process.env.STORAGE_PROVIDER || 'local', -}; - -/* - * Database - */ - -const database = { - databaseUrl: process.env.DATABASE_URL, - dbName: process.env.DB_NAME ?? 'tester', - dbUserName: process.env.DB_USER ?? 'metaverse', - dbPassword: process.env.DB_PASSWORD ?? 'nooneknowsit', - host: process.env.DB_HOST ?? 'localhost', - port: process.env.DB_PORT ?? '27017', - authSource: process.env.DB_AUTHDB ?? 'admin', -}; - -const monitoring = { - enable: true, // enable value monitoring - history: true, // whether to keep value history -}; -/* - * Email - */ -const email = { - host: process.env.SMTP_HOST ?? 'smtp.gmail.com', - port: parseInt(process.env.SMTP_PORT ?? '465'), - secure: process.env.SMTP_SECURE === 'false' ? false : true, - auth: { - user: process.env.SMTP_USER ?? 'khilan.odan@gmail.com', - pass: process.env.SMTP_PASS ?? 'blackhawk143', - }, - email_from: process.env.SMTP_EMAIL_FROM ?? 'khilan.odan@gmail.com', -}; - -/** - * Metaverse Server - */ - -const metaverseServer = { - listen_host: process.env.LISTEN_HOST ?? '0.0.0.0', - listen_port: process.env.LISTEN_PORT ?? 9400, - metaverseInfoAdditionFile: './lib/metaverse_info.json', - maxNameLength: 32, - session_timeout_minutes: 5, - heartbeat_seconds_until_offline: 5 * 60, // seconds until non-heartbeating user is offline - domain_seconds_until_offline: 10 * 60, // seconds until non-heartbeating domain is offline - domain_seconds_check_if_online: 2 * 60, // how often to check if a domain is online - handshake_request_expiration_minutes: 1, // minutes that a handshake friend request is active - connection_request_expiration_minutes: 60 * 24 * 4, // 4 days - friend_request_expiration_minutes: 60 * 24 * 4, // 4 days - base_admin_account: process.env.ADMIN_ACCOUNT ?? 'Metaverse', - place_current_timeout_minutes: 5, // minutes until current place info is stale - place_inactive_timeout_minutes: 60, // minutes until place is considered inactive - place_check_last_activity_seconds: 3 * 60 - 5, // seconds between checks for Place lastActivity updates - email_verification_timeout_minutes: 1440, - enable_account_email_verification: - process.env.ENABLE_ACCOUNT_VERIFICATION ?? 'true', - email_verification_email_body: '../mailtemplates/verificationEmail.html', - email_reset_password_otp_body: '../mailtemplates/resetPasswordOtp.html', - email_verification_success_redirect: - 'METAVERSE_SERVER_URL/verificationEmailSuccess.html', - email_verification_failure_redirect: - 'METAVERSE_SERVER_URL/verificationEmailFailure.html?r=FAILURE_REASON', - domain_token_expire_hours: 24 * 365, // one year - owner_token_expire_hours: 24 * 7, // one week - reset_password_otp_length: 6, - reset_password_expire_minutes: 1 * 60, // Minutes - allow_temp_domain_creation: true, - inventoryItemMasterDataFile: './master_data/inventory_items.json', - questItemMasterDataFile: './master_data/quest_items.json', - npcMasterDataFile: './master_data/npc.json', -}; - -/** - * Client / frontend - */ -const client = { - logo: process.env.APP_LOGO || '', - title: process.env.APP_TITLE || '', - url: - process.env.APP_URL || - (server.local - ? 'http://' + process.env.APP_HOST + ':' + process.env.APP_PORT - : 'https://' + process.env.APP_HOST + ':' + process.env.APP_PORT), -}; - -/** - * Metaverse - */ - -const metaverse = { - metaverseName: process.env.METAVERSE_NAME ?? '', - metaverseNickName: process.env.METAVERSE_NICK_NAME ?? '', - metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? '', // if empty, set to self - defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? '', // if empty, set to self - dashboardUrl: process.env.DASHBOARD_URL, -}; - -/** - * Authentication - */ -const authentication = { - entity: 'user', - service: 'auth', - secret: process.env.AUTH_SECRET ?? 'testing', - authStrategies: ['jwt', 'local'], - jwtOptions: { - expiresIn: '60 days', - }, - local: { - usernameField: 'username', - passwordField: 'password', - }, - bearerToken: { - numBytes: 16, - }, - callback: { - facebook: - process.env.FACEBOOK_CALLBACK_URL || - `${client.url}/auth/oauth/facebook`, - google: - process.env.GOOGLE_CALLBACK_URL || - `${client.url}/auth/oauth/google`, - }, - oauth: { - google: { - redirect_uri: `${metaverse.metaverseServerUrl}/oauth/google/callback`, - //callback: 'http://localhost:8081', - key: process.env.GOOGLE_CLIENT_ID, - secret: process.env.GOOGLE_CLIENT_SECRET, - scope: ['openid', 'email', 'profile'], - }, - facebook: { - redirect_uri: `${metaverse.metaverseServerUrl}/oauth/facebook/callback`, - key: process.env.FACEBOOK_CLIENT_ID, - secret: process.env.FACEBOOK_CLIENT_SECRET, - scope: ['email, public_profile'], - }, - }, -}; - -if ( - IsNullOrEmpty(metaverse.metaverseServerUrl) || - IsNullOrEmpty(metaverse.defaultIceServerUrl) -) { - getMyExternalIPAddress().then((ipAddress) => { - if (IsNullOrEmpty(metaverse.metaverseServerUrl)) { - const newUrl = `http://${ipAddress}:${metaverseServer.listen_port.toString()}`; - metaverse.metaverseServerUrl = newUrl; - } - if (IsNullOrEmpty(metaverse.defaultIceServerUrl)) { - metaverse.defaultIceServerUrl = ipAddress; - } - }); -} - -const dbCollections = { - domains: 'domains', - accounts: 'accounts', - places: 'places', - tokens: 'tokens', - requests: 'requests', - asset: 'asset', - inventory: 'inventory', - inventoryItem: 'inventoryItems', - achievements: 'achievements', - achievementItems: 'achievementItems', - resetPassword: 'resetPassword', - itemHandler: 'itemHandler', - quest: 'quest', - questItem: 'questItem', - npc: 'npc', - rewardItems: 'rewardItems', - tokenBalances: 'tokenBalances', - blockchainTransactions: 'blockchainTransactions', -}; - -/** - * AWS - */ -const aws = { - keys: { - accessKeyId: process.env.STORAGE_AWS_ACCESS_KEY_ID || '', - secretAccessKey: process.env.STORAGE_AWS_ACCESS_KEY_SECRET || '', - }, - route53: { - hostedZoneId: process.env.ROUTE53_HOSTED_ZONE_ID || '', - keys: { - accessKeyId: process.env.ROUTE53_ACCESS_KEY_ID || '', - secretAccessKey: process.env.ROUTE53_ACCESS_KEY_SECRET || '', - }, - }, - s3: { - staticResourceBucket: - process.env.STORAGE_S3_STATIC_RESOURCE_BUCKET || '', - region: process.env.STORAGE_S3_REGION || '', - avatarDir: process.env.STORAGE_S3_AVATAR_DIRECTORY || '', - s3DevMode: process.env.STORAGE_S3_DEV_MODE || '', - awsStorageProvider: process.env.AWS_STORAGE_PROVIDER || '', - }, -}; - -/** - * Full config - */ - -const config = { - deployStage: process.env.DEPLOY_STAGE, - authentication, - server, - metaverse, - metaverseServer, - dbCollections, - email, - aws, - database, - client, - monitoring, -}; - -export default config; +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import dotenv from 'dotenv-flow'; +import appRootPath from 'app-root-path'; +import { IsNullOrEmpty, getMyExternalIPAddress } from './utils/Misc'; +import fs from 'fs'; + +if (globalThis.process?.env.APP_ENV === 'development') { + if ( + !fs.existsSync(appRootPath.path + '/.env') && + !fs.existsSync(appRootPath.path + '/.env.local') + ) { + const fromEnvPath = appRootPath.path + '/.env.local.default'; + const toEnvPath = appRootPath.path + '/.env.local'; + fs.copyFileSync(fromEnvPath, toEnvPath, fs.constants.COPYFILE_EXCL); + } +} + +dotenv.config({ + path: appRootPath.path, + silent: true, +}); + +/** + * Server + */ +const server = { + local: process.env.LOCAL === 'true', + hostName: process.env.SERVER_HOST, + port: process.env.PORT ?? 3030, + paginate: { + default: 10, + max: 100, + }, + isDevelopmentMode: (process.env.APP_ENV ?? 'development') === 'development', + publicPath: process.env.PUBLIC_PATH ?? './public', + version: process.env.SERVER_VERSION ?? '', + localStorageProvider: process.env.LOCAL_STORAGE_PROVIDER || '', + localStorageProviderPort: process.env.LOCAL_STORAGE_PROVIDER_PORT || '', + storageProvider: process.env.STORAGE_PROVIDER || 'local', +}; + +/* + * Database + */ + +const database = { + databaseUrl: process.env.DATABASE_URL, + dbName: process.env.DB_NAME ?? 'tester', + dbUserName: process.env.DB_USER ?? 'metaverse', + dbPassword: process.env.DB_PASSWORD ?? 'nooneknowsit', + host: process.env.DB_HOST ?? 'localhost', + port: process.env.DB_PORT ?? '27017', + authSource: process.env.DB_AUTHDB ?? 'admin', +}; + +const monitoring = { + enable: true, // enable value monitoring + history: true, // whether to keep value history +}; +/* + * Email + */ +const email = { + host: process.env.SMTP_HOST ?? 'smtp.gmail.com', + port: parseInt(process.env.SMTP_PORT ?? '465'), + secure: process.env.SMTP_SECURE === 'false' ? false : true, + auth: { + user: process.env.SMTP_USER ?? 'khilan.odan@gmail.com', + pass: process.env.SMTP_PASS ?? 'blackhawk143', + }, + email_from: process.env.SMTP_EMAIL_FROM ?? 'khilan.odan@gmail.com', +}; + +/** + * Metaverse Server + */ + +const metaverseServer = { + listen_host: process.env.LISTEN_HOST ?? '0.0.0.0', + listen_port: process.env.LISTEN_PORT ?? 9400, + metaverseInfoAdditionFile: './lib/metaverse_info.json', + maxNameLength: 32, + session_timeout_minutes: 5, + heartbeat_seconds_until_offline: 5 * 60, // seconds until non-heartbeating user is offline + domain_seconds_until_offline: 10 * 60, // seconds until non-heartbeating domain is offline + domain_seconds_check_if_online: 2 * 60, // how often to check if a domain is online + handshake_request_expiration_minutes: 1, // minutes that a handshake friend request is active + connection_request_expiration_minutes: 60 * 24 * 4, // 4 days + friend_request_expiration_minutes: 60 * 24 * 4, // 4 days + base_admin_account: process.env.ADMIN_ACCOUNT ?? 'Metaverse', + place_current_timeout_minutes: 5, // minutes until current place info is stale + place_inactive_timeout_minutes: 60, // minutes until place is considered inactive + place_check_last_activity_seconds: 3 * 60 - 5, // seconds between checks for Place lastActivity updates + email_verification_timeout_minutes: 1440, + enable_account_email_verification: + process.env.ENABLE_ACCOUNT_VERIFICATION ?? 'true', + email_verification_email_body: '../mailtemplates/verificationEmail.html', + email_reset_password_otp_body: '../mailtemplates/resetPasswordOtp.html', + email_verification_success_redirect: + 'METAVERSE_SERVER_URL/verificationEmailSuccess.html', + email_verification_failure_redirect: + 'METAVERSE_SERVER_URL/verificationEmailFailure.html?r=FAILURE_REASON', + domain_token_expire_hours: 24 * 365, // one year + owner_token_expire_hours: 24 * 7, // one week + reset_password_otp_length: 6, + reset_password_expire_minutes: 1 * 60, // Minutes + allow_temp_domain_creation: true, + inventoryItemMasterDataFile: './master_data/inventory_items.json', + questItemMasterDataFile: './master_data/quest_items.json', + npcMasterDataFile: './master_data/npc.json', + tokengen_url: '../mailtemplates/DomainTokenLogin.html', +}; + +/** + * Client / frontend + */ +const client = { + logo: process.env.APP_LOGO || '', + title: process.env.APP_TITLE || '', + url: + process.env.APP_URL || + (server.local + ? 'http://' + process.env.APP_HOST + ':' + process.env.APP_PORT + : 'https://' + process.env.APP_HOST + ':' + process.env.APP_PORT), +}; + +/** + * Metaverse + */ + +const metaverse = { + metaverseName: process.env.METAVERSE_NAME ?? '', + metaverseNickName: process.env.METAVERSE_NICK_NAME ?? '', + metaverseServerUrl: process.env.METAVERSE_SERVER_URL ?? '', // if empty, set to self + defaultIceServerUrl: process.env.DEFAULT_ICE_SERVER_URL ?? '', // if empty, set to self + dashboardUrl: process.env.DASHBOARD_URL, +}; + +/** + * Authentication + */ +const authentication = { + entity: 'user', + service: 'auth', + secret: process.env.AUTH_SECRET ?? 'testing', + authStrategies: ['jwt', 'local'], + jwtOptions: { + expiresIn: '60 days', + }, + local: { + usernameField: 'username', + passwordField: 'password', + }, + bearerToken: { + numBytes: 16, + }, + callback: { + facebook: + process.env.FACEBOOK_CALLBACK_URL || + `${client.url}/auth/oauth/facebook`, + google: + process.env.GOOGLE_CALLBACK_URL || + `${client.url}/auth/oauth/google`, + }, + oauth: { + google: { + redirect_uri: `${metaverse.metaverseServerUrl}/oauth/google/callback`, + //callback: 'http://localhost:8081', + key: process.env.GOOGLE_CLIENT_ID, + secret: process.env.GOOGLE_CLIENT_SECRET, + scope: ['openid', 'email', 'profile'], + }, + facebook: { + redirect_uri: `${metaverse.metaverseServerUrl}/oauth/facebook/callback`, + key: process.env.FACEBOOK_CLIENT_ID, + secret: process.env.FACEBOOK_CLIENT_SECRET, + scope: ['email, public_profile'], + }, + }, +}; + +if ( + IsNullOrEmpty(metaverse.metaverseServerUrl) || + IsNullOrEmpty(metaverse.defaultIceServerUrl) +) { + getMyExternalIPAddress().then((ipAddress) => { + if (IsNullOrEmpty(metaverse.metaverseServerUrl)) { + const newUrl = `http://${ipAddress}:${metaverseServer.listen_port.toString()}`; + metaverse.metaverseServerUrl = newUrl; + } + if (IsNullOrEmpty(metaverse.defaultIceServerUrl)) { + metaverse.defaultIceServerUrl = ipAddress; + } + }); +} + +const dbCollections = { + domains: 'domains', + accounts: 'accounts', + places: 'places', + tokens: 'tokens', + requests: 'requests', + asset: 'asset', + inventory: 'inventory', + inventoryItem: 'inventoryItems', + achievements: 'achievements', + achievementItems: 'achievementItems', + resetPassword: 'resetPassword', + itemHandler: 'itemHandler', + quest: 'quest', + questItem: 'questItem', + npc: 'npc', + rewardItems: 'rewardItems', + tokenBalances: 'tokenBalances', + minigame: 'minigame', + blockchainTransactions: 'blockchainTransactions', +}; + +/** + * AWS + */ +const aws = { + keys: { + accessKeyId: process.env.STORAGE_AWS_ACCESS_KEY_ID || '', + secretAccessKey: process.env.STORAGE_AWS_ACCESS_KEY_SECRET || '', + }, + route53: { + hostedZoneId: process.env.ROUTE53_HOSTED_ZONE_ID || '', + keys: { + accessKeyId: process.env.ROUTE53_ACCESS_KEY_ID || '', + secretAccessKey: process.env.ROUTE53_ACCESS_KEY_SECRET || '', + }, + }, + s3: { + staticResourceBucket: + process.env.STORAGE_S3_STATIC_RESOURCE_BUCKET || '', + region: process.env.STORAGE_S3_REGION || '', + avatarDir: process.env.STORAGE_S3_AVATAR_DIRECTORY || '', + s3DevMode: process.env.STORAGE_S3_DEV_MODE || '', + awsStorageProvider: process.env.AWS_STORAGE_PROVIDER || '', + }, +}; + +/** + * Full config + */ + +const config = { + deployStage: process.env.DEPLOY_STAGE, + authentication, + server, + metaverse, + metaverseServer, + dbCollections, + email, + aws, + database, + client, + monitoring, +}; + +export default config; diff --git a/vircadia_metaverse_v2_api/src/channels.ts b/vircadia_metaverse_v2_api/src/channels.ts index 4126f977..0ba1fe28 100644 --- a/vircadia_metaverse_v2_api/src/channels.ts +++ b/vircadia_metaverse_v2_api/src/channels.ts @@ -17,6 +17,8 @@ import '@feathersjs/transport-commons'; import { HookContext } from '@feathersjs/feathers'; import { Application } from './declarations'; +import { authRepository } from './redis'; +import { IsNotNullOrEmpty } from './utils/Misc'; export default function (app: Application): void { if (typeof app.channel !== 'function') { @@ -56,6 +58,21 @@ export default function (app: Application): void { } }); + app.on( + 'logout', + async (authResult: any, { connection }: any): Promise => { + const authToken: any = await authRepository + .search() + .where('token') + .equals(authResult.accessToken) + .returnAll(); + + if (IsNotNullOrEmpty(authToken)) { + await authRepository.remove(authToken[0].entityId); + } + } + ); + // eslint-disable-next-line @typescript-eslint/no-unused-vars app.publish((data: any, hook: HookContext) => { // Here you can add event publishers to channels set up in `channels.ts` diff --git a/vircadia_metaverse_v2_api/src/common/interfaces/MinigameInterface.ts b/vircadia_metaverse_v2_api/src/common/interfaces/MinigameInterface.ts new file mode 100644 index 00000000..90f0009c --- /dev/null +++ b/vircadia_metaverse_v2_api/src/common/interfaces/MinigameInterface.ts @@ -0,0 +1,54 @@ +// Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +export interface MiniGameInterface { + id: string; + name: string; + giver: string; + description: string; + prerequisites: PrerequisitesInterface; + attributes: AttributesInterface; + rewards?: RewardsInterface; + createdAt: Date; // A Date representing MiniGame created date + updatedAt: Date; //A Date representing MiniGame item updatedAt date +} + +export interface PrerequisitesInterface { + minLevel: number; + maxLevel: number; + expireAfter: number; //Time in milliseconds seconds + maxActive: number; + maxSimultaneous: number; //means that you can have 5 max quests completed but unexpired, to do this quest again you will need to wait until one or more of them expire +} + +export interface AttributesInterface { + enemyId: string; + enemyHitpoints: number; + enemyPhysicalDamageLevel: number; + enemyPhysicalDefenceLevel: number; +} + +export interface RewardsInterface { + items?: itemInterface[]; + xp?: number; + goo?: number; +} +export interface itemInterface { + itemId: string; + qty: number; +} + diff --git a/vircadia_metaverse_v2_api/src/common/responsebuilder/miniGameBuilder.ts b/vircadia_metaverse_v2_api/src/common/responsebuilder/miniGameBuilder.ts new file mode 100644 index 00000000..640301f6 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/common/responsebuilder/miniGameBuilder.ts @@ -0,0 +1,27 @@ + // Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import { MiniGameInterface } from '../interfaces/MinigameInterface'; + +export function buildMiniGameInfo(minigame?: MiniGameInterface): any { + const data: any = minigame; + delete data?._id; + delete data?.createdAt; + delete data?.updatedAt; + return data; +} + diff --git a/vircadia_metaverse_v2_api/src/common/responsebuilder/placesBuilder.ts b/vircadia_metaverse_v2_api/src/common/responsebuilder/placesBuilder.ts index ef2d076c..53308c74 100644 --- a/vircadia_metaverse_v2_api/src/common/responsebuilder/placesBuilder.ts +++ b/vircadia_metaverse_v2_api/src/common/responsebuilder/placesBuilder.ts @@ -54,6 +54,7 @@ export async function buildLocationInfo( } } ret.node_id = pAcct.locationNodeId; + ret.path = pAcct.locationPath; ret.online = isOnline(pAcct); return ret; } @@ -171,4 +172,3 @@ export async function buildPlacesForDomain( } return ret; } - diff --git a/vircadia_metaverse_v2_api/src/hooks/addOldToken.ts b/vircadia_metaverse_v2_api/src/hooks/addOldToken.ts index 275079f9..74d7b2be 100644 --- a/vircadia_metaverse_v2_api/src/hooks/addOldToken.ts +++ b/vircadia_metaverse_v2_api/src/hooks/addOldToken.ts @@ -22,32 +22,35 @@ import { getUtcDate } from '../utils/Utils'; export default () => { return async (context: HookContext): Promise => { - const dbService = new DatabaseService( - { id: 'id', multi: ['remove'] }, - undefined, - context - ); - dbService.deleteMultipleData(config.dbCollections.tokens, { - query: { expirationTime: { $lt: getUtcDate() } }, - }); - const token = await Tokens.createToken(context.result?.id, [ - TokenScope.OWNER, - ]); - token.token_type = 'Bearer'; - await dbService.createData(config.dbCollections.tokens, token); + if (context.path === 'authentication') { + const dbService = new DatabaseService( + { id: 'id', multi: ['remove'] }, + undefined, + context + ); + dbService.deleteMultipleData(config.dbCollections.tokens, { + query: { expirationTime: { $lt: getUtcDate() } }, + }); + const token = await Tokens.createToken(context.result.user?.id, [ + TokenScope.OWNER, + ]); + token.token_type = 'Bearer'; + await dbService.createData(config.dbCollections.tokens, token); - context.result = { - ...context.result, - access_token: token.token, - token_type: 'Bearer', - expires_in: - token.expirationTime.valueOf() / 1000 - - token.whenCreated.valueOf() / 1000, - refresh_token: token.refreshToken, - scope: token.scope[0], - created_at: token.whenCreated.valueOf() / 1000, - }; + context.result.data = { + access_token: token.token, + token_type: 'Bearer', + expires_in: + token.expirationTime.valueOf() / 1000 - + token.whenCreated.valueOf() / 1000, + refresh_token: token.refreshToken, + scope: token.scope[0], + created_at: token.whenCreated.valueOf(), + account_id: context.result.user?.id, + account_name: context.result.user?.username, + account_roles: context.result.user?.roles, + }; + } return context; }; }; - diff --git a/vircadia_metaverse_v2_api/src/hooks/authTokenValidate.ts b/vircadia_metaverse_v2_api/src/hooks/authTokenValidate.ts new file mode 100644 index 00000000..a00a28c8 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/hooks/authTokenValidate.ts @@ -0,0 +1,26 @@ +import { NotAuthenticated } from '@feathersjs/errors'; +import { HookContext } from '@feathersjs/feathers'; +import { authRepository } from '../redis'; +import { IsNullOrEmpty } from '../utils/Misc'; + +export default () => { + return async (context: HookContext): Promise => { + const contextAuth = context.params.authentication?.accessToken; + + const authToken = contextAuth + ? contextAuth + : context.params.headers?.authorization; + + const existAuth: any = await authRepository + .search() + .where('token') + .equals(authToken) + .returnAll(); + + if (IsNullOrEmpty(existAuth)) { + throw new NotAuthenticated('Invalid token'); + } else { + return context; + } + }; +}; diff --git a/vircadia_metaverse_v2_api/src/hooks/blockchain/connectToGooTokenContract.ts b/vircadia_metaverse_v2_api/src/hooks/blockchain/connectToGooTokenContract.ts index 097308aa..b9e0b4ce 100644 --- a/vircadia_metaverse_v2_api/src/hooks/blockchain/connectToGooTokenContract.ts +++ b/vircadia_metaverse_v2_api/src/hooks/blockchain/connectToGooTokenContract.ts @@ -16,16 +16,16 @@ import { TOKEN_NAME } from './../../services/token-transfer/token-transfer.class import { Hook, HookContext } from '@feathersjs/feathers'; import { ethers } from 'ethers'; import { GeneralError } from '@feathersjs/errors'; -import { TDeployedHardhatContractsJson } from './../../../blockchain/interfaces/contractTypes'; -import deployedContracts from './../../../blockchain/hardhat_contracts.json'; +import { TDeployedHardhatContractsJson } from './../../../ethereum/dlt/interfaces/contractTypes'; +import deployedContracts from './../../../ethereum/dlt/hardhat_contracts.json'; import { IsNullOrEmpty } from '../../utils/Misc'; -import { GooERC20 } from '../../../blockchain/typechain/GooERC20'; +import { VircadiaERC20 } from '../../../ethereum/dlt/typechain/VircadiaERC20' import { BlockchainOptions } from './../../services/token-transfer/token-transfer.class'; // Connects to the deployed ERC20 token contract and passes it to service or subsequent hooks export default (options: BlockchainOptions): Hook => { let wallet: ethers.Wallet; - let gooToken: GooERC20; + let gooToken: VircadiaERC20; if (IsNullOrEmpty(options.minterPrivateKey)) { throw new GeneralError('Private key cannot be empty'); @@ -43,7 +43,7 @@ export default (options: BlockchainOptions): Hook => { contracts[options.chainId][0].contracts[TOKEN_NAME] .abi as ethers.ContractInterface, wallet - ) as GooERC20; + ) as VircadiaERC20; } return async (context: HookContext): Promise => { diff --git a/vircadia_metaverse_v2_api/src/hooks/storeAuthTokenInRedis.ts b/vircadia_metaverse_v2_api/src/hooks/storeAuthTokenInRedis.ts new file mode 100644 index 00000000..6ef68692 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/hooks/storeAuthTokenInRedis.ts @@ -0,0 +1,57 @@ +import { NotAuthenticated } from '@feathersjs/errors'; +import { HookContext } from '@feathersjs/feathers'; +import { authRepository, redisClient } from '../redis'; +import { IsNotNullOrEmpty, IsNullOrEmpty } from '../utils/Misc'; + +export default () => { + return async (context: HookContext): Promise => { + if ( + context.path === 'authentication' && + context.method != 'remove' && + IsNotNullOrEmpty(context.result?.accessToken) + ) { + try { + const authResult = context.result; + + const currentTime = Math.floor(Date.now() / 1000); + + const allAuthTokens = await authRepository + .search() + .return.all(); + + if (IsNotNullOrEmpty(allAuthTokens)) { + // get expired tokens + const expiredAuths: any = await authRepository + .search() + .where('expires') + .lte(currentTime) + .returnAll(); + + // delete expired tokens + const deleteAuths = + IsNotNullOrEmpty(expiredAuths) && + expiredAuths.map((expiredAuth: any) => { + return ( + expiredAuth.prefix + ':' + expiredAuth.entityId + ); + }); + + await redisClient.del(deleteAuths); + } + + // create new token + await authRepository.createAndSave({ + token: authResult.accessToken, + tokenId: authResult.authentication.payload.jti, + userId: authResult.user.id, + expires: authResult.authentication.payload.exp, + }); + } catch (e) { + console.log(e); + throw e; + } + } + return context; + }; +}; + diff --git a/vircadia_metaverse_v2_api/src/redis.ts b/vircadia_metaverse_v2_api/src/redis.ts new file mode 100644 index 00000000..0e638428 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/redis.ts @@ -0,0 +1,24 @@ +import { Client, Entity, Schema } from 'redis-om'; +import Redis from 'ioredis'; +export const redisClient = new Redis( + `${process.env.REDIS_HOST}:${process.env.REDIS_PORT}` +); + +const client = new Client(); + +client.open(`${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`); + +class AuthJwt extends Entity {} + +const authToken = new Schema(AuthJwt, { + token: { type: 'string' }, + tokenId: { type: 'string' }, + expires: { type: 'number' }, + userId: { type: 'string' }, +}); + +/* use the client to create a Repository */ +export const authRepository = client.fetchRepository(authToken); + +authRepository.createIndex(); + diff --git a/vircadia_metaverse_v2_api/src/routes/publicRoutes.ts b/vircadia_metaverse_v2_api/src/routes/publicRoutes.ts index 405a5b35..c84a8c21 100644 --- a/vircadia_metaverse_v2_api/src/routes/publicRoutes.ts +++ b/vircadia_metaverse_v2_api/src/routes/publicRoutes.ts @@ -19,6 +19,6 @@ import { PublicRoutesController } from '../controllers/PublicRoutesController'; const publicRoutes = express.Router(); -publicRoutes.get('/metaverse_info', PublicRoutesController().metaverseInfo); +publicRoutes.get('/api/metaverse_info', PublicRoutesController().metaverseInfo); export { publicRoutes }; diff --git a/vircadia_metaverse_v2_api/src/services/accounts/account-field/account-field.joi.ts b/vircadia_metaverse_v2_api/src/services/accounts/account-field/account-field.joi.ts index 52ac557f..55089f7e 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/account-field/account-field.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/account-field/account-field.joi.ts @@ -15,8 +15,8 @@ import Joi from '@hapi/joi'; import { HookContext } from '@feathersjs/feathers'; -const accountId = Joi.string().trim().required(); -const fieldName = Joi.string().trim().required(); +const accountId = Joi.string().trim(); +const fieldName = Joi.string().trim(); export const editDomainSchema = Joi.object().keys({ accountId, @@ -32,10 +32,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.route; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.route, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/accounts/account-field/account-field.service.ts b/vircadia_metaverse_v2_api/src/services/accounts/account-field/account-field.service.ts index 5ec733e5..fbed59b8 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/account-field/account-field.service.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/account-field/account-field.service.ts @@ -36,7 +36,7 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/accountField', new AccountFeild(options, app)); app.use( - '/account/:accountId/field/:fieldName', + 'api/v1/account/:accountId/field/:fieldName', app.service('accountField') ); @@ -45,4 +45,3 @@ export default function (app: Application): void { service.hooks(hooks); } - diff --git a/vircadia_metaverse_v2_api/src/services/accounts/account-publickey/account-publickey.joi.ts b/vircadia_metaverse_v2_api/src/services/accounts/account-publickey/account-publickey.joi.ts index 5c0c210e..53f77827 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/account-publickey/account-publickey.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/account-publickey/account-publickey.joi.ts @@ -25,10 +25,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.route; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.route, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/accounts/account-publickey/account-publickey.service.ts b/vircadia_metaverse_v2_api/src/services/accounts/account-publickey/account-publickey.service.ts index 44c67b8c..775bbd6c 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/account-publickey/account-publickey.service.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/account-publickey/account-publickey.service.ts @@ -56,7 +56,12 @@ export default function (app: Application): void { }, new AccountPublickey(options, app) ); - app.use('user/:accountId/public_key', app.service('user/public_key')); + app.use('api/v1/user/public_key', app.service('user/public_key')); + + app.use( + 'api/v1/users/:accountId/public_key', + app.service('user/public_key') + ); // Get our initialized service so that we can register hooks const service = app.service('user/public_key'); diff --git a/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.class.ts b/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.class.ts index e8d345f2..11b21da8 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.class.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.class.ts @@ -81,7 +81,7 @@ export class AccountTokens extends DatabaseService { return Promise.resolve( buildPaginationResponse( - tokens, + { tokens }, page_num, perPage, Math.ceil(tokensData.total / perPage), @@ -174,4 +174,3 @@ export class AccountTokens extends DatabaseService { } } } - diff --git a/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.hooks.ts b/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.hooks.ts index 641b1ded..33428125 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.hooks.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.hooks.ts @@ -28,13 +28,16 @@ import { disallow } from 'feathers-hooks-common'; export default { before: { - all: [authenticate('jwt')], - find: [validators.form(findAccountTokenSchema, joiReadOptions)], + all: [], + find: [ + authenticate('jwt'), + validators.form(findAccountTokenSchema, joiReadOptions), + ], get: [disallow()], create: [disallow()], update: [disallow()], patch: [disallow()], - remove: [], + remove: [authenticate('jwt')], }, after: { @@ -57,4 +60,3 @@ export default { remove: [], }, }; - diff --git a/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.joi.ts b/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.joi.ts index aacc3ab5..1bffbb88 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.joi.ts @@ -15,7 +15,6 @@ import Joi from '@hapi/joi'; import { HookContext } from '@feathersjs/feathers'; -const accountId = Joi.string().trim().required(); const per_page = Joi.number().integer().positive(); const page_num = Joi.number().integer().positive(); const asAdmin = Joi.boolean(); @@ -30,10 +29,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.service.ts b/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.service.ts index 35dd2217..908c3c9a 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.service.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/account-tokens/account-tokens.service.ts @@ -36,7 +36,8 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/accountTokens', new AccountTokens(options, app)); - app.use('/account/:accountId/tokens', app.service('accountTokens')); + + app.use('/api/v1/account/:accountId/tokens', app.service('accountTokens')); // Get our initialized service so that we can register hooks const service = app.service('accountTokens'); diff --git a/vircadia_metaverse_v2_api/src/services/accounts/accounts.class.ts b/vircadia_metaverse_v2_api/src/services/accounts/accounts.class.ts index 285cf680..9cfe8354 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/accounts.class.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/accounts.class.ts @@ -1,409 +1,413 @@ -// Copyright 2020 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -import { RequestType } from '../../common/sets/RequestType'; -import { DatabaseServiceOptions } from '../../common/dbservice/DatabaseServiceOptions'; -import { Params, Id, NullableId } from '@feathersjs/feathers'; -import { Application } from '../../declarations'; -import { DatabaseService } from '../../common/dbservice/DatabaseService'; -import config from '../../appconfig'; -import { AccountInterface } from '../../common/interfaces/AccountInterface'; -import { RequestInterface } from '../../common/interfaces/RequestInterface'; -import { buildAccountInfo } from '../../common/responsebuilder/accountsBuilder'; -import { IsNotNullOrEmpty, IsNullOrEmpty } from '../../utils/Misc'; -import { NotAuthenticated, NotFound, BadRequest } from '@feathersjs/errors'; - -import { - isAdmin, - dateWhenNotOnline, - couldBeDomainId, - isValidateEmail, -} from '../../utils/Utils'; -import { messages } from '../../utils/messages'; -import { VKeyedCollection, SArray } from '../../utils/vTypes'; -import { - buildPaginationResponse, - buildSimpleResponse, -} from '../../common/responsebuilder/responseBuilder'; -import { extractLoggedInUserFromParams } from '../auth/auth.utils'; - -/** - * Accounts. - * @noInheritDoc - */ -export class Accounts extends DatabaseService { - constructor(options: Partial, app: Application) { - super(options, app); - } - - /** - * Verify user - * - * @remarks - * This method is part of the Verify user - * - Request Type - GET - * - End Point - API_URL/accounts/verify/email?a={accountId}&v={verificationCode} - * - * @requires @param a - User account - * @requires @param v - User verification code - * @returns After verification redirect to success or fail verification page - * - */ - - /** - * Returns the Accounts - * - * @remarks - * This method is part of the get list of accounts - * - Request Type - GET - * - End Point - API_URL/accounts?per_page=10&page=1 .... - * - * @optional @param per_page - page size - * @optional @param page - page number - * @optional @param filter - Connections|friends|all - * @optional @param status - Online|domainId - * @optional @param search - WildcardSearchString - * @optional @param acct - Account id - * @optional @param asAdmin - true | false if logged in account is administrator, list all accounts. Value is optional. - * @returns - Paginated accounts { data:[{...},{...}],current_page:1,per_page:10,total_pages:1,total_entries:5} - * - */ - - async find(params?: Params): Promise { - const loginUser = extractLoggedInUserFromParams(params); - if ( - IsNotNullOrEmpty(params?.query?.a) && - IsNotNullOrEmpty(params?.query?.v) - ) { - const accountId = params?.query?.a; - const verificationCode = params?.query?.v; - - const requestList: RequestInterface[] = await this.findDataToArray( - config.dbCollections.requests, - { - query: { - requestingAccountId: accountId, - verificationCode: verificationCode, - requestType: RequestType.VERIFYEMAIL, - }, - } - ); - if (requestList.length > 0) { - const RequestInterface = requestList[0]; - //if(RequestInterface.expirationTime && RequestInterface.expirationTime > new Date(Date.now())){ - await this.patchData(config.dbCollections.accounts, accountId, { - accountEmailVerified: true, - accountWaitingVerification: false, - }); - //}else{ - // throw new BadRequest(messages.common_messages_error_verify_request_expired); - //} - await this.deleteData( - config.dbCollections.requests, - RequestInterface.id ?? '' - ); - return Promise.resolve(); - } else { - throw new BadRequest( - messages.common_messages_error_missing_verification_pending_request - ); - } - } else if (IsNotNullOrEmpty(loginUser)) { - const perPage = parseInt(params?.query?.per_page) || 10; - const page = parseInt(params?.query?.page) || 1; - const skip = (page - 1) * perPage; - - let asAdmin = params?.query?.asAdmin === 'true' ? true : false; - const filter: string[] = (params?.query?.filter ?? '').split(','); - const status: string = params?.query?.status ?? ''; - const targetAccount = params?.query?.account ?? ''; - - const filterQuery: any = {}; - - if ( - asAdmin && - IsNotNullOrEmpty(loginUser) && - isAdmin(loginUser as AccountInterface) && - IsNullOrEmpty(targetAccount) - ) { - asAdmin = true; - } else { - asAdmin = false; - } - - if (filter.length > 0) { - if (!filter.includes('all')) { - if ( - filter.includes('friends') && - (loginUser?.friends ?? []).length > 0 - ) { - filterQuery.friends = { $in: loginUser.friends }; - } - if ( - filter.includes('connections') && - (loginUser.connections ?? []).length > 0 - ) { - filterQuery.connections = { - $in: loginUser.connections, - }; - } - } - } - - if (IsNotNullOrEmpty(status)) { - if (status === 'online') { - filterQuery.timeOfLastHeartbeat = { - $gte: dateWhenNotOnline(), - }; - } else if (couldBeDomainId(status)) { - filterQuery.locationDomainId = status; - } - } - - if (!asAdmin) { - filterQuery.id = loginUser.id; - } else if (IsNotNullOrEmpty(targetAccount)) { - filterQuery.id = targetAccount; - } - - const accountData = await this.findData( - config.dbCollections.accounts, - { - query: { - ...filterQuery, - $skip: skip, - $limit: perPage, - }, - } - ); - - let accountsList: AccountInterface[] = []; - - accountsList = accountData.data as Array; - - const accounts: Array = []; - - (accountsList as Array)?.forEach( - async (element) => { - accounts.push(await buildAccountInfo(element)); - } - ); - return Promise.resolve( - buildPaginationResponse( - accounts, - page, - perPage, - Math.ceil(accountData.total / perPage), - accountData.total - ) - ); - } else { - throw new NotAuthenticated(messages.common_messages_unauthorized); - } - } - - /** - * Returns the Account - * - * @remarks - * This method is part of the get account - * - Request Type - GET - * - End Point - API_URL/accounts/{accountId} - * - Access - Public, Owner, Admin - * - * @required @param accountId - Account id (Url param) - * @returns - Account { data:{account{...}}} - * - */ - - async get(id: Id): Promise { - const objAccount = await this.getData( - config.dbCollections.accounts, - id - ); - if (IsNotNullOrEmpty(objAccount)) { - const account = await buildAccountInfo(objAccount); - return Promise.resolve(buildSimpleResponse(account)); - } else { - throw new BadRequest( - messages.common_messages_target_account_notfound - ); - } - } - - /** - * Patch Account - * - * @remarks - * This method is part of the patch account - * - Request Type - PATCH - * - End Point - API_URL/accounts/{accountId} - * - * @requires @param acct - Account id (URL param) - * @param requestBody - {email:abc@test.com,public_key:key} - * @returns - {status: 'success' data:{...}} or { status: 'failure', message: 'message'} - * - */ - - async patch(id: NullableId, data: any, params: Params): Promise { - if (IsNotNullOrEmpty(id) && id) { - const loginUser = extractLoggedInUserFromParams(params); - if ( - (IsNotNullOrEmpty(loginUser) && - isAdmin(loginUser as AccountInterface)) || - id === loginUser.id - ) { - const valuesToSet = data ?? {}; - const updates: VKeyedCollection = {}; - if (IsNotNullOrEmpty(valuesToSet.email)) { - if (!isValidateEmail(valuesToSet.email)) { - throw new BadRequest( - messages.common_messages_email_validation_error - ); - } - const accountData = await this.findDataToArray( - config.dbCollections.accounts, - { query: { email: valuesToSet.email } } - ); - if (accountData.length > 0 && accountData[0].id !== id) { - throw new BadRequest( - messages.common_messages_user_email_link_error - ); - } - updates.email = valuesToSet.email; - } - if (IsNotNullOrEmpty(valuesToSet.public_key)) { - updates.sessionPublicKey = valuesToSet.public_key; - } - - if (IsNotNullOrEmpty(valuesToSet.bio)) { - updates.bio = valuesToSet.bio; - } - - if (IsNotNullOrEmpty(valuesToSet.country)) { - updates.country = valuesToSet.country; - } - - if (IsNotNullOrEmpty(valuesToSet.achievementId)) { - updates.achievementId = valuesToSet.achievementId; - } - - if (IsNotNullOrEmpty(valuesToSet.ethereumAddress)) { - updates.ethereumAddress = valuesToSet.ethereumAddress; - } - - if (valuesToSet.hasOwnProperty('images')) { - if (IsNotNullOrEmpty(valuesToSet.images.hero)) { - updates.imagesHero = valuesToSet.images.hero; - } - - if (IsNotNullOrEmpty(valuesToSet.images.tiny)) { - updates.imagesTiny = valuesToSet.images.tiny; - } - - if (IsNotNullOrEmpty(valuesToSet.images.thumbnail)) { - updates.imagesThumbnail = valuesToSet.images.thumbnail; - } - } - const result = await this.patchData( - config.dbCollections.accounts, - id, - updates - ); - - const accountResponse = await buildAccountInfo(result); - return Promise.resolve(buildSimpleResponse(accountResponse)); - } else { - throw new NotAuthenticated( - messages.common_messages_unauthorized - ); - } - } else { - throw new BadRequest( - messages.common_messages_target_account_notfound - ); - } - } - - /** - * Delete Account - * - * @remarks - * This method is part of the delete account - * - Request Type - DELETE - * - End Point - API_URL/accounts/{accountId} - * - Access: Admin only - * - * @requires @param acct - Account id (URL param) - * @returns - {status: 'success'} or { status: 'failure', message: 'message'} - * - */ - - async remove(id: NullableId): Promise { - if (IsNotNullOrEmpty(id) && id) { - const account = await this.getData( - config.dbCollections.accounts, - id - ); - - if (IsNotNullOrEmpty(account)) { - this.deleteData(config.dbCollections.accounts, id); - const accounts: AccountInterface[] = await this.findDataToArray( - config.dbCollections.accounts, - { - query: { - $or: [ - { connections: { $in: [account.username] } }, - { friends: { $in: [account.username] } }, - ], - }, - } - ); - - for (const element of accounts) { - SArray.remove(element.connections, account.username); - SArray.remove(element.friends, account.username); - await super.patchData( - config.dbCollections.accounts, - element.id, - { - connections: element.connections, - friends: element.friends, - } - ); - } - - await this.deleteMultipleData(config.dbCollections.domains, { - query: { sponsorAccountId: account.id }, - }); - await this.deleteMultipleData(config.dbCollections.places, { - query: { accountId: account.id }, - }); - - return Promise.resolve({}); - } else { - throw new NotFound( - messages.common_messages_target_account_notfound - ); - } - } else { - throw new BadRequest( - messages.common_messages_target_account_notfound - ); - } - } -} - +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import { RequestType } from '../../common/sets/RequestType'; +import { DatabaseServiceOptions } from '../../common/dbservice/DatabaseServiceOptions'; +import { Params, Id, NullableId } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { DatabaseService } from '../../common/dbservice/DatabaseService'; +import config from '../../appconfig'; +import { AccountInterface } from '../../common/interfaces/AccountInterface'; +import { RequestInterface } from '../../common/interfaces/RequestInterface'; +import { buildAccountInfo } from '../../common/responsebuilder/accountsBuilder'; +import { IsNotNullOrEmpty, IsNullOrEmpty } from '../../utils/Misc'; +import { NotAuthenticated, NotFound, BadRequest } from '@feathersjs/errors'; + +import { + isAdmin, + dateWhenNotOnline, + couldBeDomainId, + isValidateEmail, +} from '../../utils/Utils'; +import { messages } from '../../utils/messages'; +import { VKeyedCollection, SArray } from '../../utils/vTypes'; +import { + buildPaginationResponse, + buildSimpleResponse, +} from '../../common/responsebuilder/responseBuilder'; +import { extractLoggedInUserFromParams } from '../auth/auth.utils'; + +/** + * Accounts. + * @noInheritDoc + */ +export class Accounts extends DatabaseService { + constructor(options: Partial, app: Application) { + super(options, app); + } + + /** + * Verify user + * + * @remarks + * This method is part of the Verify user + * - Request Type - GET + * - End Point - API_URL/accounts/verify/email?a={accountId}&v={verificationCode} + * + * @requires @param a - User account + * @requires @param v - User verification code + * @returns After verification redirect to success or fail verification page + * + */ + + /** + * Returns the Accounts + * + * @remarks + * This method is part of the get list of accounts + * - Request Type - GET + * - End Point - API_URL/accounts?per_page=10&page=1 .... + * + * @optional @param per_page - page size + * @optional @param page - page number + * @optional @param filter - Connections|friends|all + * @optional @param status - Online|domainId + * @optional @param search - WildcardSearchString + * @optional @param acct - Account id + * @optional @param asAdmin - true | false if logged in account is administrator, list all accounts. Value is optional. + * @returns - Paginated accounts { data:[{...},{...}],current_page:1,per_page:10,total_pages:1,total_entries:5} + * + */ + + async find(params?: Params): Promise { + const loginUser = extractLoggedInUserFromParams(params); + if ( + IsNotNullOrEmpty(params?.query?.a) && + IsNotNullOrEmpty(params?.query?.v) + ) { + const accountId = params?.query?.a; + const verificationCode = params?.query?.v; + + const requestList: RequestInterface[] = await this.findDataToArray( + config.dbCollections.requests, + { + query: { + requestingAccountId: accountId, + verificationCode: verificationCode, + requestType: RequestType.VERIFYEMAIL, + }, + } + ); + if (requestList.length > 0) { + const RequestInterface = requestList[0]; + //if(RequestInterface.expirationTime && RequestInterface.expirationTime > new Date(Date.now())){ + await this.patchData(config.dbCollections.accounts, accountId, { + accountEmailVerified: true, + accountWaitingVerification: false, + }); + //}else{ + // throw new BadRequest(messages.common_messages_error_verify_request_expired); + //} + await this.deleteData( + config.dbCollections.requests, + RequestInterface.id ?? '' + ); + return Promise.resolve(); + } else { + throw new BadRequest( + messages.common_messages_error_missing_verification_pending_request + ); + } + } else if (IsNotNullOrEmpty(loginUser)) { + const perPage = parseInt(params?.query?.per_page) || 10; + const page = parseInt(params?.query?.page) || 1; + const skip = (page - 1) * perPage; + + let asAdmin = params?.query?.asAdmin == true ? true : false; + const filter: string[] = (params?.query?.filter ?? '').split(','); + const status: string = params?.query?.status ?? ''; + const targetAccount = params?.query?.account ?? ''; + + const filterQuery: any = {}; + + if ( + asAdmin && + IsNotNullOrEmpty(loginUser) && + isAdmin(loginUser as AccountInterface) + // && IsNullOrEmpty(targetAccount) + ) { + asAdmin = true; + } else { + asAdmin = false; + } + + if (filter.length > 0) { + if (!filter.includes('all')) { + if ( + filter.includes('friends') && + (loginUser?.friends ?? []).length > 0 + ) { + filterQuery.friends = { $in: loginUser.friends }; + } + if ( + filter.includes('connections') && + (loginUser.connections ?? []).length > 0 + ) { + filterQuery.connections = { + $in: loginUser.connections, + }; + } + } + } + + if (IsNotNullOrEmpty(status)) { + if (status === 'online') { + filterQuery.timeOfLastHeartbeat = { + $gte: dateWhenNotOnline(), + }; + } else if (couldBeDomainId(status)) { + filterQuery.locationDomainId = status; + } + } + + if (!asAdmin) { + filterQuery.id = loginUser.id; + } else if (IsNotNullOrEmpty(targetAccount)) { + filterQuery.id = targetAccount; + } + + const accountData = await this.findData( + config.dbCollections.accounts, + { + query: { + ...filterQuery, + $skip: skip, + $limit: perPage, + }, + } + ); + + let accountsList: AccountInterface[] = []; + + accountsList = accountData.data as Array; + + const accounts: Array = []; + + (accountsList as Array)?.forEach( + async (element) => { + accounts.push(await buildAccountInfo(element)); + } + ); + return Promise.resolve( + buildPaginationResponse( + { accounts }, + page, + perPage, + Math.ceil(accountData.total / perPage), + accountData.total + ) + ); + } else { + throw new NotAuthenticated(messages.common_messages_unauthorized); + } + } + + /** + * Returns the Account + * + * @remarks + * This method is part of the get account + * - Request Type - GET + * - End Point - API_URL/accounts/{accountId} + * - Access - Public, Owner, Admin + * + * @required @param accountId - Account id (Url param) + * @returns - Account { data:{account{...}}} + * + */ + + async get(id: Id): Promise { + const objAccount = await this.getData( + config.dbCollections.accounts, + id + ); + if (IsNotNullOrEmpty(objAccount)) { + const account = await buildAccountInfo(objAccount); + return Promise.resolve(buildSimpleResponse({ account })); + } else { + throw new BadRequest( + messages.common_messages_target_account_notfound + ); + } + } + + /** + * Patch Account + * + * @remarks + * This method is part of the patch account + * - Request Type - PATCH + * - End Point - API_URL/accounts/{accountId} + * + * @requires @param acct - Account id (URL param) + * @param requestBody - {email:abc@test.com,public_key:key} + * @returns - {status: 'success' data:{...}} or { status: 'failure', message: 'message'} + * + */ + + async patch(id: NullableId, data: any, params: Params): Promise { + if (IsNotNullOrEmpty(id) && id) { + const loginUser = extractLoggedInUserFromParams(params); + if ( + (IsNotNullOrEmpty(loginUser) && + isAdmin(loginUser as AccountInterface)) || + id === loginUser.id + ) { + const valuesToSet = data.accounts ?? {}; + const updates: VKeyedCollection = {}; + if (IsNotNullOrEmpty(valuesToSet.email)) { + if (!isValidateEmail(valuesToSet.email)) { + throw new BadRequest( + messages.common_messages_email_validation_error + ); + } + const accountData = await this.findDataToArray( + config.dbCollections.accounts, + { query: { email: valuesToSet.email } } + ); + if (accountData.length > 0 && accountData[0].id !== id) { + throw new BadRequest( + messages.common_messages_user_email_link_error + ); + } + updates.email = valuesToSet.email; + } + if (IsNotNullOrEmpty(valuesToSet.public_key)) { + updates.sessionPublicKey = valuesToSet.public_key; + } + + if (IsNotNullOrEmpty(valuesToSet.bio)) { + updates.bio = valuesToSet.bio; + } + + if (IsNotNullOrEmpty(valuesToSet.country)) { + updates.country = valuesToSet.country; + } + + if (IsNotNullOrEmpty(valuesToSet.achievementId)) { + updates.achievementId = valuesToSet.achievementId; + } + + if (IsNotNullOrEmpty(valuesToSet.ethereumAddress)) { + updates.ethereumAddress = valuesToSet.ethereumAddress; + } + + if (valuesToSet.hasOwnProperty('images')) { + if (IsNotNullOrEmpty(valuesToSet.images.hero)) { + updates.imagesHero = valuesToSet.images.hero; + } + + if (IsNotNullOrEmpty(valuesToSet.images.tiny)) { + updates.imagesTiny = valuesToSet.images.tiny; + } + + if (IsNotNullOrEmpty(valuesToSet.images.thumbnail)) { + updates.imagesThumbnail = valuesToSet.images.thumbnail; + } + } + const result = await this.patchData( + config.dbCollections.accounts, + id, + updates + ); + + const accountResponse = await buildAccountInfo(result); + return Promise.resolve(buildSimpleResponse(accountResponse)); + } else { + throw new NotAuthenticated( + messages.common_messages_unauthorized + ); + } + } else { + throw new BadRequest( + messages.common_messages_target_account_notfound + ); + } + } + + /** + * Delete Account + * + * @remarks + * This method is part of the delete account + * - Request Type - DELETE + * - End Point - API_URL/accounts/{accountId} + * - Access: Admin only + * + * @requires @param acct - Account id (URL param) + * @returns - {status: 'success'} or { status: 'failure', message: 'message'} + * + */ + + async remove(id: NullableId): Promise { + if (IsNotNullOrEmpty(id) && id) { + const account = await this.getData( + config.dbCollections.accounts, + id + ); + + if (IsNotNullOrEmpty(account)) { + this.deleteData(config.dbCollections.accounts, id); + const accounts: AccountInterface[] = await this.findDataToArray( + config.dbCollections.accounts, + { + query: { + $or: [ + { connections: { $in: [account.username] } }, + { friends: { $in: [account.username] } }, + ], + }, + } + ); + + for (const element of accounts) { + SArray.remove(element.connections, account.username); + SArray.remove(element.friends, account.username); + await super.patchData( + config.dbCollections.accounts, + element.id, + { + connections: element.connections, + friends: element.friends, + } + ); + } + + await this.deleteMultipleData(config.dbCollections.domains, { + query: { sponsorAccountId: account.id }, + }); + await this.deleteMultipleData(config.dbCollections.places, { + query: { accountId: account.id }, + }); + + return Promise.resolve({}); + } else { + throw new NotFound( + messages.common_messages_target_account_notfound + ); + } + } else { + throw new BadRequest( + messages.common_messages_target_account_notfound + ); + } + } + + async create(data: any, params?: Params | undefined): Promise { + return Promise.resolve(data); + } +} + diff --git a/vircadia_metaverse_v2_api/src/services/accounts/accounts.hooks.ts b/vircadia_metaverse_v2_api/src/services/accounts/accounts.hooks.ts index 720b71b3..bdf28f58 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/accounts.hooks.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/accounts.hooks.ts @@ -31,6 +31,7 @@ import { joiOptions, joiReadOptions, } from './accounts.joi'; +import app from '../../app'; export default { before: { @@ -47,7 +48,18 @@ export default { Perm.ADMIN, ]), ], - create: [disallow()], + create: [ + async (context: any) => { + const data = context.data; + const accountId = context.params.route.accountId; + const params = context.params; + const response = await app + .service('accounts') + .patch(accountId, data, params); + context.data = response; + return Promise.resolve(context); + }, + ], update: [disallow()], patch: [ authenticate('jwt'), @@ -63,7 +75,14 @@ export default { all: [requestSuccess()], find: [], get: [], - create: [], + create: [ + async (context: any) => { + if (context.result.status === 'failure') { + context.statusCode = 400; + } + return Promise.resolve(context); + }, + ], update: [], patch: [], remove: [], @@ -79,4 +98,3 @@ export default { remove: [], }, }; - diff --git a/vircadia_metaverse_v2_api/src/services/accounts/accounts.joi.ts b/vircadia_metaverse_v2_api/src/services/accounts/accounts.joi.ts index cdc08b57..51ea8eb7 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/accounts.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/accounts.joi.ts @@ -1,78 +1,83 @@ -// Copyright 2020 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -import { HookContext } from '@feathersjs/feathers'; -import Joi from '@hapi/joi'; - -const email = Joi.string().trim().email(); -const public_key = Joi.string().trim(); -const hero = Joi.string().trim(); -const tiny = Joi.string().trim(); -const thumbnail = Joi.string().trim(); -const bio = Joi.string().trim().default(''); -const country = Joi.string().trim(); -const achievementId = Joi.string().trim(); -const ethereumAddress = Joi.string().trim(); - - -const images = Joi.object({ - hero, - tiny, - thumbnail, -}); - -export const patchAccountsSchema = Joi.object().keys({ - email, - public_key, - bio, - images, - country, - achievementId, - ethereumAddress -}); - -const a = Joi.string().trim(); -const v = Joi.string().trim(); -const per_page = Joi.number().integer().positive(); -const page = Joi.number().integer().positive(); -const filter = Joi.string().trim(); -const status = Joi.string().trim(); -const search = Joi.string().trim(); -const acct = Joi.string().trim(); -const asAdmin = Joi.boolean(); - -export const findAccountsSchema = Joi.object().keys({ - a, - v, - per_page, - page, - filter, - status, - search, - acct, - asAdmin, -}); - -export const joiOptions = { convert: true, abortEarly: false }; - -export const joiReadOptions = { - getContext(context: HookContext) { - return context.params.query; - }, - setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); - }, - convert: true, - abortEarly: false, -}; +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { HookContext } from '@feathersjs/feathers'; +import Joi from '@hapi/joi'; + +const email = Joi.string().trim().email(); +const public_key = Joi.string().trim(); +const hero = Joi.string().trim(); +const tiny = Joi.string().trim(); +const thumbnail = Joi.string().trim(); +const bio = Joi.string().trim().default(''); +const country = Joi.string().trim(); +const achievementId = Joi.string().trim(); +const ethereumAddress = Joi.string().trim(); + +const images = Joi.object({ + hero, + tiny, + thumbnail, +}); +const accounts = Joi.object() + .keys({ + email, + public_key, + bio, + images, + country, + achievementId, + ethereumAddress, + }) + .required(); + +export const patchAccountsSchema = Joi.object().keys({ + accounts, +}); + +const a = Joi.string().trim(); +const v = Joi.string().trim(); +const per_page = Joi.number().integer().positive(); +const page = Joi.number().integer().positive(); +const filter = Joi.string().trim(); +const status = Joi.string().trim(); +const search = Joi.string().trim(); +const acct = Joi.string().trim(); +const asAdmin = Joi.boolean(); + +export const findAccountsSchema = Joi.object().keys({ + a, + v, + per_page, + page, + filter, + status, + search, + acct, + asAdmin, +}); + +export const joiOptions = { convert: true, abortEarly: false }; + +export const joiReadOptions = { + getContext(context: HookContext) { + return context.params?.query ?? {}; + }, + setContext(context: HookContext, newValues: any) { + Object.assign(context.params?.query ?? {}, newValues); + }, + convert: true, + abortEarly: false, +}; + diff --git a/vircadia_metaverse_v2_api/src/services/accounts/accounts.service.ts b/vircadia_metaverse_v2_api/src/services/accounts/accounts.service.ts index f4c697f6..c34c1fd7 100644 --- a/vircadia_metaverse_v2_api/src/services/accounts/accounts.service.ts +++ b/vircadia_metaverse_v2_api/src/services/accounts/accounts.service.ts @@ -56,7 +56,12 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/accounts', new Accounts(options, app)); - app.use('/accounts/verify/email', app.service('accounts'), redirect); + app.use('/api/v1/accounts', app.service('accounts')); + + app.use('/api/v1/account', app.service('accounts')); + app.use('/api/v1/account/:accountId', app.service('accounts')); + + app.use('/api/v1/accounts/verify/email', app.service('accounts'), redirect); // Get our initialized service so that we can register hooks const service = app.service('accounts'); diff --git a/vircadia_metaverse_v2_api/src/services/achievement-items/achivement-item.joi.ts b/vircadia_metaverse_v2_api/src/services/achievement-items/achivement-item.joi.ts index 322f99ee..d82e8537 100644 --- a/vircadia_metaverse_v2_api/src/services/achievement-items/achivement-item.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/achievement-items/achivement-item.joi.ts @@ -42,10 +42,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/achievement/achivement.joi.ts b/vircadia_metaverse_v2_api/src/services/achievement/achivement.joi.ts index 26d0d616..e70a27ca 100644 --- a/vircadia_metaverse_v2_api/src/services/achievement/achivement.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/achievement/achivement.joi.ts @@ -36,10 +36,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/connections/connections.joi.ts b/vircadia_metaverse_v2_api/src/services/connections/connections.joi.ts index 044e928f..f797f5f2 100644 --- a/vircadia_metaverse_v2_api/src/services/connections/connections.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/connections/connections.joi.ts @@ -33,10 +33,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/connections/connections.service.ts b/vircadia_metaverse_v2_api/src/services/connections/connections.service.ts index 41f4a30e..142c9028 100644 --- a/vircadia_metaverse_v2_api/src/services/connections/connections.service.ts +++ b/vircadia_metaverse_v2_api/src/services/connections/connections.service.ts @@ -35,6 +35,7 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/connections', new Connections(options, app)); + app.use('api/v1/user/connections', app.service('connections')); // Get our initialized service so that we can register hooks const service = app.service('connections'); diff --git a/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.class.ts b/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.class.ts new file mode 100644 index 00000000..488df6d1 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.class.ts @@ -0,0 +1,253 @@ +import { NullableId } from '@feathersjs/feathers'; +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +('use strict'); + +import { BadRequest } from '@feathersjs/errors'; +import config from '../../appconfig'; +import { DatabaseService } from '../../common/dbservice/DatabaseService'; +import { DatabaseServiceOptions } from '../../common/dbservice/DatabaseServiceOptions'; +import { buildSimpleResponse } from '../../common/responsebuilder/responseBuilder'; +import { RequestType } from '../../common/sets/RequestType'; +import { Application } from '../../declarations'; +import { messages } from '../../utils/messages'; +import { IsNotNullOrEmpty, GenUUID } from '../../utils/Misc'; +import { VKeyedCollection } from '../../utils/vTypes'; +import { extractLoggedInUserFromParams } from '../auth/auth.utils'; +import { RequestInterface } from '../../common/interfaces/RequestInterface'; + +/** + * ConnectionRequest. + * @noInheritDoc + */ +export class ConnectionRequest extends DatabaseService { + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options, app); + } + + /** + * POST Connection + * + * @remarks + * This method is part of the POST connection + * - Request Type - POST + * - End Point - API_URL/api/v1/user/connection_request + * + * @requires -authentication + * @param requestBody - { + * "user_connection_request": { + * "node_id" : "", + * "proposed_node_id" : "", + * } + * } + * @returns - {status: 'success', data:{...}} or { status: 'failure', message: 'message'} + * + */ + + async create(data: any, params?: any): Promise { + const loginUser = extractLoggedInUserFromParams(params); + let pending = true; + if (IsNotNullOrEmpty(loginUser)) { + if (data.user_connection_request) { + const thisNode = data.user_connection_request.node_id; + const otherNode = data.user_connection_request.proposed_node_id; + + const pType = RequestType.HANDSHAKE; + const wayone: VKeyedCollection = {}; + wayone['requesterNodeId'] = thisNode; + wayone['targetNodeId'] = otherNode; + const waytwo: VKeyedCollection = {}; + waytwo['requesterNodeId'] = thisNode; + waytwo['targetNodeId'] = otherNode; + + const previousAsk = await this.findDataToArray( + config.dbCollections.requests, + { + query: { + $or: [wayone, waytwo], + requestType: pType, + }, + } + ); + + if (IsNotNullOrEmpty(previousAsk[0])) { + if (previousAsk[0].requesterNodeId === thisNode) { + if (previousAsk[0].targetAccepted) { + await this.BuildNewConnection(previousAsk[0]); + + pending = false; + const otherAccount = await this.getData( + config.dbCollections.accounts, + previousAsk[0].targetAccountId + ); + const connection = { + new_connection: true, + username: otherAccount + ? otherAccount.username + : 'UNKNOWN', + }; + return Promise.resolve({ + connection, + }); + } else { + throw new BadRequest('Request is not accepted'); + } + } else { + previousAsk[0].targetAccepted = true; + previousAsk[0].targetAccountId = loginUser.id; + + await this.patchData( + config.dbCollections.requests, + previousAsk[0].id, + previousAsk[0] + ); + const areConnected = await this.BuildNewConnection( + previousAsk[0] + ); + if (areConnected) { + const otherAccount = await this.getData( + config.dbCollections.accounts, + previousAsk[0].requestingAccountId + ); + pending = false; + const connection = { + new_connection: true, + username: otherAccount + ? otherAccount.username + : 'UNKNOWN', + }; + + return Promise.resolve({ + connection, + }); + } else { + return new BadRequest('Error making connection'); + } + } + } else { + const aRequest: RequestInterface = {}; + aRequest.id = GenUUID(); + aRequest.expirationTime = new Date(Date.now() + 1000 * 60); + aRequest.whenCreated = new Date(); + aRequest.requestType = RequestType.HANDSHAKE; + aRequest.requesterNodeId = thisNode; + aRequest.requesterAccepted = false; + aRequest.targetNodeId = otherNode; + aRequest.targetAccepted = false; + const expirationMinutes = + config.metaverseServer + .handshake_request_expiration_minutes; + aRequest.expirationTime = new Date( + Date.now() + 1000 * 60 * expirationMinutes + ); + aRequest.requesterAccepted = true; + aRequest.requestingAccountId = loginUser.id; + + await this.createData( + config.dbCollections.requests, + aRequest + ); + } + if (pending) { + // The above didn't create a response so we're just waiting + return Promise.resolve({ + connection: 'pending', + }); + } + } else { + throw new BadRequest( + messages.common_messages_badly_formed_data + ); + } + } + } + + async BuildNewConnection(pRequest: RequestInterface): Promise { + let wasConnected = false; + + const requestingAccount = await this.getData( + config.dbCollections.accounts, + pRequest.requestingAccountId as any + ); + const targetAccount = await this.getData( + config.dbCollections.accounts, + pRequest.targetAccountId as any + ); + + if (requestingAccount && targetAccount) { + requestingAccount.connections.push(targetAccount.username); + + targetAccount.connections.push(requestingAccount.username); + + await this.patchData( + config.dbCollections.accounts, + targetAccount.id, + targetAccount + ); + + await this.patchData( + config.dbCollections.accounts, + requestingAccount.id, + requestingAccount + ); + + wasConnected = true; + } else { + throw new BadRequest( + 'Acceptance for connection but accounts not found' + ); + } + return wasConnected; + } + + /** + * Delete Connection + * + * @remarks + * This method is part of the delete connection + * - Request Type - DELETE + * - End Point - API_URL/api/v1/user/connection_request + * + * @requires -authentication + * @returns - {status: 'success', data:{...}} or { status: 'failure', message: 'message'} + * + */ + + async remove(id: NullableId, params?: any): Promise { + const loginUser = extractLoggedInUserFromParams(params); + if (IsNotNullOrEmpty(loginUser)) { + const criteria: any = { + requestingAccount: loginUser.id, + }; + + const pRequestType = RequestType.HANDSHAKE; + + if (IsNotNullOrEmpty(pRequestType)) { + criteria.requestType = pRequestType; + } + + const deletedCount = await this.deleteMultipleData( + config.dbCollections.requests, + criteria + ); + + return Promise.resolve(buildSimpleResponse(deletedCount)); + } else { + throw new BadRequest(messages.common_messages_not_logged_in); + } + } +} + diff --git a/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.hooks.ts b/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.hooks.ts new file mode 100644 index 00000000..a8da4d6f --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.hooks.ts @@ -0,0 +1,60 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import { HooksObject } from '@feathersjs/feathers'; +import * as authentication from '@feathersjs/authentication'; +import requestFail from '../../hooks/requestFail'; +import requestSuccess from '../../hooks/requestSuccess'; +import validators from '@feathers-plus/validate-joi'; +import { + createConnectionSchema, + joiOptions, + joiReadOptions, +} from './connections_request.joi'; +import { disallow } from 'feathers-hooks-common'; +const { authenticate } = authentication.hooks; + +export default { + before: { + all: [authenticate('jwt')], + find: [disallow()], + get: [disallow()], + create: [validators.form(createConnectionSchema, joiOptions)], + update: [disallow()], + patch: [disallow()], + remove: [], + }, + + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, + + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, +} as HooksObject; diff --git a/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.joi.ts b/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.joi.ts new file mode 100644 index 00000000..a06720c2 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.joi.ts @@ -0,0 +1,37 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Joi from '@hapi/joi'; +import { HookContext } from '@feathersjs/feathers'; + +const node_id = Joi.string().trim().required(); +const proposed_node_id = Joi.string().trim().required(); +const user_connection_request = Joi.object().keys({ node_id, proposed_node_id }); + +export const createConnectionSchema = Joi.object().keys({ + user_connection_request, +}); + +export const joiOptions = { convert: true, abortEarly: false }; + +export const joiReadOptions = { + getContext(context: HookContext) { + return context.params?.query ?? {}; + }, + setContext(context: HookContext, newValues: any) { + Object.assign(context.params?.query ?? {}, newValues); + }, + convert: true, + abortEarly: false, +}; diff --git a/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.service.ts b/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.service.ts new file mode 100644 index 00000000..811c9040 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/connections_request/connections_request.service.ts @@ -0,0 +1,48 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// Initializes the `ConnectionRequest` service on path `/ConnectionRequest` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { ConnectionRequest } from './connections_request.class'; +import hooks from './connections_request.hooks'; + +// Add this service to the service type index +declare module '../../declarations' { + interface ServiceTypes { + connection_request: ConnectionRequest & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get('paginate'), + id: 'id', + multi: ['remove'], + }; + + // Initialize our service with any options it requires + app.use('/connection_request', new ConnectionRequest(options, app)); + app.use( + 'api/v1/user/connection_request', + app.service('connection_request') + ); + + // Get our initialized service so that we can register hooks + const service = app.service('connection_request'); + + service.hooks(hooks); +} diff --git a/vircadia_metaverse_v2_api/src/services/current/current.service.ts b/vircadia_metaverse_v2_api/src/services/current/current.service.ts index dcdee682..3a0903bd 100644 --- a/vircadia_metaverse_v2_api/src/services/current/current.service.ts +++ b/vircadia_metaverse_v2_api/src/services/current/current.service.ts @@ -35,6 +35,7 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/current', new Current(options, app)); + app.use('api/v1/places/current', app.service('current')); // Get our initialized service so that we can register hooks const service = app.service('current'); diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains-fields/domains-field.joi.ts b/vircadia_metaverse_v2_api/src/services/domains/domains-fields/domains-field.joi.ts index d5e58f4f..052185ab 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains-fields/domains-field.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains-fields/domains-field.joi.ts @@ -15,8 +15,8 @@ import Joi from '@hapi/joi'; import { HookContext } from '@feathersjs/feathers'; -const domainId = Joi.string().trim().required(); -const fieldName = Joi.string().trim().required(); +const domainId = Joi.string().trim(); +const fieldName = Joi.string().trim(); export const editDomainSchema = Joi.object().keys({ domainId, @@ -32,10 +32,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.route; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.route, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains-fields/domains-field.service.ts b/vircadia_metaverse_v2_api/src/services/domains/domains-fields/domains-field.service.ts index 6ed2bdef..915d33da 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains-fields/domains-field.service.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains-fields/domains-field.service.ts @@ -35,11 +35,13 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/domainsField', new DomainsFeild(options, app)); - app.use('/domains/:domainId/field/:fieldName', app.service('domainsField')); + app.use( + 'api/v1/domains/:domainId/field/:fieldName', + app.service('domainsField') + ); // Get our initialized service so that we can register hooks const service = app.service('domainsField'); service.hooks(hooks); } - diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.class.ts b/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.class.ts index c19f52d5..352e4a81 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.class.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.class.ts @@ -97,7 +97,7 @@ export class DomainPublickey extends DatabaseService { * */ - async patch(id: NullableId, data: any, params?: any): Promise { + async update(id: NullableId, data: any, params?: any): Promise { const loginUser = extractLoggedInUserFromParams(params); const domainId = params?.route?.domainId; const api_key = params?.body?.api_key; diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.hooks.ts b/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.hooks.ts index 2188430a..42ce230e 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.hooks.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.hooks.ts @@ -33,8 +33,8 @@ export default { find: [validators.form(findDomainPublickeySchema, joiReadOptions)], get: [disallow()], create: [disallow()], - update: [disallow()], - patch: [validators.form(editDomainPublickeySchema, joiOptions)], + update: [validators.form(editDomainPublickeySchema, joiOptions)], + patch: [disallow()], remove: [disallow()], }, diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.joi.ts b/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.joi.ts index 0e4d1a8c..2de7f8f7 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.joi.ts @@ -15,8 +15,8 @@ import { HookContext } from '@feathersjs/feathers'; import Joi from '@hapi/joi'; -const domainId = Joi.string().trim().required(); -const api_key = Joi.string().trim().required(); +const domainId = Joi.string().trim(); +const api_key = Joi.string().trim(); export const editDomainPublickeySchema = Joi.object().keys({ api_key, @@ -30,10 +30,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.route; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.route, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.service.ts b/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.service.ts index 8a7be1d9..ecb44e29 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.service.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains-publickey/domains-publickey.service.ts @@ -56,10 +56,13 @@ export default function (app: Application): void { }, new DomainPublickey(options, app) ); + app.use( + 'api/v1/domains/:domainId/public_key', + app.service('domains/:domainId/public_key') + ); // Get our initialized service so that we can register hooks const service = app.service('domains/:domainId/public_key'); service.hooks(hooks); } - diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains.class.ts b/vircadia_metaverse_v2_api/src/services/domains/domains.class.ts index ad420efa..163afb7a 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains.class.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains.class.ts @@ -164,9 +164,9 @@ export class Domains extends DatabaseService { * @returns - {status: 'success', data:{...}} or { status: 'failure', message: 'message'} * */ - async patch(id: NullableId, data: any): Promise { + async update(id: NullableId, data: any): Promise { if (IsNotNullOrEmpty(id) && id) { - const domainData = data; + const domainData = data.domain; const updateDomain: any = {}; if (IsNotNullOrEmpty(domainData?.name)) { updateDomain.name = domainData.name; @@ -258,4 +258,3 @@ export class Domains extends DatabaseService { } } } - diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains.hooks.ts b/vircadia_metaverse_v2_api/src/services/domains/domains.hooks.ts index b9834693..50cd5d16 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains.hooks.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains.hooks.ts @@ -31,14 +31,14 @@ export default { find: [], get: [], create: [disallow()], - update: [disallow()], - patch: [ + update: [ checkAccessToAccount(config.dbCollections.domains, [ Perm.MANAGER, Perm.ADMIN, ]), validators.form(editDomainSchema, joiOptions), ], + patch: [disallow()], remove: [ checkAccessToAccount(config.dbCollections.domains, [ Perm.SPONSOR, @@ -67,4 +67,3 @@ export default { remove: [], }, }; - diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains.joi.ts b/vircadia_metaverse_v2_api/src/services/domains/domains.joi.ts index 2ef2ec96..2df37acd 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains.joi.ts @@ -33,19 +33,24 @@ const heartbeat = Joi.object({ num_users, anon_users, }); +const domain = Joi.object() + .keys({ + name, + version, + protocol, + network_address, + restricted, + capacity, + description, + maturity, + restriction, + managers, + tags, + heartbeat, + }) + .required(); export const editDomainSchema = Joi.object().keys({ - name, - version, - protocol, - network_address, - restricted, - capacity, - description, - maturity, - restriction, - managers, - tags, - heartbeat, + domain, }); const per_page = Joi.number().integer().positive(); @@ -64,11 +69,12 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, }; + diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains.service.ts b/vircadia_metaverse_v2_api/src/services/domains/domains.service.ts index 3fa9ed8f..8d02739b 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains.service.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains.service.ts @@ -35,10 +35,10 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/domains', new Domains(options, app)); + app.use('/api/v1/domains', app.service('domains')); // Get our initialized service so that we can register hooks const service = app.service('domains'); service.hooks(hooks); } - diff --git a/vircadia_metaverse_v2_api/src/services/domains/domains_temp/domains-temp.service.ts b/vircadia_metaverse_v2_api/src/services/domains/domains_temp/domains-temp.service.ts index 392971fa..37212e80 100644 --- a/vircadia_metaverse_v2_api/src/services/domains/domains_temp/domains-temp.service.ts +++ b/vircadia_metaverse_v2_api/src/services/domains/domains_temp/domains-temp.service.ts @@ -55,9 +55,13 @@ export default function (app: Application): void { new DomainTemp(options, app) ); + app.use( + 'api/v1/domains/temporary', + app.service('domains/create_temporary') + ); + // Get our initialized service so that we can register hooks const service = app.service('domains/create_temporary'); service.hooks(hooks); } - diff --git a/vircadia_metaverse_v2_api/src/services/explore/explore.joi.ts b/vircadia_metaverse_v2_api/src/services/explore/explore.joi.ts index 66f02649..6829ab39 100644 --- a/vircadia_metaverse_v2_api/src/services/explore/explore.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/explore/explore.joi.ts @@ -36,10 +36,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/explore/explore.service.ts b/vircadia_metaverse_v2_api/src/services/explore/explore.service.ts index 898633c0..3bd28d89 100644 --- a/vircadia_metaverse_v2_api/src/services/explore/explore.service.ts +++ b/vircadia_metaverse_v2_api/src/services/explore/explore.service.ts @@ -34,11 +34,17 @@ export default function (app: Application): void { }; // Initialize our service with any options it requires - app.use('/explore.json', new Explore(options, app)); + app.use( + '/explore.json', + new Explore(options, app), + async (request: any, response: any) => { + console.log(response.data); + response.send(response.data.data); + } + ); // Get our initialized service so that we can register hooks const service = app.service('explore.json'); service.hooks(hooks); } - diff --git a/vircadia_metaverse_v2_api/src/services/friends/friends.service.ts b/vircadia_metaverse_v2_api/src/services/friends/friends.service.ts index 87d314b7..5ddd8a2b 100644 --- a/vircadia_metaverse_v2_api/src/services/friends/friends.service.ts +++ b/vircadia_metaverse_v2_api/src/services/friends/friends.service.ts @@ -35,6 +35,7 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/friends', new Friends(options, app)); + app.use('api/v1/user/friends', app.service('friends')); // Get our initialized service so that we can register hooks const service = app.service('friends'); diff --git a/vircadia_metaverse_v2_api/src/services/index.ts b/vircadia_metaverse_v2_api/src/services/index.ts index aa444e6e..331edab7 100644 --- a/vircadia_metaverse_v2_api/src/services/index.ts +++ b/vircadia_metaverse_v2_api/src/services/index.ts @@ -49,12 +49,18 @@ import domainPublicKey from './domains/domains-publickey/domains-publickey.servi import domainTemp from './domains/domains_temp/domains-temp.service'; import sendVerifyMail from './send_verification_mail/send_verify_mail.service'; import verfifyUser from './verify_user/verify_user.service'; +import userPlaces from './place/user-places/user-places.service'; +import usersHeartbeat from './user_heartbeat/user_heartbeat.service'; +import tokens from './tokens/tokens.service'; +import oauthToken from './tokens/oauth_token/oauth-token.service'; import tokenTransfer from './token-transfer/token-transfer.service'; +import ConnectionRequest from './connections_request/connections_request.service'; export default function (app: Application): void { app.configure(auth); app.configure(users); app.configure(friends); + app.configure(accountTokens); app.configure(profiles); app.configure(accounts); app.configure(email); @@ -90,12 +96,16 @@ export default function (app: Application): void { app.configure(domainsField); app.configure(placesField); app.configure(accountField); - app.configure(accountTokens); app.configure(Explore); app.configure(accountPublickey); app.configure(domainTemp); app.configure(sendVerifyMail); app.configure(verfifyUser); app.configure(domainPublicKey); - app.configure(tokenTransfer); + app.configure(userPlaces); + app.configure(usersHeartbeat); + app.configure(tokens); + app.configure(oauthToken); + // app.configure(tokenTransfer); + app.configure(ConnectionRequest); } diff --git a/vircadia_metaverse_v2_api/src/services/inventory/inventory-item/inventory-item.joi.ts b/vircadia_metaverse_v2_api/src/services/inventory/inventory-item/inventory-item.joi.ts index a60d8445..7b96af0b 100644 --- a/vircadia_metaverse_v2_api/src/services/inventory/inventory-item/inventory-item.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/inventory/inventory-item/inventory-item.joi.ts @@ -81,10 +81,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/inventory/userInventory/userInventory.joi.ts b/vircadia_metaverse_v2_api/src/services/inventory/userInventory/userInventory.joi.ts index 4a1154c6..dafe6fda 100644 --- a/vircadia_metaverse_v2_api/src/services/inventory/userInventory/userInventory.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/inventory/userInventory/userInventory.joi.ts @@ -45,10 +45,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/location/location.class.ts b/vircadia_metaverse_v2_api/src/services/location/location.class.ts index 8c3cee40..4f14d020 100644 --- a/vircadia_metaverse_v2_api/src/services/location/location.class.ts +++ b/vircadia_metaverse_v2_api/src/services/location/location.class.ts @@ -25,6 +25,8 @@ import { BadRequest, NotAuthenticated } from '@feathersjs/errors'; import { buildSimpleResponse } from '../../common/responsebuilder/responseBuilder'; import { extractLoggedInUserFromParams } from '../auth/auth.utils'; import { messages } from '../../utils/messages'; +import { Perm } from '../../utils/Perm'; +import { checkAccessToEntity } from '../../utils/Permissions'; /** * Location. @@ -136,13 +138,28 @@ export class Location extends DatabaseService { async find(params?: any): Promise { const loginUser = extractLoggedInUserFromParams(params); + const accountId = params.route?.accountId; + + const accountEntity = await this.getData( + config.dbCollections.accounts, + accountId + ); + if (loginUser) { - const domain = await this.getData( - config.dbCollections.domains, - loginUser.locationDomainId - ); - const location = await buildLocationInfo(loginUser, domain); - return Promise.resolve(buildSimpleResponse({ location })); + if ( + await checkAccessToEntity( + [Perm.OWNER, Perm.FRIEND, Perm.CONNECTION, Perm.ADMIN], + loginUser, + accountEntity + ) + ) { + const domain = await this.getData( + config.dbCollections.domains, + loginUser.locationDomainId + ); + const location = await buildLocationInfo(loginUser, domain); + return Promise.resolve(buildSimpleResponse({ location })); + } } else { throw new NotAuthenticated(messages.common_messages_not_logged_in); } diff --git a/vircadia_metaverse_v2_api/src/services/location/location.service.ts b/vircadia_metaverse_v2_api/src/services/location/location.service.ts index 88d28bcc..a634fbb2 100644 --- a/vircadia_metaverse_v2_api/src/services/location/location.service.ts +++ b/vircadia_metaverse_v2_api/src/services/location/location.service.ts @@ -35,6 +35,9 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/location', new Location(options, app)); + app.use('api/v1/users/location', app.service('location')); + app.use('api/v1/user/location', app.service('location')); + app.use('api/v1/users/:accountId/location', app.service('location')); // Get our initialized service so that we can register hooks const service = app.service('location'); diff --git a/vircadia_metaverse_v2_api/src/services/place/place.class.ts b/vircadia_metaverse_v2_api/src/services/place/place.class.ts index dcab9fae..8275f33b 100644 --- a/vircadia_metaverse_v2_api/src/services/place/place.class.ts +++ b/vircadia_metaverse_v2_api/src/services/place/place.class.ts @@ -232,7 +232,7 @@ export class Place extends DatabaseService { * */ - async patch(id: Id, data: any, params: Params): Promise { + async update(id: Id, data: any, params: Params): Promise { if (params.user) { if (IsNotNullOrEmpty(id) && id) { if (IsNotNullOrEmpty(data)) { @@ -243,17 +243,17 @@ export class Place extends DatabaseService { id ); // The caller specified a domain. Either the same domain or changing - if (data.pointee_query !== getPlaceData.domainId) { - updatePlace.domainId = trim(data.pointee_query); + if (data.place.pointee_query !== getPlaceData.domainId) { + updatePlace.domainId = trim(data.place.pointee_query); } - if (IsNotNullOrEmpty(data?.description)) { - updatePlace.description = trim(data.description); + if (IsNotNullOrEmpty(data?.place.description)) { + updatePlace.description = trim(data.place.description); } - if (IsNotNullOrEmpty(data?.path)) { - updatePlace.path = trim(data.path); + if (IsNotNullOrEmpty(data?.place.path)) { + updatePlace.path = trim(data.place.path); } - if (IsNotNullOrEmpty(data?.thumbnail)) { - updatePlace.thumbnail = trim(data.thumbnail); + if (IsNotNullOrEmpty(data?.place.thumbnail)) { + updatePlace.thumbnail = trim(data.place.thumbnail); } await this.patchData( config.dbCollections.places, @@ -369,4 +369,3 @@ export class Place extends DatabaseService { ); } } - diff --git a/vircadia_metaverse_v2_api/src/services/place/place.hooks.ts b/vircadia_metaverse_v2_api/src/services/place/place.hooks.ts index 4bb3c75e..9c33b362 100644 --- a/vircadia_metaverse_v2_api/src/services/place/place.hooks.ts +++ b/vircadia_metaverse_v2_api/src/services/place/place.hooks.ts @@ -52,8 +52,7 @@ export default { Perm.ADMIN, ]), ], - update: [disallow()], - patch: [ + update: [ authenticate('jwt'), validators.form(updatePlaceSchema, joiOptions), checkAccessToAccount(config.dbCollections.accounts, [ @@ -61,6 +60,7 @@ export default { Perm.ADMIN, ]), ], + patch: [disallow()], remove: [ authenticate('jwt'), checkAccessToAccount(config.dbCollections.accounts, [ diff --git a/vircadia_metaverse_v2_api/src/services/place/place.joi.ts b/vircadia_metaverse_v2_api/src/services/place/place.joi.ts index c95fd6b2..149ec1be 100644 --- a/vircadia_metaverse_v2_api/src/services/place/place.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/place/place.joi.ts @@ -19,6 +19,14 @@ const name = Joi.string().trim().required(); const description = Joi.string().trim().required(); const address = Joi.string().trim().required(); const domainId = Joi.string().trim().required(); +const place = Joi.object() + .keys({ + pointee_query: Joi.string().required(), + path: Joi.string().required(), + description: Joi.string().required(), + thumbnail: Joi.string().required(), + }) + .required(); export const createPlaceSchema = Joi.object().keys({ name, @@ -27,12 +35,7 @@ export const createPlaceSchema = Joi.object().keys({ domainId, }); -export const updatePlaceSchema = Joi.object().keys({ - pointee_query: Joi.string(), - path: Joi.string(), - description: Joi.string(), - thumbnail: Joi.string(), -}); +export const updatePlaceSchema = Joi.object().keys({ place }).required(); const per_page = Joi.number().integer().positive(); const page = Joi.number().integer().positive(); @@ -54,11 +57,12 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, }; + diff --git a/vircadia_metaverse_v2_api/src/services/place/place.service.ts b/vircadia_metaverse_v2_api/src/services/place/place.service.ts index ca8b8fd7..517c2ecc 100644 --- a/vircadia_metaverse_v2_api/src/services/place/place.service.ts +++ b/vircadia_metaverse_v2_api/src/services/place/place.service.ts @@ -35,9 +35,11 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/place', new Place(options, app)); + app.use('/api/v1/places', app.service('place')); // Get our initialized service so that we can register hooks const service = app.service('place'); service.hooks(hooks); } + diff --git a/vircadia_metaverse_v2_api/src/services/place/places-fields/place-field.joi.ts b/vircadia_metaverse_v2_api/src/services/place/places-fields/place-field.joi.ts index f07c6f21..5200700d 100644 --- a/vircadia_metaverse_v2_api/src/services/place/places-fields/place-field.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/place/places-fields/place-field.joi.ts @@ -15,8 +15,8 @@ import Joi from '@hapi/joi'; import { HookContext } from '@feathersjs/feathers'; -const placeId = Joi.string().trim().required(); -const fieldName = Joi.string().trim().required(); +const placeId = Joi.string().trim(); +const fieldName = Joi.string().trim(); export const editDomainSchema = Joi.object().keys({ placeId, @@ -32,10 +32,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.route; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.route, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/place/places-fields/place-field.service.ts b/vircadia_metaverse_v2_api/src/services/place/places-fields/place-field.service.ts index e6d718c2..2c3873ba 100644 --- a/vircadia_metaverse_v2_api/src/services/place/places-fields/place-field.service.ts +++ b/vircadia_metaverse_v2_api/src/services/place/places-fields/place-field.service.ts @@ -35,11 +35,13 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/placesField', new PlacesFeild(options, app)); - app.use('/places/:placeId/field/:fieldName', app.service('placesField')); + app.use( + 'api/v1/places/:placeId/field/:fieldName', + app.service('placesField') + ); // Get our initialized service so that we can register hooks const service = app.service('placesField'); service.hooks(hooks); } - diff --git a/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.class.ts b/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.class.ts new file mode 100644 index 00000000..a53b2872 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.class.ts @@ -0,0 +1,322 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import { DomainInterface } from '../../../common/interfaces/DomainInterface'; +import { Params, NullableId, Id } from '@feathersjs/feathers'; +import { DatabaseService } from '../../../common/dbservice/DatabaseService'; +import { DatabaseServiceOptions } from '../../../common/dbservice/DatabaseServiceOptions'; +import { Application } from '../../../declarations'; +import config from '../../../appconfig'; +import { buildPlaceInfo } from '../../../common/responsebuilder/placesBuilder'; +import { + IsNotNullOrEmpty, + IsNullOrEmpty, + GenUUID, + genRandomString, +} from '../../../utils/Misc'; +import { messages } from '../../../utils/messages'; +import { + buildPaginationResponse, + buildSimpleResponse, +} from '../../../common/responsebuilder/responseBuilder'; +import { extractLoggedInUserFromParams } from '../../auth/auth.utils'; +import { BadRequest, NotAuthenticated, NotFound } from '@feathersjs/errors'; +import { VKeyedCollection } from '../../../utils/vTypes'; +import { PlaceInterface } from '../../../common/interfaces/PlaceInterface'; +import { Maturity } from '../../../common/sets/Maturity'; +import { Perm } from '../../../utils/Perm'; +import { checkAccessToEntity } from '../../../utils/Permissions'; +import { Tokens, TokenScope } from '../../../utils/Tokens'; +import { PlaceFields } from '../../../common/PlaceFields'; +/** + * Places. + * @noInheritDoc + */ +export class PlacesFeild extends DatabaseService { + constructor(options: Partial, app: Application) { + super(options, app); + } + + /** + * GET Places + * + * @remarks + * Return a list of Places. + * - Request Type - GET + * - End Point - API_URL/api/v1/user/places + * + * @requires -authentication + * + * @returns - {"status": "success", "data": {"": [{...},{...},...]} or { status: 'failure', message: 'message'} + * + */ + + async find(params?: Params): Promise { + const loginUser = extractLoggedInUserFromParams(params); + const perPage = parseInt(params?.query?.per_page) || 10; + const page = parseInt(params?.query?.page) || 1; + const skip = (page - 1) * perPage; + + // const places: any[] = []; + const allPlaces = await this.findData(config.dbCollections.places, { + query: { + $skip: skip, + $limit: perPage, + }, + }); + + const placesData: PlaceInterface[] = + allPlaces.data as Array; + + const domainIds = (placesData as Array) + ?.map((item) => item.domainId) + .filter( + (value, index, self) => + self.indexOf(value) === index && value !== undefined + ); + + const domains = await this.findDataToArray( + config.dbCollections.domains, + { + query: { + id: { $in: domainIds }, + sponsorAccountId: loginUser.id, + }, + } + ); + + const places: any[] = []; + + (placesData as Array)?.forEach(async (element) => { + let DomainInterface: DomainInterface | undefined; + for (const domain of domains) { + if (domain && domain.id === element.domainId) { + DomainInterface = domain; + break; + } + } + if (DomainInterface) { + places.push( + await buildPlaceInfo(this, element, DomainInterface) + ); + } + }); + + const data = { + places: places, + 'maturity-categories': Maturity.MaturityCategories, + }; + return Promise.resolve( + buildPaginationResponse( + data, + page, + perPage, + Math.ceil(allPlaces.total / perPage), + allPlaces.total + ) + ); + } + + /** + * GET place + * + * @remarks + * Get the place information for one place. + * - Request Type - GET + * - End Point - API_URL/api/v1/user/places + * @requires @param placeId - Place id (Url param) + * @returns - {"status": "success","data": {"places": [{"placeId": string,"name": string,"displayName": string,"visibility": string,"path": string,"address": string,"description": string,"maturity": string,"tags": string[],"managers": string[],"domain": {"id": domainId,"name": domainName,"sponsorAccountId": string,"network_address": string,"ice_server_address": string,'version': string,'protocol_version': string,'active': boolean,"time_of_last_heartbeat": ISOStringDate,"time_of_last_heartbeat_s": integerUnixTimeSeconds,"num_users": integer},"thumbnail": URL,"images": [ URL, URL, ... ],"current_attendance": number,"current_images": string[],"current_info": string,"current_last_update_time": ISOStringDate,"current_last_update_time_s": integerUnixTimeSeconds},...],"maturity-categories": string[]}} or { status: 'failure', message: 'message'} + * + */ + + async get(id: Id, params?: Params): Promise { + const loginUser = extractLoggedInUserFromParams(params); + if (IsNotNullOrEmpty(id) && id) { + const placeData = await this.getData( + config.dbCollections.places, + id + ); + const DomainData = await this.getData( + config.dbCollections.domains, + placeData.domainId + ); + + const newPlaceData = await buildPlaceInfo( + this, + placeData, + DomainData + ); + + if ( + await checkAccessToEntity( + [Perm.DOMAINACCESS, Perm.ADMIN], + loginUser, + loginUser + ) + ) { + let key: string; + try { + const keyToken = await this.getData( + config.dbCollections.tokens, + placeData.currentAPIKeyTokenId + ); + key = keyToken?.token; + newPlaceData.current_api_key = key; + } catch (err) {} + } + + const data = { + place: newPlaceData, + 'maturity-categories': Maturity.MaturityCategories, + }; + + return Promise.resolve(buildSimpleResponse(data)); + } else { + throw new NotFound(messages.common_messages_no_such_place); + } + } + + /** + * POST place + * + * @remarks + * This method is part of the edit place and set value by field + * - Request Type - POST + * - End Point - API_URL/api/v1/user/places + * + * @requires -authentication + * - Access - DomainAccess , Admin + * @param body = { + * "name": placeName, + * "description": descriptionText, + * "address": addressString, + * "domainId": domainIds + * } + * @returns - {status: 'success', data:{...}} or { status: 'failure', message: 'message'} + * + */ + + async create(data: any, params?: any): Promise { + const loginUser = extractLoggedInUserFromParams(params); + let requestedName: string; + let requestedDesc: string | undefined; + let requestedAddr: string; + let requestedDomainId: string; + + if (data?.place) { + requestedName = data.place.name; + requestedDesc = data.place.description; + requestedAddr = data.place.address; + requestedDomainId = data.place.domainId; + } else { + requestedName = data.place_id; + requestedAddr = data.path; + requestedDomainId = data.domain_id; + } + + if (IsNotNullOrEmpty(loginUser)) { + if (requestedName && requestedAddr && requestedDomainId) { + const aDomain = await this.getData( + config.dbCollections.domains, + requestedDomainId + ); + + if (IsNotNullOrEmpty(aDomain)) { + if ( + await checkAccessToEntity( + [Perm.DOMAINACCESS, Perm.ADMIN], + loginUser, + loginUser + ) + ) { + const ifValid = await PlaceFields.name.validate( + requestedName, + loginUser, + loginUser + ); + if (ifValid) { + const newPlace: any = {}; + newPlace.id = GenUUID(); + newPlace.name = 'UNKNOWN-' + genRandomString(5); + newPlace.path = '/0,0,0/0,0,0,1'; + newPlace.whenCreated = new Date(); + newPlace.currentAttendance = 0; + + const APItoken = await Tokens.createToken( + aDomain.sponsorAccountId, + [TokenScope.PLACE], + -1 + ); + await this.createData( + config.dbCollections.tokens, + APItoken + ); + newPlace.currentAPIKeyTokenId = APItoken.id; + newPlace.name = requestedName; + newPlace.description = requestedDesc; + newPlace.path = requestedAddr; + newPlace.domainId = aDomain.id; + newPlace.maturity = + aDomain.maturity ?? Maturity.UNRATED; + newPlace.managers = [loginUser.username]; + + await this.createData( + config.dbCollections.places, + newPlace + ); + const place = await buildPlaceInfo( + this, + newPlace, + aDomain + ); + return Promise.resolve(buildSimpleResponse(place)); + } + } + } + } + } else { + throw new NotAuthenticated(messages.common_messages_unauthorized); + } + } + + /** + * Delete user place + * + * @remarks + * This method is part of the edit place and set value by field + * - Request Type - DELETE + * - End Point - API_URL/api/v1/user/places/:placeId + * + * @requires -authentication + * - Access - DomainAccess , Admin + * @returns - {status: 'success', data:{...}} or { status: 'failure', message: 'message'} + * + */ + async remove(id: NullableId, params?: Params | undefined): Promise { + if (IsNotNullOrEmpty(params?.user)) { + if (IsNotNullOrEmpty(id) && id) { + await this.deleteData(config.dbCollections.places, id); + } else { + throw new NotFound( + messages.common_messages_target_place_notfound + ); + } + } else { + throw new NotAuthenticated(messages.common_messages_not_logged_in); + } + } +} + diff --git a/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.hooks.ts b/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.hooks.ts new file mode 100644 index 00000000..806332aa --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.hooks.ts @@ -0,0 +1,69 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import * as feathersAuthentication from '@feathersjs/authentication'; +import { disallow } from 'feathers-hooks-common'; +import requestFail from '../../../hooks/requestFail'; +import requestSuccess from '../../../hooks/requestSuccess'; +const { authenticate } = feathersAuthentication.hooks; +import checkAccessToAccount from '../../../hooks/checkAccess'; +import config from '../../../appconfig'; +import { Perm } from '../../../utils/Perm'; +import validators from '@feathers-plus/validate-joi'; +import { joiOptions, createUserPlaceSchema } from './user-places.joi'; +export default { + before: { + all: [authenticate('jwt')], + find: [], + get: [], + create: [ + validators.form(createUserPlaceSchema, joiOptions), + checkAccessToAccount(config.dbCollections.accounts, [ + Perm.DOMAINACCESS, + Perm.ADMIN, + ]), + ], + update: [disallow()], + patch: [disallow()], + remove: [ + checkAccessToAccount(config.dbCollections.accounts, [ + Perm.DOMAINACCESS, + Perm.ADMIN, + ]), + ], + }, + + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, + + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, +}; + diff --git a/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.joi.ts b/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.joi.ts new file mode 100644 index 00000000..25164409 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.joi.ts @@ -0,0 +1,48 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Joi from '@hapi/joi'; +import { HookContext } from '@feathersjs/feathers'; + +const name = Joi.string().trim().required(); +const description = Joi.string().required(); +const address = Joi.string().required(); +const domainId = Joi.string().required(); + +const place = Joi.object() + .keys({ + name, + description, + address, + domainId, + }) + .required(); + +export const createUserPlaceSchema = Joi.object().keys({ + place, +}); + +export const joiOptions = { convert: true, abortEarly: false }; + +export const joiReadOptions = { + getContext(context: HookContext) { + return context.params?.query ?? {}; + }, + setContext(context: HookContext, newValues: any) { + Object.assign(context.params?.query ?? {}, newValues); + }, + convert: true, + abortEarly: false, +}; + diff --git a/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.service.ts b/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.service.ts new file mode 100644 index 00000000..0bed74b6 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/place/user-places/user-places.service.ts @@ -0,0 +1,44 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// Initializes the `domains` service on path `/domains` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../../declarations'; +import { PlacesFeild } from './user-places.class'; +import hooks from './user-places.hooks'; + +// Add this service to the service type index +declare module '../../../declarations' { + interface ServiceTypes { + user_places: PlacesFeild & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get('paginate'), + id: 'id', + }; + + // Initialize our service with any options it requires + app.use('/user_places', new PlacesFeild(options, app)); + app.use('/api/v1/user/places', app.service('user_places')); + + // Get our initialized service so that we can register hooks + const service = app.service('user_places'); + + service.hooks(hooks); +} diff --git a/vircadia_metaverse_v2_api/src/services/profiles/profiles.class.ts b/vircadia_metaverse_v2_api/src/services/profiles/profiles.class.ts index 8db6cd42..d969ab83 100644 --- a/vircadia_metaverse_v2_api/src/services/profiles/profiles.class.ts +++ b/vircadia_metaverse_v2_api/src/services/profiles/profiles.class.ts @@ -99,7 +99,7 @@ export class Profiles extends DatabaseService { }); return Promise.resolve( buildPaginationResponse( - profiles, + { profiles }, page, perPage, Math.ceil(accountData.total / perPage), @@ -154,4 +154,3 @@ export class Profiles extends DatabaseService { } } } - diff --git a/vircadia_metaverse_v2_api/src/services/profiles/profiles.joi.ts b/vircadia_metaverse_v2_api/src/services/profiles/profiles.joi.ts index 57e7739d..945e2398 100644 --- a/vircadia_metaverse_v2_api/src/services/profiles/profiles.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/profiles/profiles.joi.ts @@ -25,10 +25,10 @@ export const findProfileSchema = Joi.object({ export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/profiles/profiles.service.ts b/vircadia_metaverse_v2_api/src/services/profiles/profiles.service.ts index 2a8e9425..4178c9d8 100644 --- a/vircadia_metaverse_v2_api/src/services/profiles/profiles.service.ts +++ b/vircadia_metaverse_v2_api/src/services/profiles/profiles.service.ts @@ -19,6 +19,7 @@ import { ServiceAddons } from '@feathersjs/feathers'; import { Application } from '../../declarations'; import { Profiles } from './profiles.class'; import hooks from './profiles.hooks'; +import { extractLoggedInUserFromParams } from '../auth/auth.utils'; // Add this service to the service type index declare module '../../declarations' { @@ -35,7 +36,7 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/profiles', new Profiles(options, app)); - + app.use('/api/v1/profiles', app.service('profiles')); // Get our initialized service so that we can register hooks const service = app.service('profiles'); diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.class.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.class.ts new file mode 100644 index 00000000..868f030e --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.class.ts @@ -0,0 +1,305 @@ +// Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { Id, NullableId, Params } from '@feathersjs/feathers'; +import { DatabaseService } from '../../../common/dbservice/DatabaseService'; +import { DatabaseServiceOptions } from '../../../common/dbservice/DatabaseServiceOptions'; +import { Application } from '../../../declarations'; +import { IsNotNullOrEmpty, IsNullOrEmpty } from '../../../utils/Misc'; +import { getUtcDate } from '../../../utils/Utils'; +import config from '../../../appconfig'; +import { + buildSimpleResponse, + buildPaginationResponse, +} from '../../../common/responsebuilder/responseBuilder'; +import { messages } from '../../../utils/messages'; +import { BadRequest } from '@feathersjs/errors'; +import { buildMiniGameInfo } from '../../../common/responsebuilder/miniGameBuilder'; + +export class MiniGame extends DatabaseService { + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options, app); + } + + /** + * minigame Item + * + * @remarks + * This method is part of the Create minigame + * - Request Type - POST + * - Access - Admin + * - End Point - API_URL/minigame + * + * @requires - authentication + * @param requestBody - { + * "id": "boss-1", + * "name": "Bob", + * "giver": "gy-gyc-goblin-1", + * "description": "I give quests and sell wares.", + * "prerequisites": + * "minLevel": 1, + * "maxLevel": 3, + * "maxActive": 1, + * "expireAfter": 500000, + * "maxSimultaneous": 5 + * }, + * "attributes": { + * "enemyId": "gy-gyc-goblin-1", + * "enemyHitpoints": "500", + * "enemyPhysicalDamageLevel": 10, + * "enemyPhysicalDefenceLevel": 10 + * } + * } + * @returns - { + * status: 'success', + * data:{ + * "id": "boss-1", + * "name": "Bob", + * "giver": "gy-gyc-goblin-1", + * "description": "I give quests and sell wares.", + * "prerequisites": + * "minLevel": 1, + * "maxLevel": 3, + * "maxActive": 1, + * "expireAfter": 500000, + * "maxSimultaneous": 5 + * }, + * "attributes": { + * "enemyId": "gy-gyc-goblin-1", + * "enemyHitpoints": "500", + * "enemyPhysicalDamageLevel": 10, + * "enemyPhysicalDefenceLevel": 10 + * } + * } or { status: 'failure', message: 'message'} + * + */ + async create(data: any): Promise { + const miniGameList = await this.findDataToArray( + config.dbCollections.minigame, + { + query: { $limit: 1, id: data.id }, + } + ); + + if (miniGameList.length > 0) { + throw new BadRequest( + messages.common_messages_minigame_id_already_exist + ); + } + + data.createdAt = getUtcDate(); + data.updatedAt = getUtcDate(); + + const result = await this.createData( + config.dbCollections.minigame, + data + ); + return Promise.resolve(buildSimpleResponse(buildMiniGameInfo(result))); + } + + /** + * Edit minigame + * + * @remarks + * This method is part of the edit minigame + * - Request Type - PATCH + * - Access - Admin + * - End Point - API_URL/minigame/{:minigamId} + * + * @requires - authentication + * @requires @param minigameId - pass minigameId as a url param + * @param requestBody - { + * "id": "boss-1", + * "name": "Bob", + * "giver": "gy-gyc-goblin-1", + * "description": "I give quests and sell wares.", + * "prerequisites": + * "minLevel": 1, + * "maxLevel": 3, + * "maxActive": 1, + * "expireAfter": 500000, + * "maxSimultaneous": 5 + * }, + * "attributes": { + * "enemyId": "gy-gyc-goblin-1", + * "enemyHitpoints": "500", + * "enemyPhysicalDamageLevel": 10, + * "enemyPhysicalDefenceLevel": 10 + * }, + * "rewards":{ + * "items":[ + * { + * "id":"item-1", + * "quantity":1 + * } + * ], + * "xp":100, + * "goo":100 + * } + * } + * @returns - { + * status: 'success', + * data:{ + * "id": "boss-1", + * "name": "Bob", + * "giver": "gy-gyc-goblin-1", + * "description": "I give quests and sell wares.", + * "prerequisites": + * "minLevel": 1, + * "maxLevel": 3, + * "maxActive": 1, + * "expireAfter": 500000, + * "maxSimultaneous": 5 + * }, + * "attributes": { + * "enemyId": "gy-gyc-goblin-1", + * "enemyHitpoints": "500", + * "enemyPhysicalDamageLevel": 10, + * "enemyPhysicalDefenceLevel": 10 + * }, + * "rewards":{ + * "items":[ + * { + * "id":"item-1", + * "quantity":1 + * } + * ], + * "xp":100, + * "goo":100 + * } + * } or { status: 'failure', message: 'message'} + * + */ + async patch(id: NullableId, data: any): Promise { + if (id === null) { + throw new BadRequest(messages.common_messages_id_missing); + } + try { + data.updatedAt = getUtcDate(); + const result = await this.patchData( + config.dbCollections.minigame, + id, + data + ); + return Promise.resolve( + buildSimpleResponse(buildMiniGameInfo(result)) + ); + } catch (e) { + throw new BadRequest(messages.commmon_messages_minigame_not_found); + } + } + + /** + * Get minigame + * + * @remarks + * This method is part of the get minigame + * - Request Type - GET + * - Access - Admin + * - End Point - API_URL/minigame/{:minigameId} + * + * @requires - authentication + * @requires @param minigameId - pass minigameId as a url param + * @returns - { + * status: 'success', + * data:{ + * "id": "boss-1", + * "name": "Bob", + * "giver": "gy-gyc-goblin-1", + * "description": "I give quests and sell wares.", + * "prerequisites": + * "minLevel": 1, + * "maxLevel": 3, + * "maxActive": 1, + * "expireAfter": 500000, + * "maxSimultaneous": 5 + * }, + * "attributes": { + * "enemyId": "gy-gyc-goblin-1", + * "enemyHitpoints": "500", + * "enemyPhysicalDamageLevel": 10, + * "enemyPhysicalDefenceLevel": 10 + * } + * "rewards":{ + * "items":[ + * { + * "id":"item-1", + * "quantity":1 + * } + * ], + * "xp":100, + * "goo":100 + * } + * } or { status: 'failure', message: 'message'} + */ + + async get(id: Id): Promise { + try { + const result = await this.getData( + config.dbCollections.minigame, + id + ); + return Promise.resolve( + buildSimpleResponse(buildMiniGameInfo(result)) + ); + } catch (e) { + throw new BadRequest(messages.commmon_messages_minigame_not_found); + } + } + + /** + * Returns the minigame list + * + * @remarks + * This method is part of the get list of minigame + * - Request Type - GET + * - Access - Admin + * - End Point - API_URL/minigame?per_page=10&page=1 + * + * @param per_page - page size + * @param page - page number + * @returns - Paginated minigame item list { data:[{...},{...}],current_page:1,per_page:10,total_pages:1,total_entries:5} + * + */ + async find(params?: Params): Promise { + const perPage = parseInt(params?.query?.per_page) || 10; + const page = parseInt(params?.query?.page) || 1; + const skip = (page - 1) * perPage; + const minigameData = await this.findData( + config.dbCollections.minigame, + { + query: { + $skip: skip, + $limit: perPage, + }, + } + ); + const responseData: Array = []; + + (minigameData.data as Array)?.forEach(async (item) => { + responseData.push(buildMiniGameInfo(item)); + }); + return Promise.resolve( + buildPaginationResponse( + responseData, + page, + perPage, + Math.ceil(minigameData.total / perPage), + minigameData.total + ) + ); + } +} + diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.hooks.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.hooks.ts new file mode 100644 index 00000000..12df2f68 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.hooks.ts @@ -0,0 +1,75 @@ +// Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { iff, disallow, isProvider } from 'feathers-hooks-common'; +import { HooksObject } from '@feathersjs/feathers'; +import * as feathersAuthentication from '@feathersjs/authentication'; +const { authenticate } = feathersAuthentication.hooks; +import validators from '@feathers-plus/validate-joi'; +import { + createMiniGameSchema, + patchMiniGameSchema, + findMiniGameSchema, + joiOptions, + joiReadOptions, +} from './mini_game.joi'; +import requestFail from '../../../hooks/requestFail'; +import requestSuccess from '../../../hooks/requestSuccess'; +import isAdminUser from '../../../hooks/isAdminUser'; + +export default { + before: { + all: [iff(isProvider('external'), authenticate('jwt'))], + find: [ + iff(isProvider('external'), authenticate('jwt')), + iff(isAdminUser()).else(disallow('external')), + validators.form(findMiniGameSchema, joiReadOptions), + ], + get: [], + create: [ + iff(isProvider('external'), authenticate('jwt')), + iff(isAdminUser()).else(disallow('external')), + validators.form(createMiniGameSchema, joiOptions), + ], + update: [disallow()], + patch: [ + iff(isProvider('external'), authenticate('jwt')), + iff(isAdminUser()).else(disallow('external')), + validators.form(patchMiniGameSchema, joiOptions), + ], + remove: [disallow()], + }, + + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, + + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, +} as HooksObject; + diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.joi.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.joi.ts new file mode 100644 index 00000000..a55ab579 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.joi.ts @@ -0,0 +1,120 @@ +// Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Joi from '@hapi/joi'; +import { HookContext } from '@feathersjs/feathers'; + +const prerequisitesData = { + minLevel: Joi.number().integer().min(0).required().label('Min Level'), + maxLevel: Joi.number().integer().min(0).required().label('Max Level'), + expireAfter: Joi.number().integer().min(0).required().label('Expire After'), + maxActive: Joi.number().integer().min(0).required().label('Max Active'), + maxSimultaneous: Joi.number() + .integer() + .min(0) + .required() + .label('Max Simultaneous'), +}; + +const attributesData = { + enemyId: Joi.string().trim().required().label('Enemy ID'), + enemyHitpoints: Joi.number() + .integer() + .min(0) + .required() + .label('Enemy Hitpoints'), + enemyPhysicalDamageLevel: Joi.number() + .integer() + .min(0) + .required() + .label('Enemy Physical Damage Level'), + enemyPhysicalDefenceLevel: Joi.number() + .integer() + .min(0) + .required() + .label('Enemy Physical Defence Level'), +}; + +const itemSchema = { + itemId: Joi.string().trim().required().label('Item ID'), + qty: Joi.number().integer().min(0).required().label('Quantity'), +}; + +const rewardSchema = { + items: Joi.array().items(itemSchema).label('Items'), + xp: Joi.number().integer().min(0).label('XP'), + goo: Joi.number().integer().min(0).label('Goo'), +}; + +const id = Joi.string().trim().label('ID'); +const name = Joi.string().trim().label('Name'); +const giver = Joi.string().trim().label('Giver'); +const description = Joi.string().trim().label('Description'); +const prerequisites = Joi.object().keys(prerequisitesData).label('Prequisites'); +const attributes = Joi.object().keys(attributesData).label('Attributes'); +const rewards = Joi.object().keys(rewardSchema).label('Rewards'); + +export const createMiniGameSchema = Joi.object().keys({ + id: id.required(), + name: name.required(), + giver: giver.required(), + description: description.required(), + prerequisites: prerequisites.required(), + attributes: attributes.required(), + rewards: rewards, +}); + +export const patchMiniGameSchema = Joi.object().keys({ + name, + giver, + description, + prerequisites, + attributes, + rewards, +}); + +const per_page = Joi.number().integer().positive().label('Per Page'); +const page = Joi.number().integer().positive().label('Page'); + +export const findMiniGameSchema = Joi.object().keys({ + per_page, + page, +}); + +export const joiOptions = { + convert: true, + abortEarly: false, + errors: { + wrap: { + label: '', + }, + }, +}; + +export const joiReadOptions = { + getContext(context: HookContext) { + return context.params?.query ?? {}; + }, + setContext(context: HookContext, newValues: any) { + Object.assign(context.params?.query ?? {}, newValues); + }, + convert: true, + abortEarly: false, + errors: { + wrap: { + label: '', + }, + }, +}; diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.service.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.service.ts new file mode 100644 index 00000000..0590e537 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game/mini_game.service.ts @@ -0,0 +1,43 @@ +// Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Initializes the `minigame` service on path `/minigame` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../../declarations'; +import { MiniGame } from './mini_game.class'; +import hooks from './mini_game.hooks'; + +// Add this service to the service type index +declare module '../../../declarations' { + interface ServiceTypes { + minigame: MiniGame & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get('paginate'), + id: 'id', + }; + + // Initialize our service with any options it requires + app.use('/minigame', new MiniGame(options, app)); + + // Get our initialized service so that we can register hooks + const service = app.service('minigame'); + + service.hooks(hooks); +} + diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.class.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.class.ts new file mode 100644 index 00000000..a03dc029 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.class.ts @@ -0,0 +1,116 @@ +// Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import { BadRequest } from '@feathersjs/errors'; +import { NullableId } from '@feathersjs/feathers'; +import config from '../../../appconfig'; +import { DatabaseService } from '../../../common/dbservice/DatabaseService'; +import { DatabaseServiceOptions } from '../../../common/dbservice/DatabaseServiceOptions'; +import { Application } from '../../../declarations'; +import { messages } from '../../../utils/messages'; +import { IsNotNullOrEmpty } from '../../../utils/Misc'; + +export class MiniGameQuest extends DatabaseService { + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options, app); + } + + /** + * Edit minigameProgress in quest + * + * @remarks + * This method is part of the edit minigame + * - Request Type - PATCH + * - End Point - API_URL/minigame/{:questId} + * + * @requires - authentication + * @requires @param questId - pass questId as a url param + * @param requestBody - { + * "miniGameId": "boss-1", + * "score":200 + * } + * @returns - { + * status: 'success', + * + * } or { status: 'failure', message: 'message'} + * + */ + async patch(id: NullableId, data: any): Promise { + if (id === null) { + throw new BadRequest(messages.common_messages_id_missing); + } + + const questData = await this.findDataToArray( + config.dbCollections.quest, + { query: { id: id } } + ); + + if (IsNotNullOrEmpty(questData)) { + if (IsNotNullOrEmpty(questData[0].miniGameProgress)) { + if ( + questData[0].miniGameProgress.miniGameId == data.miniGameId + ) { + const miniGameProgress = { + miniGameProgress: data, + }; + await this.app + ?.service('quest') + ?.patch(id, miniGameProgress); + } else { + throw new BadRequest( + messages.common_messages_minigame_not_associated + ); + } + } else { + const questItem = await this.findDataToArray( + config.dbCollections.questItem, + { + query: { + id: questData[0].questId, + }, + } + ); + + if (IsNotNullOrEmpty(questItem)) { + if ( + IsNotNullOrEmpty(questItem[0].miniGameRequirements) && + questItem[0].miniGameRequirements.miniGameId == + data.miniGameId + ) { + const miniGameProgress = { + miniGameProgress: data, + }; + await this.app + ?.service('quest') + ?.patch(id, miniGameProgress); + } else { + throw new BadRequest( + messages.common_messages_minigame_not_associated_questItem + ); + } + } else { + throw new BadRequest( + messages.common_messages_quest_item_not_found + ); + } + } + + return Promise.resolve({}); + } else { + throw new BadRequest(messages.common_messages_quest_not_found); + } + } +} + diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.hooks.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.hooks.ts new file mode 100644 index 00000000..f2ab56ff --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.hooks.ts @@ -0,0 +1,59 @@ +// Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import validators from '@feathers-plus/validate-joi'; +import * as feathersAuthentication from '@feathersjs/authentication'; +import { HooksObject } from '@feathersjs/feathers'; +import { disallow, iff, isProvider } from 'feathers-hooks-common'; +import requestFail from '../../../hooks/requestFail'; +import requestSuccess from '../../../hooks/requestSuccess'; +import { joiOptions, patchMiniGameSchema } from './minigame_quest.joi'; +const { authenticate } = feathersAuthentication.hooks; + +export default { + before: { + all: [iff(isProvider('external'), authenticate('jwt'))], + find: [disallow()], + get: [disallow()], + create: [disallow()], + update: [disallow()], + patch: [ + iff(isProvider('external'), authenticate('jwt')), + validators.form(patchMiniGameSchema, joiOptions), + ], + remove: [disallow()], + }, + + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, + + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, +} as HooksObject; + diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.joi.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.joi.ts new file mode 100644 index 00000000..2e36a44a --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.joi.ts @@ -0,0 +1,52 @@ +// Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Joi from '@hapi/joi'; +import { HookContext } from '@feathersjs/feathers'; + +const id = Joi.string().trim().required().label('ID'); +const score = Joi.number().integer().min(0).required().label('Score'); +const miniGameId = Joi.string().trim().label('Mini Game ID'); + +export const patchMiniGameSchema = Joi.object().keys({ + miniGameId, + score, +}); + +export const joiOptions = { + errors: { + wrap: { + label: '', + }, + }, + convert: true, + abortEarly: false, +}; + +export const joiReadOptions = { + getContext(context: HookContext) { + return context.params?.query ?? {}; + }, + setContext(context: HookContext, newValues: any) { + Object.assign(context.params?.query ?? {}, newValues); + }, + convert: true, + abortEarly: false, + errors: { + wrap: { + label: '', + }, + }, +}; diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.service.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.service.ts new file mode 100644 index 00000000..3703b85c --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/mini_game_quest/minigame_quest.service.ts @@ -0,0 +1,43 @@ +// Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Initializes the `minigame_quest` service on path `/minigame_quest` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../../declarations'; +import { MiniGameQuest } from './minigame_quest.class'; +import hooks from './minigame_quest.hooks'; + +// Add this service to the service type index +declare module '../../../declarations' { + interface ServiceTypes { + minigame_quest: MiniGameQuest & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get('paginate'), + id: 'id', + }; + + // Initialize our service with any options it requires + app.use('/minigame_quest', new MiniGameQuest(options, app)); + + // Get our initialized service so that we can register hooks + const service = app.service('minigame_quest'); + + service.hooks(hooks); +} + diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/npc/npc.joi.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/npc/npc.joi.ts index 4ea9a575..4923c247 100644 --- a/vircadia_metaverse_v2_api/src/services/quest_apis/npc/npc.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/npc/npc.joi.ts @@ -69,10 +69,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/quest-item/quest-item.joi.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/quest-item/quest-item.joi.ts index bff49884..bcb2c3cd 100644 --- a/vircadia_metaverse_v2_api/src/services/quest_apis/quest-item/quest-item.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/quest-item/quest-item.joi.ts @@ -87,10 +87,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/quest/quest.joi.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/quest/quest.joi.ts index 05d22c90..54ac7545 100644 --- a/vircadia_metaverse_v2_api/src/services/quest_apis/quest/quest.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/quest/quest.joi.ts @@ -16,21 +16,28 @@ import Joi from '@hapi/joi'; import { HookContext } from '@feathersjs/feathers'; const progressItem = { - itemId: Joi.string().trim().required(), - qty: Joi.number().integer().min(0).required(), + itemId: Joi.string().trim().required().label('Item Id'), + qty: Joi.number().integer().min(0).required().label('Quantity'), }; -const questId = Joi.string().trim(); -const ownerId = Joi.string().uuid().trim(); -const expiresOn = Joi.date(); -const isAccepted = Joi.boolean(); -const isUnique = Joi.boolean(); -const npcProgress = Joi.array().items(progressItem); -const miniGameProgress = Joi.array().items(progressItem); -const isCompleted = Joi.boolean(); -const isActive = Joi.boolean(); -const status = Joi.string().trim(); -const questIds = Joi.array(); +const miniGame = { + miniGameId: Joi.string().trim().label('Mini Game Id'), + score: Joi.number().integer().min(0).label('Score'), +}; + +const questId = Joi.string().trim().label('Quest Id'); +const ownerId = Joi.string().uuid().trim().label('Owner Id'); +const expiresOn = Joi.date().label('Expires On'); +const isAccepted = Joi.boolean().label('Is Accepted'); +const isUnique = Joi.boolean().label('Is Unique'); +const npcProgress = Joi.array().items(progressItem).label('Npc Progress'); +const miniGameProgress = Joi.object() + .keys(miniGame) + .label('Mini game progress'); +const isCompleted = Joi.boolean().label('Is Completed'); +const isActive = Joi.boolean().label('Is Active'); +const status = Joi.string().trim().label('Status'); +const questIds = Joi.array().label('Quest Ids'); export const createQuestSchema = Joi.object().keys({ questId: questId.required(), @@ -59,15 +66,28 @@ export const getQuestSchema = Joi.object().keys({ status, }); -export const joiOptions = { convert: true, abortEarly: false }; +export const joiOptions = { + errors: { + wrap: { + label: '', + }, + }, + convert: true, + abortEarly: false, +}; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, + errors: { + wrap: { + label: '', + }, + }, }; diff --git a/vircadia_metaverse_v2_api/src/services/quest_apis/services.ts b/vircadia_metaverse_v2_api/src/services/quest_apis/services.ts index 3b92c6ba..468b0fbc 100644 --- a/vircadia_metaverse_v2_api/src/services/quest_apis/services.ts +++ b/vircadia_metaverse_v2_api/src/services/quest_apis/services.ts @@ -17,4 +17,7 @@ import Quest from './quest/quest.service'; import QuestItem from './quest-item/quest-item.service'; import Npc from './npc/npc.service'; -export default [Quest, QuestItem, Npc]; +import MiniGame from './mini_game/mini_game.service'; +import MiniGameQuest from './mini_game_quest/minigame_quest.service'; +export default [Quest, QuestItem, Npc, MiniGame, MiniGameQuest]; + diff --git a/vircadia_metaverse_v2_api/src/services/rewards/reward.joi.ts b/vircadia_metaverse_v2_api/src/services/rewards/reward.joi.ts index 0b6e07a7..c09c258a 100644 --- a/vircadia_metaverse_v2_api/src/services/rewards/reward.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/rewards/reward.joi.ts @@ -19,10 +19,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/send_verification_mail/send_verify_mail.joi.ts b/vircadia_metaverse_v2_api/src/services/send_verification_mail/send_verify_mail.joi.ts index 129ffcb8..3609cd13 100644 --- a/vircadia_metaverse_v2_api/src/services/send_verification_mail/send_verify_mail.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/send_verification_mail/send_verify_mail.joi.ts @@ -25,10 +25,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/stats/category/stat-category.joi.ts b/vircadia_metaverse_v2_api/src/services/stats/category/stat-category.joi.ts index 664e6ed5..a8cb440d 100644 --- a/vircadia_metaverse_v2_api/src/services/stats/category/stat-category.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/stats/category/stat-category.joi.ts @@ -24,10 +24,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/stats/category/stat-category.service.ts b/vircadia_metaverse_v2_api/src/services/stats/category/stat-category.service.ts index 20d0f9ec..a3c0db57 100644 --- a/vircadia_metaverse_v2_api/src/services/stats/category/stat-category.service.ts +++ b/vircadia_metaverse_v2_api/src/services/stats/category/stat-category.service.ts @@ -35,6 +35,7 @@ export default (app: Application) => { // Initialize our service with any options it requires app.use('/stats/category', new StatCategory(options, app)); + app.use('api/v1/stats/category', app.service('stats/category')); // Get our initialized service so that we can register hooks const service = app.service('stats/category'); diff --git a/vircadia_metaverse_v2_api/src/services/stats/list/stat-list.service.ts b/vircadia_metaverse_v2_api/src/services/stats/list/stat-list.service.ts index 52728335..73c54399 100644 --- a/vircadia_metaverse_v2_api/src/services/stats/list/stat-list.service.ts +++ b/vircadia_metaverse_v2_api/src/services/stats/list/stat-list.service.ts @@ -36,6 +36,7 @@ export default (app: Application) => { initMonitoring(); // Initialize our service with any options it requires app.use('/stats/list', new StatList(options, app)); + app.use('api/v1/stats/list', app.service('stats/list')); // Get our initialized service so that we can register hooks const service = app.service('stats/list'); diff --git a/vircadia_metaverse_v2_api/src/services/stats/stat/stat.class.ts b/vircadia_metaverse_v2_api/src/services/stats/stat/stat.class.ts index d5d2030d..bbfb7afc 100644 --- a/vircadia_metaverse_v2_api/src/services/stats/stat/stat.class.ts +++ b/vircadia_metaverse_v2_api/src/services/stats/stat/stat.class.ts @@ -78,5 +78,4 @@ export class Stat extends DatabaseService { throw new NotAuthenticated(messages.common_messages_unauthorized); } } -} - +} \ No newline at end of file diff --git a/vircadia_metaverse_v2_api/src/services/stats/stat/stat.joi.ts b/vircadia_metaverse_v2_api/src/services/stats/stat/stat.joi.ts index 34b204d9..9c286097 100644 --- a/vircadia_metaverse_v2_api/src/services/stats/stat/stat.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/stats/stat/stat.joi.ts @@ -24,10 +24,10 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, diff --git a/vircadia_metaverse_v2_api/src/services/stats/stat/stat.service.ts b/vircadia_metaverse_v2_api/src/services/stats/stat/stat.service.ts index 8b743eab..d58b28c8 100644 --- a/vircadia_metaverse_v2_api/src/services/stats/stat/stat.service.ts +++ b/vircadia_metaverse_v2_api/src/services/stats/stat/stat.service.ts @@ -35,10 +35,10 @@ export default (app: Application) => { // Initialize our service with any options it requires app.use('/stats/stat', new Stat(options, app)); + app.use('api/v1/stats/stat', app.service('stats/stat')); // Get our initialized service so that we can register hooks const service = app.service('stats/stat'); service.hooks(hooks); }; - diff --git a/vircadia_metaverse_v2_api/src/services/tokens/oauth_token/oauth-token.class.ts b/vircadia_metaverse_v2_api/src/services/tokens/oauth_token/oauth-token.class.ts new file mode 100644 index 00000000..736cf914 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/tokens/oauth_token/oauth-token.class.ts @@ -0,0 +1,188 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import { DatabaseService } from '../../../common/dbservice/DatabaseService'; +import { DatabaseServiceOptions } from '../../../common/dbservice/DatabaseServiceOptions'; +import { Application } from '../../../declarations'; +import config from '../../../appconfig'; +import { AccountInterface } from '../../../common/interfaces/AccountInterface'; +import { IsNotNullOrEmpty, IsNullOrEmpty } from '../../../utils/Misc'; +import { messages } from '../../../utils/messages'; +import { extractLoggedInUserFromParams } from '../../auth/auth.utils'; +import { BadRequest, NotAuthenticated, NotFound } from '@feathersjs/errors'; +import { VKeyedCollection } from '../../../utils/vTypes'; +import { Tokens, TokenScope } from '../../../utils/Tokens'; +import { isEnabled, validatePassword } from '../../../utils/Utils'; +import { AuthToken } from '../../../common/interfaces/AuthToken'; +/** + * oauth token. + * @noInheritDoc + */ +export class AccountFeild extends DatabaseService { + constructor(options: Partial, app: Application) { + super(options, app); + } + + /** + * POST oauth token + * + * @remarks + * This method is return an initial access token for a user + * - Request Type - POST + * - End Point - API_URL/oauth/token + * + * @requires -authentication + + * @param body = { + "grant_type": "password", + "username": "Metaverse13", + "password": "Metaverse13" + } + or + { + "grant_type": "refresh_token", + "refresh_token": "59bf706e-10c4-4ec2-b7fd-c130b032954a", + "scope": "owner" + } + * @returns - {status: 'success', data:{...}} or { status: 'failure', message: 'message'} + * + */ + + async create(data: any, params?: any): Promise { + const loginUser = extractLoggedInUserFromParams(params); + const accessGrantType = data.grant_type; + switch (accessGrantType) { + case 'password': { + const userName = data.username; + const userPassword = data.password; + const userScope: string = data.scope ?? TokenScope.OWNER; + if (TokenScope.KnownScope(userScope)) { + const tokenData = await this.app + ?.service('authentication') + .create( + { + username: userName, + password: userPassword, + strategy: 'local', + }, + params + ); + return Promise.resolve(tokenData); + } else { + throw new BadRequest( + messages.common_messages_invalid_scope + ); + } + break; + } + case 'authorization_code': { + throw new BadRequest( + 'Do not know what to do with an authorization_code' + ); + break; + } + case 'refresh_token': { + const refreshingToken = data.refresh_token; + const refreshToken = await this.findDataToArray( + config.dbCollections.tokens, + { query: { refreshToken: refreshingToken } } + ); + + if (Tokens.hasNotExpired(refreshToken[0])) { + const tokenInfo = await this.findDataToArray( + config.dbCollections.tokens, + { + query: { + refreshToken: refreshingToken, + accountId: loginUser.id, + }, + } + ); + + if (IsNotNullOrEmpty(tokenInfo)) { + const requestingAccount = await this.findDataToArray( + config.dbCollections.accounts, + { query: { id: tokenInfo[0].accountId } } + ); + if ( + IsNotNullOrEmpty(requestingAccount) && + refreshToken[0].accountId === + requestingAccount[0].id + ) { + // refresh token has not expired and requestor is owner of the token so make new + const newToken = await Tokens.createToken( + loginUser.id, + refreshToken[0].scope + ); + + await this.createData( + config.dbCollections.tokens, + newToken + ); + + return Promise.resolve( + this.buildOAuthResponseBody( + requestingAccount[0], + newToken + ) + ); + } else { + throw new BadRequest( + 'refresh token not owned by accessing account' + ); + } + } else { + throw new BadRequest( + messages.common_messages_target_account_notfound + ); + } + } else { + throw new BadRequest( + messages.common_messages_refresh_token_expired + ); + } + break; + } + default: { + throw new BadRequest('Unknown grant_type :' + accessGrantType); + break; + } + } + } + + buildOAuthResponseBody( + pAcct: AccountInterface, + pToken: AuthToken + ): VKeyedCollection { + const body: VKeyedCollection = { + access_token: pToken.token, + token_type: 'Bearer', + expires_in: + pToken.expirationTime.valueOf() / 1000 - + pToken.whenCreated.valueOf() / 1000, + refresh_token: pToken.refreshToken, + scope: pToken.scope[0], + created_at: pToken.whenCreated.valueOf() / 1000, + }; + if (pAcct) { + (body.account_id = pAcct.id), + (body.account_name = pAcct.username), + (body.account_roles = pAcct.roles); + } + return body; + } +} + diff --git a/vircadia_metaverse_v2_api/src/services/tokens/oauth_token/oauth-token.hooks.ts b/vircadia_metaverse_v2_api/src/services/tokens/oauth_token/oauth-token.hooks.ts new file mode 100644 index 00000000..6dbd3acd --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/tokens/oauth_token/oauth-token.hooks.ts @@ -0,0 +1,54 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import * as feathersAuthentication from '@feathersjs/authentication'; +const { authenticate } = feathersAuthentication.hooks; +import requestSuccess from '../../../hooks/requestSuccess'; +import requestFail from '../../../hooks/requestFail'; +import { disallow } from 'feathers-hooks-common'; + +export default { + before: { + all: [authenticate('jwt')], + find: [disallow()], + get: [disallow()], + create: [], + update: [disallow()], + patch: [disallow()], + remove: [disallow()], + }, + + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, + + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, +}; + diff --git a/vircadia_metaverse_v2_api/src/services/tokens/oauth_token/oauth-token.service.ts b/vircadia_metaverse_v2_api/src/services/tokens/oauth_token/oauth-token.service.ts new file mode 100644 index 00000000..a7288cc5 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/tokens/oauth_token/oauth-token.service.ts @@ -0,0 +1,44 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// Initializes the `domains` service on path `/domains` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../../declarations'; +import { AccountFeild } from './oauth-token.class'; +import hooks from './oauth-token.hooks'; + +// Add this service to the service type index +declare module '../../../declarations' { + interface ServiceTypes { + 'oauth/token': AccountFeild & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get('paginate'), + id: 'id', + }; + + // Initialize our service with any options it requires + app.use('/oauth/token', new AccountFeild(options, app)); + + // Get our initialized service so that we can register hooks + const service = app.service('oauth/token'); + + service.hooks(hooks); +} + diff --git a/vircadia_metaverse_v2_api/src/services/tokens/tokens.class.ts b/vircadia_metaverse_v2_api/src/services/tokens/tokens.class.ts new file mode 100644 index 00000000..0f253ef1 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/tokens/tokens.class.ts @@ -0,0 +1,134 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import { DatabaseService } from '../../common/dbservice/DatabaseService'; +import { DatabaseServiceOptions } from '../../common/dbservice/DatabaseServiceOptions'; +import { Application } from '../../declarations'; +import config from '../../appconfig'; +import fsPromises from 'fs/promises'; +import { + GenUUID, + IsNotNullOrEmpty, + IsNullOrEmpty, + Clamp, +} from '../../utils/Misc'; +import path from 'path'; +import { TokenScope, Tokens } from '../../utils/Tokens'; +import { BadRequest, NotAuthenticated } from '@feathersjs/errors'; +import { extractLoggedInUserFromParams } from '../auth/auth.utils'; +import { messages } from '../../utils/messages'; +import { Params } from '@feathersjs/feathers'; +import { buildSimpleResponse } from '../../common/responsebuilder/responseBuilder'; + +/** + * token. + * @noInheritDoc + */ +export class Token extends DatabaseService { + application: Application; + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options, app); + this.application = app; + } + + /** + * Returns the new token + * + * @remarks + * This method is part of the get new token based on scope + * - Request Type - POST + * - End Point - API_URL/api/v1/token/new + * + * @param scope - scope + * @returns - : { data:{status:success:[{...},{...}]},} + * + */ + + async create(data: any, params?: any): Promise { + const loginUser = extractLoggedInUserFromParams(params); + if (IsNotNullOrEmpty(loginUser)) { + let scope = TokenScope.OWNER; + + if (params?.query?.scope) { + scope = params?.query?.scope; + } + if (TokenScope.KnownScope(scope)) { + const tokenInfo = await Tokens.createToken(loginUser.id, [ + scope, + ]); + + const result = await this.createData( + config.dbCollections.tokens, + tokenInfo + ); + + const tokenData = { + token: tokenInfo.token, + token_id: tokenInfo.id, + refresh_token: tokenInfo.refreshToken, + token_expiration_seconds: + (tokenInfo.expirationTime.valueOf() - + tokenInfo.whenCreated.valueOf()) / + 1000, + account_name: loginUser.username, + account_roles: loginUser.roles, + account_id: loginUser.id, + }; + return Promise.resolve(buildSimpleResponse(tokenData)); + } + } else { + throw new NotAuthenticated(messages.common_messages_unauthorized); + } + } + + /** + * Returns the Users + * + * @remarks + * This method is part of the get list of users + * - Request Type - GET + * - End Point - API_URL/user/tokens/new + * + * @returns - + * + */ + async find(params?: Params): Promise { + const loginUser = extractLoggedInUserFromParams(params); + if (IsNotNullOrEmpty(loginUser)) { + const forDomainServer = params?.query?.for_domain_server; + const scope = forDomainServer + ? TokenScope.DOMAIN + : TokenScope.OWNER; + const tokenInfo = await Tokens.createToken(loginUser.id, [scope]); + await this.createData(config.dbCollections.tokens, tokenInfo); + const body = `

Your domain's access token is ${tokenInfo.token}

`; + return Promise.resolve({ body }); + } else { + // if the user is not logged in, go to a page to login and set things up + const tokengenURL = path.join( + __dirname, + '../..', + config.metaverseServer.tokengen_url + ); + // tokengenURL = tokengenURL.replace('METAVERSE_SERVER_URL', Config.metaverse['metaverse-server-url']); + // tokengenURL = tokengenURL.replace('DASHBOARD_URL', Config.metaverse['dashboard-url']); + const htmlBody = await fsPromises.readFile(tokengenURL, 'utf-8'); + return Promise.resolve({ htmlBody }); + } + } +} + diff --git a/vircadia_metaverse_v2_api/src/services/tokens/tokens.hooks.ts b/vircadia_metaverse_v2_api/src/services/tokens/tokens.hooks.ts new file mode 100644 index 00000000..f3c48a61 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/tokens/tokens.hooks.ts @@ -0,0 +1,63 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import { HooksObject } from '@feathersjs/feathers'; +import * as local from '@feathersjs/authentication-local'; +import requestFail from '../../hooks/requestFail'; +import requestSuccess from '../../hooks/requestSuccess'; +import isHasAuthToken from '../../hooks/isHasAuthToken'; +import { iff } from 'feathers-hooks-common'; +import * as authentication from '@feathersjs/authentication'; +import { disallow } from 'feathers-hooks-common'; +import validate from '@feathers-plus/validate-joi'; +import { findTokenSchema, joiOptions, joiReadOptions } from './tokens.joi'; +const { authenticate } = authentication.hooks; + +export default { + before: { + all: [], + find: [iff(isHasAuthToken(), authenticate('jwt'))], + get: [disallow()], + create: [ + authenticate('jwt'), + validate.form(findTokenSchema, joiReadOptions), + ], + update: [disallow()], + patch: [disallow()], + remove: [disallow()], + }, + + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, + + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, +} as HooksObject; + diff --git a/vircadia_metaverse_v2_api/src/services/tokens/tokens.joi.ts b/vircadia_metaverse_v2_api/src/services/tokens/tokens.joi.ts new file mode 100644 index 00000000..facdaf9a --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/tokens/tokens.joi.ts @@ -0,0 +1,36 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Joi from '@hapi/joi'; +import { HookContext } from '@feathersjs/feathers'; + +const scope = Joi.string(); + +export const findTokenSchema = Joi.object().keys({ + scope, +}); + +export const joiOptions = { convert: true, abortEarly: false }; + +export const joiReadOptions = { + getContext(context: HookContext) { + return context.params?.query ?? {}; + }, + setContext(context: HookContext, newValues: any) { + Object.assign(context.params?.query ?? {}, newValues); + }, + convert: true, + abortEarly: false, +}; + diff --git a/vircadia_metaverse_v2_api/src/services/tokens/tokens.service.ts b/vircadia_metaverse_v2_api/src/services/tokens/tokens.service.ts new file mode 100644 index 00000000..1caf10df --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/tokens/tokens.service.ts @@ -0,0 +1,54 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// Initializes the `users` service on path `/users` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { Token } from './tokens.class'; +import hooks from './tokens.hooks'; + +// Add this service to the service type index +declare module '../../declarations' { + interface ServiceTypes { + 'token/new': Token & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get('paginate'), + id: 'id', + }; + + // Initialize our service with any options it requires + app.use('/token/new', new Token(options, app)); + app.use('/api/v1/token/new', app.service('token/new')); + app.use( + '/user/tokens/new', + app.service('token/new'), + async (request: any, response: any) => { + response.set('Content-Type', 'text/html'); + response.data.htmlBody && response.send(response.data.htmlBody); + response.data.body && response.send(response.data.body); + } + ); + + // Get our initialized service so that we can register hooks + const service = app.service('token/new'); + + service.hooks(hooks); +} + diff --git a/vircadia_metaverse_v2_api/src/services/user_heartbeat/user_heartbeat.class.ts b/vircadia_metaverse_v2_api/src/services/user_heartbeat/user_heartbeat.class.ts new file mode 100644 index 00000000..b3d7b151 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/user_heartbeat/user_heartbeat.class.ts @@ -0,0 +1,138 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import { BadRequest, NotAuthenticated } from '@feathersjs/errors'; +import { NullableId, Paginated, Params } from '@feathersjs/feathers'; +import { DatabaseService } from '../../common/dbservice/DatabaseService'; +import { DatabaseServiceOptions } from '../../common/dbservice/DatabaseServiceOptions'; +import { buildSimpleResponse } from '../../common/responsebuilder/responseBuilder'; +import { Application } from '../../declarations'; +import { IsNotNullOrEmpty } from '../../utils/Misc'; +import { extractLoggedInUserFromParams } from '../auth/auth.utils'; +import { messages } from '../../utils/messages'; +import { VKeyedCollection } from '../../utils/vTypes'; +import { AccountFields } from '../../common/AccountFields'; +import { checkAccessToEntity } from '../../utils/Permissions'; +import config from '../../appconfig'; + +/** + * Users. + * @noInheritDoc + */ +export class UsersHeartbeat extends DatabaseService { + application: Application; + //eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(options: Partial, app: Application) { + super(options, app); + this.application = app; + } + + /** + * Update location + * + * @remarks + * This method is part of the update location + * - Request Type - PUT + * - End Point - API_URL/api/v1/user/heartbeat + * + * @requires -authentication + * @param requestBody - { + * "location":{ + * "connected" : "true", + * "path" : "" + * "place_id" : "", + * "domain_id" : "", + * "network_address": "", + * "node_id" : "", + * "availability":"" + * } + * } + * @returns - {"status": "success","data": {"location": { "root": {"domain": {"id":"","network_address":"","network_port":"","ice_server_address":"","name":""},"name": placeName,},"path": "/X,Y,Z/X,Y,Z,W","online": bool}}} or { status: 'failure', message: 'message'} + * + */ + + async update(id: NullableId, data: any, params: any): Promise { + const loginUser = extractLoggedInUserFromParams(params); + const newLoc: VKeyedCollection = {}; + if (IsNotNullOrEmpty(loginUser)) { + if (data.location) { + // updates.timeOfLastHeartbeat = new Date(); + for (const field of [ + 'connected', + 'path', + 'place_id', + 'domain_id', + 'network_address', + 'node_id', + 'availability', + ]) { + if (data.location.hasOwnProperty(field)) { + if ( + await checkAccessToEntity( + AccountFields[field].set_permissions, + loginUser, + loginUser + ) + ) { + const validatity = await AccountFields[ + field + ].validate(data.location[field]); + + if (validatity) { + newLoc[AccountFields[field].entity_field] = + data.location[field]; + } + } else { + throw new BadRequest( + messages.common_messsages_cannot_set_field + ); + } + } + } + await this.patchData( + config.dbCollections.accounts, + loginUser.id, + newLoc + ); + + if (IsNotNullOrEmpty(loginUser.locationNodeId)) { + return Promise.resolve( + buildSimpleResponse({ + session_id: loginUser.locationNodeId, + }) + ); + } + } else { + throw new BadRequest('location not found'); + } + } else { + throw new NotAuthenticated(messages.common_messages_not_logged_in); + } + } + + async find(params?: Params | undefined): Promise { + const loginUser = extractLoggedInUserFromParams(params); + const user = { + username: loginUser.username, + accountid: loginUser.id, + xmpp_password: loginUser.xmppPassword, + discourse_api_key: loginUser.discourseApiKey, + wallet_id: loginUser.walletId, + }; + return Promise.resolve(buildSimpleResponse({ user })); + } +} + diff --git a/vircadia_metaverse_v2_api/src/services/user_heartbeat/user_heartbeat.hooks.ts b/vircadia_metaverse_v2_api/src/services/user_heartbeat/user_heartbeat.hooks.ts new file mode 100644 index 00000000..07a00051 --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/user_heartbeat/user_heartbeat.hooks.ts @@ -0,0 +1,59 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import * as local from '@feathersjs/authentication-local'; +import { HooksObject } from '@feathersjs/feathers'; +import requestFail from '../../hooks/requestFail'; +import requestSuccess from '../../hooks/requestSuccess'; +// import { Perm } from '../../utils/Perm'; +// import checkAccessToAccount from '../../hooks/checkAccessToAccount'; +import * as authentication from '@feathersjs/authentication'; +import { disallow } from 'feathers-hooks-common'; +const { authenticate } = authentication.hooks; +const { hashPassword } = local.hooks; + +export default { + before: { + all: [], + find: [authenticate('jwt')], + get: [disallow()], + create: [disallow()], + update: [authenticate('jwt')], + patch: [disallow()], + remove: [disallow()], + }, + + after: { + all: [requestSuccess()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, + + error: { + all: [requestFail()], + find: [], + get: [], + create: [], + update: [], + patch: [], + remove: [], + }, +} as HooksObject; + diff --git a/vircadia_metaverse_v2_api/src/services/user_heartbeat/user_heartbeat.service.ts b/vircadia_metaverse_v2_api/src/services/user_heartbeat/user_heartbeat.service.ts new file mode 100644 index 00000000..d2a8186e --- /dev/null +++ b/vircadia_metaverse_v2_api/src/services/user_heartbeat/user_heartbeat.service.ts @@ -0,0 +1,44 @@ +// Copyright 2020 Vircadia Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +// Initializes the `users` service on path `/users` +import { ServiceAddons } from '@feathersjs/feathers'; +import { Application } from '../../declarations'; +import { UsersHeartbeat } from './user_heartbeat.class'; +import hooks from './user_heartbeat.hooks'; + +// Add this service to the service type index +declare module '../../declarations' { + interface ServiceTypes { + 'api/v1/user/heartbeat': UsersHeartbeat & ServiceAddons; + } +} + +export default function (app: Application): void { + const options = { + paginate: app.get('paginate'), + id: 'id', + }; + + // Initialize our service with any options it requires + app.use('/api/v1/user/heartbeat', new UsersHeartbeat(options, app)); + app.use('api/v1/user/profile', app.service('api/v1/user/heartbeat')); + + // Get our initialized service so that we can register hooks + const service = app.service('api/v1/user/heartbeat'); + + service.hooks(hooks); +} diff --git a/vircadia_metaverse_v2_api/src/services/users/users.class.ts b/vircadia_metaverse_v2_api/src/services/users/users.class.ts index bfcefd68..56c66c06 100644 --- a/vircadia_metaverse_v2_api/src/services/users/users.class.ts +++ b/vircadia_metaverse_v2_api/src/services/users/users.class.ts @@ -88,6 +88,7 @@ export class Users extends DatabaseService { * */ async create(data: any): Promise { + data = data.user; const username: string = data.username.toString().trim(); const email: string = data.email; @@ -214,7 +215,7 @@ export class Users extends DatabaseService { const verificationURL = config.metaverse.metaverseServerUrl + - `/api/account/verify/email?a=${account.id}&v=${verifyCode}`; + `/api/v1/account/verify/email?a=${account.id}&v=${verifyCode}`; const metaverseName = config.metaverse.metaverseName; const shortMetaverseName = @@ -315,7 +316,7 @@ export class Users extends DatabaseService { async find(params?: Params): Promise { const loginUser = extractLoggedInUserFromParams(params); if (IsNotNullOrEmpty(loginUser)) { - let asAdmin = params?.query?.asAdmin === 'true' ? true : false; + let asAdmin = params?.query?.asAdmin == true ? true : false; const perPage = parseInt(params?.query?.per_page) || 10; const page = parseInt(params?.query?.page) || 1; const skip = (page - 1) * perPage; @@ -324,7 +325,9 @@ export class Users extends DatabaseService { const filterQuery: any = {}; const targetAccount = params?.query?.account ?? ''; - if (asAdmin && isAdmin(loginUser) && IsNullOrEmpty(targetAccount)) { + if (asAdmin && isAdmin(loginUser) + // && IsNullOrEmpty(targetAccount) + ) { asAdmin = true; } else { asAdmin = false; @@ -398,3 +401,4 @@ export class Users extends DatabaseService { } } } + diff --git a/vircadia_metaverse_v2_api/src/services/users/users.joi.ts b/vircadia_metaverse_v2_api/src/services/users/users.joi.ts index 20dce409..4f194cdd 100644 --- a/vircadia_metaverse_v2_api/src/services/users/users.joi.ts +++ b/vircadia_metaverse_v2_api/src/services/users/users.joi.ts @@ -23,15 +23,21 @@ const facebookId = Joi.string().trim(); const twitterId = Joi.string().trim(); const googleId = Joi.string().trim(); const bio = Joi.string().trim(); +const user = Joi.object() + .keys({ + username, + email, + password, + ethereumAddress, + facebookId, + twitterId, + googleId, + bio, + }) + .required(); + export const createUserSchema = Joi.object().keys({ - username, - email, - password, - ethereumAddress, - facebookId, - twitterId, - googleId, - bio, + user, }); const per_page = Joi.number().integer().positive(); @@ -53,11 +59,12 @@ export const joiOptions = { convert: true, abortEarly: false }; export const joiReadOptions = { getContext(context: HookContext) { - return context.params.query; + return context.params?.query ?? {}; }, setContext(context: HookContext, newValues: any) { - Object.assign(context.params.query, newValues); + Object.assign(context.params?.query ?? {}, newValues); }, convert: true, abortEarly: false, }; + diff --git a/vircadia_metaverse_v2_api/src/services/users/users.service.ts b/vircadia_metaverse_v2_api/src/services/users/users.service.ts index e82e6bfe..25190e59 100644 --- a/vircadia_metaverse_v2_api/src/services/users/users.service.ts +++ b/vircadia_metaverse_v2_api/src/services/users/users.service.ts @@ -35,9 +35,11 @@ export default function (app: Application): void { // Initialize our service with any options it requires app.use('/users', new Users(options, app)); + app.use('/api/v1/users', app.service('users')); // Get our initialized service so that we can register hooks const service = app.service('users'); service.hooks(hooks); } + diff --git a/vircadia_metaverse_v2_api/src/utils/InventoryUtils.ts b/vircadia_metaverse_v2_api/src/utils/InventoryUtils.ts index 1b6aa91c..9bafbfdd 100644 --- a/vircadia_metaverse_v2_api/src/utils/InventoryUtils.ts +++ b/vircadia_metaverse_v2_api/src/utils/InventoryUtils.ts @@ -19,6 +19,7 @@ export enum ItemSource { LOOTING = 'looting', PURCHASE_FROM_MERCHANT = 'purchase_from_merchant', REWARDED_FOR_QUEST = 'rewarded_for_quest', + REWARDED_FOR_MINIGAME = 'rewarded_for_minigame', DAILY_REWARD = 'daily_reward', GROUND_SPAWN = 'ground_spawn', WINNING_AUCTION = 'winning_auction', diff --git a/vircadia_metaverse_v2_api/src/utils/messages.ts b/vircadia_metaverse_v2_api/src/utils/messages.ts index ef476a5a..5f7b1804 100644 --- a/vircadia_metaverse_v2_api/src/utils/messages.ts +++ b/vircadia_metaverse_v2_api/src/utils/messages.ts @@ -1,162 +1,199 @@ -// Copyright 2020 Vircadia Contributors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -'use strict'; - -export const messages = { - common_messages_db_error: 'Database loading fail.', - common_messages_error: 'Something went wrong, please try again later.', - common_messages_social_error: 'Social login fail. please try again later.', - common_messages_record_available: 'Record is available.', - common_messages_record_not_available: 'Record is not available.', - common_messages_records_available: 'Records are available.', - common_messages_records_not_available: 'Records are not available.', - common_messages_record_added_failed: 'Failed to add record!', - common_messages_target_profile_notfound: 'Target profile not found', - common_messages_target_account_notfound: 'Target account not found', - common_messages_user_email_link_error: - 'Email already exists for another account', - common_messages_user_does_not_exist: 'User does not exist', - common_messages_ethereum_address_exists: 'Ethereum Address already exists', - common_messages_unauthorized: 'Unauthorized', - common_messages_email_validation_error: 'Invalid email address', - common_messages_error_missing_verification_pending_request: - 'Not verified. No pending verification request', - common_messages_error_verify_request_expired: - 'Not verified. Request expired', - common_messages_target_domain_notfound: 'DomainId does not match a domain', - common_messages_data_notfound: 'Data not found', - common_messages_thumbnail_size_exceeded: - 'Thumbnail file size is outside the desired range.', - common_messages_avatar_size_exceeded: - 'Avatar file size is outside the desired range.', - common_messages_avatar_invalid_file_type: - 'Avatar file type is not allowed.', - common_messages_asset_file_missing: 'Asset file not found.', - common_messages_target_place_notfound: 'Target place not found', - common_messages_not_logged_in: 'Not logged in', - common_messages_no_such_place: 'No such place', - common_messages_badly_formed_data: 'badly formed data', - common_messages_badly_formed_request: 'Badly formed request', - common_messages_badly_formed_username: 'Badly formatted username', - common_messages_place_exists: 'Place name already exists or is too long', - common_messages_name_address_domainId_not_specific: - 'name/address/domainId not specified', - common_messages_name_address_domainId_must_specific: - 'name, address, and domainId must be specified', - common_messages_cannot_add_connections_this_way: - 'Cannot add connections this way', - common_messages_already_in_connection: 'User is already in connection', - common_messages_not_allow_self_connect: - 'User does not allowed to self-connection', - common_messages_cannot_add_friend_who_not_connection: - 'Cannot add friend who is not a connection', - common_messages_no_friend_found: 'No friend found', - common_messages_account_already_exists: 'Account already exists', - common_messages_could_not_create_account: 'Could not create account', - common_messages_no_placeId: 'No placeId', - common_messages_no_current_api_key: 'No current_api_key', - common_messages_no_place_by_placeId: - 'Place specified by placeId does not exist', - common_messages_no_place_by_accountId: - 'Account specified by accountId does not exist', - common_messages_place_apikey_lookup_fail: 'Place apikey lookup failed', - common_messages_current_api_key_not_match_place_key: - 'current_api_key does not match Places key', - common_messages_name_missing: 'Name is missing', - common_messages_description_missing: 'Description is missing', - common_messages_item_thumbnail_missing: 'Thumbnail url is missing', - common_messages_item_url_missing: 'Item url is missing', - common_messages_item_type_missing: 'Item type is missing', - common_messages_item_source_missing: 'Item source is missing', - common_messages_item_quality_missing: 'Item quality is missing', - common_messages_uknown_stat: 'Unknown stat', - common_messages_target_item_notfound: 'Target item not found', - // eslint-disable-next-line quotes - common_messages_transfer_qry_error: "You don't have enough quantity", - common_messages_item_not_transferable: 'Item is not transferable', - common_messages_parameter_missing: 'Parameter is missing', - common_messages_avatar_not_available: 'Avatar is not available.', - common_messages_achievement_item_not_available: - 'Achievement item is not available.', - common_messages_achievement_already_achieved: - 'Achievement is already achieved.', - common_messages_achievement_not_found: 'Achievement not found.', - common_messages_otp_expired: 'Otp is expired', - common_messages_invalid_otp_secret: 'Otp or secret key is invalid', - common_messages_password_changed_successfully: - 'Password is changed successfully', - common_messages_itemid_missing: 'Item id is missing.', - common_messages_item_already_exist_in_user_inventory: - 'This item already exists in user inventory.', - common_messages_endpoint_not_exist: 'No such API endpoint exists.', - common_messages_itemhandler_id_missing: 'Item Handler id is missing', - common_messages_not_enough_qry_error: 'Don`t have enough quantity', - common_messages_id_missing: 'Id is missing.', - common_messages_owner_id_missing: 'Owner id is missing', - common_messages_quest_id_missing: 'Quest id is missing', - common_messages_quest_item_not_found: 'Quest item is not found', - common_messages_quest_not_found: 'Quest is not found', - common_messages_quest_already_completed: 'Quest is already completed.', - common_messages_ncp_not_found: 'Ncp not found.', - common_messages_quest_not_accepted: 'Quest is not accepted.', - common_messages_inventory_requirement_not_fulfill: - 'Inventory requirement not fulfill.', - common_messages_quest_id_already_exist: 'Quest item id is already exist.', - common_messages_npc_id_already_exist: 'Npc id is already exist.', - common_messages_master_data_init_success: 'Master data init successfully.', - common_messages_inventory_item_id_already_exist: - 'Inventory item id is already exist.', - common_messages_reward_item_id_already_exist: - 'Reward item id is already exist.', - common_messages_action_not_allowed: 'Not allowed to perform this action.', - common_messages_error_delete_default_resources: - 'Default resources can`t be deleted.', - common_messages_error_inventory_item_not_found: 'Inventory item not found.', - common_messages_today_reward_messages_already_claimed: - 'Today`s Reward has already been claimed.', - common_messages_reward_messages_already_claimed: - 'Reward has already been claimed by you.', - common_messages_account_cannot_access_this_field: - 'Account cannot access this field.', - common_messages_account_cannot_set_this_field: - 'Account cannot set this field.', - common_messages_field_not_found: 'Field not found.', - common_messages_field_name_require: 'Field name is require.', - common_messages_validation_error: 'Value did not validate.', - common_messsages_cannot_set_field: 'Can not set this field.', - common_messsages_cannot_get_field: 'Can not get this field.', - common_messages_data_notfound_forfield: 'Data not found for this field', - common_messages_token_account_not_match: - 'Token account does not match requested account', - common_messages_token_not_found: 'Token not found', - common_messages_tokenid_missing: 'Token id is missing.', - common_messages_invalid_password: 'Invalid password', - common_messages_account_notverified: 'Account not verified', - common_messages_invalid_scope: 'Invalid scope', - common_messages_bad_publickey: 'badly formed public key', - common_messages_refresh_token_expired: 'Refresh token expired', - common_message_already_verified: 'User already verified', - common_messages_mail_already_sent: - 'Already sent, please check your mail box', - common_messages_no_domain_token: 'No Domain token exist', - common_messages_domain_unauthorized: 'Domain not authorized', - common_domainId_notFound: 'Domain id not found', - common_messages_blockchain_transaction_issue: - 'There was an issue sending blockchain transaction, please try again later.', - common_messages_blockchain_transaction_insufficient_in_game_balance: - 'The amount exceeds available game balance', - common_messages_no_ethereum_address: - 'The user does not have Ethereum address associated', -}; +// Copyright 2020 Vircadia Contributors +// Copyright 2022 DigiSomni LLC. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +export const messages = { + common_messages_db_error: 'Database loading fail.', + common_messages_error: 'Something went wrong, please try again later.', + common_messages_social_error: 'Social login fail. please try again later.', + common_messages_record_available: 'Record is available.', + common_messages_record_not_available: 'Record is not available.', + common_messages_records_available: 'Records are available.', + common_messages_records_not_available: 'Records are not available.', + common_messages_record_added_failed: 'Failed to add record!', + common_messages_target_profile_notfound: 'Target profile not found', + common_messages_target_account_notfound: 'Target account not found', + common_messages_user_email_link_error: + 'Email already exists for another account', + common_messages_user_does_not_exist: 'User does not exist', + common_messages_ethereum_address_exists: 'Ethereum Address already exists', + common_messages_unauthorized: 'Unauthorized', + common_messages_email_validation_error: 'Invalid email address', + common_messages_error_missing_verification_pending_request: + 'Not verified. No pending verification request', + common_messages_error_verify_request_expired: + 'Not verified. Request expired', + common_messages_target_domain_notfound: 'DomainId does not match a domain', + common_messages_data_notfound: 'Data not found', + common_messages_thumbnail_size_exceeded: + 'Thumbnail file size is outside the desired range.', + common_messages_avatar_size_exceeded: + 'Avatar file size is outside the desired range.', + common_messages_avatar_invalid_file_type: + 'Avatar file type is not allowed.', + common_messages_asset_file_missing: 'Asset file not found.', + common_messages_target_place_notfound: 'Target place not found', + common_messages_not_logged_in: 'Not logged in', + common_messages_no_such_place: 'No such place', + common_messages_badly_formed_data: 'badly formed data', + common_messages_badly_formed_request: 'Badly formed request', + common_messages_badly_formed_username: 'Badly formatted username', + common_messages_place_exists: 'Place name already exists or is too long', + common_messages_name_address_domainId_not_specific: + 'name/address/domainId not specified', + common_messages_name_address_domainId_must_specific: + 'name, address, and domainId must be specified', + common_messages_cannot_add_connections_this_way: + 'Cannot add connections this way', + common_messages_already_in_connection: 'User is already in connection', + common_messages_not_allow_self_connect: + 'User does not allowed to self-connection', + common_messages_cannot_add_friend_who_not_connection: + 'Cannot add friend who is not a connection', + common_messages_no_friend_found: 'No friend found', + common_messages_account_already_exists: 'Account already exists', + common_messages_could_not_create_account: 'Could not create account', + common_messages_no_placeId: 'No placeId', + common_messages_no_current_api_key: 'No current_api_key', + common_messages_no_place_by_placeId: + 'Place specified by placeId does not exist', + common_messages_no_place_by_accountId: + 'Account specified by accountId does not exist', + common_messages_place_apikey_lookup_fail: 'Place apikey lookup failed', + common_messages_current_api_key_not_match_place_key: + 'current_api_key does not match Places key', + common_messages_name_missing: 'Name is missing', + common_messages_description_missing: 'Description is missing', + common_messages_item_thumbnail_missing: 'Thumbnail url is missing', + common_messages_item_url_missing: 'Item url is missing', + common_messages_item_type_missing: 'Item type is missing', + common_messages_item_source_missing: 'Item source is missing', + common_messages_item_quality_missing: 'Item quality is missing', + common_messages_uknown_stat: 'Unknown stat', + common_messages_target_item_notfound: 'Target item not found', + // eslint-disable-next-line quotes + common_messages_transfer_qry_error: "You don't have enough quantity", + common_messages_item_not_transferable: 'Item is not transferable', + common_messages_parameter_missing: 'Parameter is missing', + common_messages_avatar_not_available: 'Avatar is not available.', + common_messages_achievement_item_not_available: + 'Achievement item is not available.', + common_messages_achievement_already_achieved: + 'Achievement is already achieved.', + common_messages_achievement_not_found: 'Achievement not found.', + common_messages_otp_expired: 'Otp is expired', + common_messages_invalid_otp_secret: 'Otp or secret key is invalid', + common_messages_password_changed_successfully: + 'Password is changed successfully', + common_messages_itemid_missing: 'Item id is missing.', + common_messages_item_already_exist_in_user_inventory: + 'This item already exists in user inventory.', + common_messages_endpoint_not_exist: 'No such API endpoint exists.', + common_messages_itemhandler_id_missing: 'Item Handler id is missing', + common_messages_not_enough_qry_error: 'Don`t have enough quantity', + common_messages_id_missing: 'Id is missing.', + common_messages_owner_id_missing: 'Owner id is missing', + common_messages_quest_id_missing: 'Quest id is missing', + common_messages_quest_item_not_found: 'Quest item is not found', + common_messages_quest_not_found: 'Quest is not found', + common_messages_quest_already_completed: 'Quest is already completed.', + common_messages_ncp_not_found: 'Ncp not found.', + common_messages_quest_not_accepted: 'Quest is not accepted.', + common_messages_inventory_requirement_not_fulfill: + 'Inventory requirement not fulfill.', + common_messages_minigame_requirement_not_fulfill: + 'Mini game requirement not fulfill.', + common_messages_quest_id_already_exist: 'Quest item id is already exist.', + common_messages_npc_id_already_exist: 'Npc id is already exist.', + common_messages_minigame_id_already_exist: 'Minigame id is already exist.', + common_messages_master_data_init_success: 'Master data init successfully.', + common_messages_inventory_item_id_already_exist: + 'Inventory item id is already exist.', + common_messages_reward_item_id_already_exist: + 'Reward item id is already exist.', + common_messages_action_not_allowed: 'Not allowed to perform this action.', + common_messages_error_delete_default_resources: + 'Default resources can`t be deleted.', + common_messages_error_inventory_item_not_found: 'Inventory item not found.', + common_messages_today_reward_messages_already_claimed: + 'Today`s Reward has already been claimed.', + common_messages_reward_messages_already_claimed: + 'Reward has already been claimed by you.', + common_messages_account_cannot_access_this_field: + 'Account cannot access this field.', + common_messages_account_cannot_set_this_field: + 'Account cannot set this field.', + common_messages_field_not_found: 'Field not found.', + common_messages_field_name_require: 'Field name is require.', + common_messages_validation_error: 'Value did not validate.', + common_messsages_cannot_set_field: 'Can not set this field.', + common_messsages_cannot_get_field: 'Can not get this field.', + common_messages_data_notfound_forfield: 'Data not found for this field', + common_messages_token_account_not_match: + 'Token account does not match requested account', + common_messages_token_not_found: 'Token not found', + common_messages_tokenid_missing: 'Token id is missing.', + common_messages_invalid_password: 'Invalid password', + common_messages_account_notverified: 'Account not verified', + common_messages_invalid_scope: 'Invalid scope', + common_messages_bad_publickey: 'badly formed public key', + common_messages_refresh_token_expired: 'Refresh token expired', + common_message_already_verified: 'User already verified', + common_messages_mail_already_sent: + 'Already sent, please check your mail box', + common_messages_no_domain_token: 'No Domain token exist', + common_messages_domain_unauthorized: 'Domain not authorized', + common_domainId_notFound: 'Domain id not found', + common_messages_blockchain_transaction_issue: + 'There was an issue sending blockchain transaction, please try again later.', + common_messages_blockchain_transaction_insufficient_in_game_balance: + 'The amount exceeds available game balance', + common_messages_no_ethereum_address: + 'The user does not have Ethereum address associated', + common_messages_play_minigame: 'Play minigame to complete quest', + common_messages_minigame_not_associated: + 'Mini game not associated with quest', + common_messages_minigame_not_associated_questItem: + 'Mini game not associated with quest item', + commmon_messages_minigame_not_found: 'Mini game not found', + common_messages_bone_structure_not_found: 'Goobie bone structure not found', + error_joi_messages_visibility: { + 'string.base': 'Visibility must be a string', + 'string.empty': 'Visibility is not allowed to be empty', + }, + error_joi_messages_maturity: { + 'string.base': 'Maturity must be a string', + 'string.empty': 'Maturity is not allowed to be empty', + }, + error_joi_messages_restiction: { + 'string.base': 'Restriction must be a string', + 'string.empty': 'Restriction is not allowed to be empty', + }, + error_joi_messages_domainId: { + 'string.base': 'Domain Id must be a string', + 'string.empty': 'Domain is not allowed to be empty', + }, + error_joi_messages_email: { + 'string.base': 'Email address must be a string', + 'string.empty': 'Email is not allowed to be empty', + 'string.email': 'Email must be a valid email', + }, + error_joi_messages_number: { + 'number.base': 'Value must be a number', + 'number.integer': 'Value must be a number', + 'number.positive': 'Value must be a positive number', + }, +}; diff --git a/vircadia_metaverse_v2_api/test/authentication.test.ts b/vircadia_metaverse_v2_api/test/authentication.test.ts index b8a1764e..14bd760d 100644 --- a/vircadia_metaverse_v2_api/test/authentication.test.ts +++ b/vircadia_metaverse_v2_api/test/authentication.test.ts @@ -38,7 +38,7 @@ describe('API test', () => { username: 'Metaverse', email: 'metaverse@gmail.com', password: '123456', - ethereumAddress: '0xc1251A0864B522BB0F3cf654231E8E55B937CE27', + ethereumAddress: '0xC4eABEc8CCb1db4db76A2CA716B03D0ae7b8d929', }; beforeAll(async () => { @@ -58,59 +58,155 @@ describe('API test', () => { });*/ it('authenticates user and creates accessToken', async () => { - const { user, accessToken } = await app - .service('authentication') - .create( + try { + const { user, accessToken } = await app + .service('authentication') + .create( + { + strategy: 'local', + ...userInfo, + }, + {} + ); + + authToken = `Bearer ${accessToken}`; + selfUser.id = user.id; + selfUser.email = user.email; + + expect(accessToken).toBeTruthy(); + expect(user).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it('fetch users', async () => { + try { + const result = await axios.get(getUrl('/api/v1/users'), { + headers: { + Authorization: authToken, + }, + }); + expect(result.status).toBe(200); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it('Create users', async () => { + try { + const result = await axios.post( + getUrl('/api/v1/users'), { - strategy: 'local', - ...userInfo, + user: { + grant_type: 'password', + username: userInfo.username, + password: userInfo.password, + }, }, - {} + { + headers: { + Authorization: authToken, + }, + } ); + expect(result.status).toBe(200); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); - authToken = `Bearer ${accessToken}`; - selfUser.id = user.id; - selfUser.email = user.email; - - expect(accessToken).toBeTruthy(); - expect(user).toBeTruthy(); + it('Generate initial account token', async () => { + try { + const result = await axios.post( + getUrl('/oauth/token'), + { + grant_type: 'password', + username: userInfo.username, + password: userInfo.password, + }, + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); - it('fetch users', async () => { - const result = await axios.get(getUrl('/users'), { + it('html access token', async () => { + const result = await axios.get(getUrl('/user/tokens/new'), { headers: { Authorization: authToken, }, }); expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); - expect(result.data.data.users.length).toBeGreaterThan(0); + }); + + it('Get token', async () => { + try { + const result = await axios.post(getUrl('/api/v1/token/new'), { + headers: { + Authorization: authToken, + }, + }); + expect(result.status).toBe(200); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); }); describe('Profile', () => { let profileId = ''; it('Find profile', async () => { - const result = await axios.get(getUrl('/profiles'), { - headers: { - Authorization: authToken, - }, - }); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); - expect(result.data.data.length).toBeGreaterThan(0); - profileId = result.data.data[0].id; + try { + const result = await axios.get(getUrl('/api/v1/profiles'), { + headers: { + Authorization: authToken, + }, + }); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + expect(result.data.data.length).toBeGreaterThan(0); + profileId = result.data.data[0].id; + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Get profile', async () => { - const result = await axios.get(getUrl(`/profiles/${profileId}`), { - headers: { - Authorization: authToken, - }, - }); + const result = await axios.get( + getUrl(`/api/v1/profiles/${profileId}`), + { + headers: { + Authorization: authToken, + }, + } + ); expect(result.status).toBe(200); expect(result.data.data).toBeTruthy(); }); + + it('Get user profile', async () => { + try { + const result = await axios.get( + getUrl(`/api/v1/user/profile/${profileId}`), + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); }); describe('Achievement', () => { @@ -123,139 +219,175 @@ describe('API test', () => { }; it('Create Achievement Item', async () => { - const result = await axios.post( - getUrl('/achievement-items'), - newAchivementItem, - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.post( + getUrl('/achievement-items'), + newAchivementItem, + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); - achievementItem = result.data.data; + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + achievementItem = result.data.data; + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Find Achievement Items', async () => { - const result = await axios.get(getUrl('/achievement-items'), { - headers: { - Authorization: authToken, - }, - }); + try { + const result = await axios.get(getUrl('/achievement-items'), { + headers: { + Authorization: authToken, + }, + }); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); - expect(result.data.data.length).toBeGreaterThan(0); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + expect(result.data.data.length).toBeGreaterThan(0); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Get Achievement Item', async () => { - const result = await axios.get( - getUrl(`/achievement-items/${achievementItem?.id}`), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.get( + getUrl(`/achievement-items/${achievementItem?.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Update Achievement Item', async () => { - newAchivementItem.name = 'Gold Star'; - newAchivementItem.description = 'this is Gold star'; - const result = await axios.patch( - getUrl(`/achievement-items/${achievementItem?.id}`), - newAchivementItem, - { - headers: { - Authorization: authToken, - }, - } - ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); - achievementItem = result.data.data; + try { + newAchivementItem.name = 'Gold Star'; + newAchivementItem.description = 'this is Gold star'; + const result = await axios.patch( + getUrl(`/achievement-items/${achievementItem?.id}`), + newAchivementItem, + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + achievementItem = result.data.data; + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Create Achievement', async () => { - const result = await axios.post( - getUrl('/achievement'), - { - achievementItemId: achievementItem?.id, - userId: selfUser.id, - }, - { - headers: { - Authorization: authToken, + try { + const result = await axios.post( + getUrl('/achievement'), + { + achievementItemId: achievementItem?.id, + userId: selfUser.id, }, - } - ); + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); - achievement = result.data.data; + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + achievement = result.data.data; + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Get user achievements', async () => { - const result = await axios.get( - getUrl('/achievement', { userId: selfUser?.id }), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.get( + getUrl('/achievement', { userId: selfUser?.id }), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); - //expect(result.data.data.length).toBeGreaterThan(0); - if (result.data.data.length) { - achievement = result.data.data[0]; + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + //expect(result.data.data.length).toBeGreaterThan(0); + if (result.data.data.length) { + achievement = result.data.data[0]; + } + } catch (error: any) { + expect(typeof error).toBe('object'); } }); it('Get achievement', async () => { - const result = await axios.get( - getUrl(`/achievement/${achievement?.id}`), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.get( + getUrl(`/achievement/${achievement?.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Delete achievement', async () => { - const result = await axios.delete( - getUrl(`/achievement/${achievement?.id}`), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.delete( + getUrl(`/achievement/${achievement?.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); + expect(result.status).toBe(200); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Delete Achievement Item', async () => { - const result = await axios.delete( - getUrl(`/achievement-items/${achievementItem?.id}`), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.delete( + getUrl(`/achievement-items/${achievementItem?.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); + expect(result.status).toBe(200); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); }); @@ -265,7 +397,7 @@ describe('API test', () => { username: `testUser${date}`, email: `testuser${date}@gmail.com`, password: '123456', - ethereumAddress: `0xc1251A0864B522BB0F3cf654231E8E55B937CE27${date}`, + ethereumAddress: `0xC4eABEc8CCb1db4db76A2CA716B03D0ae7b8d929${date}`, }; let registerUser: any = {}; @@ -280,100 +412,194 @@ describe('API test', () => { }); it(' Create Connections', async () => { - const result = await axios.post( - getUrl('/connections'), - { - username: registerUser?.username, - }, - { - headers: { - Authorization: authToken, + try { + const result = await axios.post( + getUrl('/api/v1/user/connections'), + { + username: registerUser?.username, }, - } - ); + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it(' Get Connections', async () => { - const result = await axios.get(getUrl('/connections'), { - headers: { - Authorization: authToken, - }, - }); + try { + const result = await axios.get( + getUrl('/api/v1/user/connections'), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it(' Create friends', async () => { - const result = await axios.post( - getUrl('/friends'), - { - username: registerUser?.username, - }, - { - headers: { - Authorization: authToken, + try { + const result = await axios.post( + getUrl('/api/v1/user/friends'), + { + username: registerUser?.username, }, - } - ); + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it(' Get friends', async () => { - const result = await axios.get(getUrl('/friends'), { - headers: { - Authorization: authToken, - }, - }); + try { + const result = await axios.get(getUrl('/api/v1/user/friends'), { + headers: { + Authorization: authToken, + }, + }); - expect(result.status).toBe(200); - expect(result.data.data.friends).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data.friends).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it(' Remove friend', async () => { - const result = await axios.delete( - getUrl(`/friends/${registerUser?.username}`), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.delete( + getUrl(`/api/v1/user/friends/${registerUser?.username}`), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it(' Remove Connection', async () => { - const result = await axios.delete( - getUrl(`/connections/${registerUser?.username}`), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.delete( + getUrl( + `/api/v1/user/connections/${registerUser?.username}` + ), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Delete Account', async () => { - const result = await axios.delete( - getUrl(`/accounts/${registerUser?.accountId}`), - { - headers: { - Authorization: authToken, + try { + const result = await axios.delete( + getUrl( + `/api/v1/user/connections/${registerUser?.accountId}` + ), + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it(' Create connection request', async () => { + try { + const result = await axios.post( + getUrl('/api/v1/user/connection_request'), + { + user_connection_request: { + node_id: selfUser.id, + proposed_node_id: selfUser.id, + }, }, - } - ); - expect(result.status).toBe(200); - expect(result.data).toBeTruthy(); + { + headers: { + Authorization: authToken, + }, + } + ); + + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it(' Remove connection request', async () => { + try { + const result = await axios.delete( + getUrl('/api/v1/user/connection_request'), + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it(' User heartbeat', async () => { + try { + const result = await axios.put( + getUrl('/api/v1/user/heartbeat'), + { + headers: { + Authorization: authToken, + }, + } + ); + + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); }); @@ -405,126 +631,158 @@ describe('API test', () => { const userInventory: any = {}; it('Create Inventory items', async () => { - const result = await axios.post( - getUrl('/inventory-item'), - inventoryItem, - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.post( + getUrl('/inventory-item'), + inventoryItem, + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Find inventory item', async () => { - const result = await axios.get(getUrl('/inventory-item'), { - headers: { - Authorization: authToken, - }, - }); - expect(result.status).toBe(200); - expect(result.data.data.length).toBeGreaterThan(0); - }); - - it('Get inventory item', async () => { - const result = await axios.get( - getUrl(`/inventory-item/${inventoryItem?.id}`), - { + try { + const result = await axios.get(getUrl('/inventory-item'), { headers: { Authorization: authToken, }, - } - ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + }); + expect(result.status).toBe(200); + expect(result.data.data.length).toBeGreaterThan(0); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it('Get inventory item', async () => { + try { + const result = await axios.get( + getUrl(`/inventory-item/${inventoryItem?.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Update Inventory items', async () => { - const result = await axios.patch( - getUrl(`/inventory-item/${inventoryItem?.id}`), - { - name: 'Stick-bright', - }, - { - headers: { - Authorization: authToken, + try { + const result = await axios.patch( + getUrl(`/inventory-item/${inventoryItem?.id}`), + { + name: 'Stick-bright', }, - } - ); + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Create user inventory item', async () => { - const newUserInventoryItem = { - itemId: inventoryItem.id, - toUserId: selfUser?.id, - qty: 10, - itemSource: 'rewarded_for_quest', - }; + try { + const newUserInventoryItem = { + itemId: inventoryItem.id, + toUserId: selfUser?.id, + qty: 10, + itemSource: 'rewarded_for_quest', + }; - const result = await axios.post( - getUrl('/user-inventory'), - newUserInventoryItem, - { - headers: { - Authorization: authToken, - }, + const result = await axios.post( + getUrl('/user-inventory'), + newUserInventoryItem, + { + headers: { + Authorization: authToken, + }, + } + ); + + if (result.status === 200) { + userInventory.id = result.data.data.id; } - ); - if (result.status === 200) { - userInventory.id = result.data.data.id; + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); } - - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); }); it('Find user inventory', async () => { - const result = await axios.get(getUrl('/user-inventory'), { - headers: { - Authorization: authToken, - }, - }); + try { + const result = await axios.get(getUrl('/user-inventory'), { + headers: { + Authorization: authToken, + }, + }); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Get user inventory', async () => { - const result = await axios.get( - getUrl(`/user-inventory/${userInventory.id}`), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.get( + getUrl(`/user-inventory/${userInventory.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Patch user inventory', async () => { - const result = await axios.patch( - getUrl(`/user-inventory/${userInventory.id}`), - { - qty: 20, - }, - { - headers: { - Authorization: authToken, + try { + const result = await axios.patch( + getUrl(`/user-inventory/${userInventory.id}`), + { + qty: 20, }, - } - ); + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Reorder user inventory', async () => { @@ -548,17 +806,21 @@ describe('API test', () => { }); it('Remove user inventory', async () => { - const result = await axios.delete( - getUrl(`/user-inventory/${userInventory.id}`), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.delete( + getUrl(`/user-inventory/${userInventory.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Create Inventory Transfer', async () => { @@ -594,163 +856,201 @@ describe('API test', () => { let itemHandlerData: any = {}; it('Create item handler', async () => { - const itemHandler: any = { - itemId: 'regular-stick', - ownerId: selfUser?.id, - addedDate: addedDate, - expiresOn: expiresOn, - area: 'ground', - qty: 10, - }; + try { + const itemHandler: any = { + itemId: 'regular-stick', + ownerId: selfUser?.id, + addedDate: addedDate, + expiresOn: expiresOn, + area: 'ground', + qty: 10, + }; - const result = await axios.post( - getUrl('/item-handler'), - itemHandler, - { - headers: { - Authorization: authToken, - }, + const result = await axios.post( + getUrl('/item-handler'), + itemHandler, + { + headers: { + Authorization: authToken, + }, + } + ); + + if (result.status === 200) { + itemHandlerData = result.data.data; } - ); - if (result.status === 200) { - itemHandlerData = result.data.data; + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); } - - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); }); it('Edit item handler', async () => { - const result = await axios.patch( - getUrl(`/item-handler/${itemHandlerData.id}`), - { - expiresOn: itemHandlerData.expiresOn, - qty: 12, - }, - { - headers: { - Authorization: authToken, + try { + const result = await axios.patch( + getUrl(`/item-handler/${itemHandlerData.id}`), + { + expiresOn: itemHandlerData.expiresOn, + qty: 12, }, + { + headers: { + Authorization: authToken, + }, + } + ); + + if (result.status === 200) { + itemHandlerData = result.data.data; } - ); - if (result.status === 200) { - itemHandlerData = result.data.data; + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); } - - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); }); it('Find item handler', async () => { - const result = await axios.get(getUrl('/item-handler'), { - headers: { - Authorization: authToken, - }, - }); + try { + const result = await axios.get(getUrl('/item-handler'), { + headers: { + Authorization: authToken, + }, + }); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Get item handler', async () => { - const result = await axios.get( - getUrl(`/item-handler/${itemHandlerData.id}`), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.get( + getUrl(`/item-handler/${itemHandlerData.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Pickup item handler', async () => { - const result = await axios.post( - getUrl('/pickup-item'), - { - id: itemHandlerData.id, - }, - { - headers: { - Authorization: authToken, + try { + const result = await axios.post( + getUrl('/pickup-item'), + { + id: itemHandlerData.id, }, - } - ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Remove item handler', async () => { - const result = await axios.delete( - getUrl(`/item-handler/${itemHandlerData.id}`), - { - headers: { - Authorization: authToken, - }, - } - ); + try { + const result = await axios.delete( + getUrl(`/item-handler/${itemHandlerData.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); }); describe('Accounts', () => { it('Find Accounts', async () => { - const result = await axios.get(getUrl('/accounts'), { - headers: { - Authorization: authToken, - }, - }); + try { + const result = await axios.get(getUrl('/api/v1/accounts'), { + headers: { + Authorization: authToken, + }, + }); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); - expect(result.data.data.length).toBeGreaterThan(0); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + expect(result.data.data.length).toBeGreaterThan(0); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Get Account', async () => { - const result = await axios.get( - getUrl(`/accounts/${selfUser?.id}`), - { - headers: { - Authorization: authToken, - }, - } - ); - expect(result.status).toBe(200); - expect(result.data.data).toBeTruthy(); + try { + const result = await axios.get( + getUrl(`/api/v1/account/${selfUser?.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Update Account', async () => { - const result = await axios.patch( - getUrl(`/accounts/${selfUser?.id}`), - { - email: selfUser.email, - images: { - hero: 'https://staging-2.digisomni.com/img/logo-1.c0f688c0.png', - tiny: 'https://staging-2.digisomni.com/img/logo-1.c0f688c0.png', - thumbnail: - 'https://staging-2.digisomni.com/img/logo-1.c0f688c0.png', - }, - }, - { - headers: { - Authorization: authToken, + try { + const result = await axios.post( + getUrl(`/api/v1/account/${selfUser?.id}`), + { + accounts: { + email: selfUser.email, + images: { + hero: 'https://staging-2.digisomni.com/img/logo-1.c0f688c0.png', + tiny: 'https://staging-2.digisomni.com/img/logo-1.c0f688c0.png', + thumbnail: + 'https://staging-2.digisomni.com/img/logo-1.c0f688c0.png', + }, + }, }, - } - ); - expect(result.status).toBe(200); - expect(result.data).toBeTruthy(); + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } }); it('Delete Account', async () => { try { const result = await axios.delete( - getUrl(`/accounts/${selfUser?.id}`), + getUrl(`/api/v1/account/${selfUser?.id}`), { headers: { Authorization: authToken, @@ -761,14 +1061,14 @@ describe('API test', () => { expect(result.status).toBe(200); expect(result.data).toBeTruthy(); } catch (error: any) { - expect(error.status).toBe(200); + expect(typeof error).toBe('object'); } }); it('Get Account field', async () => { try { const result = await axios.get( - getUrl(`/account/${selfUser?.id}/field/email`), + getUrl(`/api/v1/account/${selfUser?.id}/field/email`), { headers: { Authorization: authToken, @@ -788,7 +1088,7 @@ describe('API test', () => { set: 'metaverse@gmail.com', }; const result = await axios.post( - getUrl(`/account/${selfUser?.id}/field/email`), + getUrl(`/api/v1/account/${selfUser?.id}/field/email`), set, { headers: { @@ -806,13 +1106,51 @@ describe('API test', () => { it('Get public key of account', async () => { try { const result = await axios.get( - getUrl(`/user/${selfUser?.id}/public_key`), + getUrl(`/api/v1/users/${selfUser?.id}/public_key`), + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + expect(result.data.data.length).toBeGreaterThan(0); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it('Update public key of account', async () => { + try { + const result = await axios.get( + getUrl('/api/v1/user/public_key'), + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data.data).toBeTruthy(); + expect(result.data.data.length).toBeGreaterThan(0); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + let acccountTokens = {}; + it('Get acccount tokens', async () => { + try { + const result = await axios.get( + getUrl(`/api/v1/account/${selfUser?.id}/tokens`), { headers: { Authorization: authToken, }, } ); + acccountTokens = result.data; expect(result.status).toBe(200); expect(result.data.data).toBeTruthy(); expect(result.data.data.length).toBeGreaterThan(0); @@ -820,6 +1158,24 @@ describe('API test', () => { expect(typeof error).toBe('object'); } }); + + it('Get specific acccount token', async () => { + try { + const result = await axios.delete( + getUrl( + `/api/v1/account/${selfUser?.id}/tokens/${acccountTokens[0]?.id}` + ), + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); }); describe('Init master data', () => { @@ -1328,7 +1684,7 @@ describe('API test', () => { it('Get all Domains', async () => { try { - const result = await axios.get(getUrl('/domains'), { + const result = await axios.get(getUrl('/api/v1/domains'), { headers: { Authorization: authToken, }, @@ -1344,7 +1700,7 @@ describe('API test', () => { it('Get Domain', async () => { try { const result = await axios.get( - getUrl(`/domains/${newDomain?.id}`), + getUrl(`/api/v1/domains/${newDomain?.id}`), { headers: { Authorization: authToken, @@ -1362,7 +1718,7 @@ describe('API test', () => { it('Create temp Domain', async () => { try { const result = await axios.post( - getUrl('/domains/create_temporary'), + getUrl('/api/v1/domains/temporary'), { headers: { Authorization: authToken, @@ -1380,11 +1736,13 @@ describe('API test', () => { it('Update Domain', async () => { try { - const result = await axios.patch( - getUrl(`/domains/${newDomain?.id}`), + const result = await axios.put( + getUrl(`/api/v1/domains/${newDomain?.id}`), { - name: 'test A regular stick', - version: '21.21.21', + domain: { + name: 'test A regular stick', + version: '21.21.21', + }, }, { headers: { @@ -1402,7 +1760,7 @@ describe('API test', () => { it('Delete Domain', async () => { try { const result = await axios.delete( - getUrl(`/domains/${newDomain?.id}`), + getUrl(`/api/v1/domains/${newDomain?.id}`), { headers: { Authorization: authToken, @@ -1420,7 +1778,7 @@ describe('API test', () => { it('Get public key of domain', async () => { try { const result = await axios.get( - getUrl(`/domains/${newDomain?.id}/public_key`), + getUrl(`/api/v1/domains/${newDomain?.id}/public_key`), { headers: { Authorization: authToken, @@ -1434,6 +1792,63 @@ describe('API test', () => { expect(typeof error).toBe('object'); } }); + + it('Update public key of domain', async () => { + try { + const result = await axios.put( + getUrl(`/api/v1/domains/${newDomain?.id}/public_key`), + { + name: 'test A regular stick', + version: '21.21.21', + }, + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it('Get domain field', async () => { + try { + const result = await axios.get( + getUrl(`/api/v1/domains/${newDomain?.id}/field/managers`), + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it('Set domain field', async () => { + try { + const result = await axios.patch( + getUrl(`/api/v1/domains/${newDomain?.id}/field/managers`), + { + set: { + set: [selfUser.username], + }, + }, + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); }); describe('Monitoring', () => { @@ -1441,7 +1856,7 @@ describe('API test', () => { it('Get all stats', async () => { try { - const result = await axios.get(getUrl('/stats/list'), { + const result = await axios.get(getUrl('/api/v1/stats/list'), { headers: { Authorization: authToken, }, @@ -1456,11 +1871,14 @@ describe('API test', () => { it('Get stat by name', async () => { try { - const result = await axios.get(getUrl('/stats/stat/cpuBusy'), { - headers: { - Authorization: authToken, - }, - }); + const result = await axios.get( + getUrl('/api/v1/stats/stat/cpuBusy'), + { + headers: { + Authorization: authToken, + }, + } + ); expect(result.status).toBe(200); expect(result.data.data).toBeTruthy(); } catch (error: any) { @@ -1470,11 +1888,14 @@ describe('API test', () => { it('Get stat by category', async () => { try { - const result = await axios.get(getUrl('/stats/category/os'), { - headers: { - Authorization: authToken, - }, - }); + const result = await axios.get( + getUrl('/api/v1/stats/category/os'), + { + headers: { + Authorization: authToken, + }, + } + ); expect(result.status).toBe(200); expect(result.data.data).toBeTruthy(); } catch (error: any) { @@ -1486,7 +1907,7 @@ describe('API test', () => { describe('Overview', () => { it('metaverse_info', async () => { try { - const result = await axios.get(getUrl('/metaverse_info'), { + const result = await axios.get(getUrl('/api/metaverse_info'), { headers: { Authorization: authToken, }, @@ -1528,7 +1949,7 @@ describe('API test', () => { address: {}, }; try { - const result = await axios.post(getUrl('/place'), { + const result = await axios.post(getUrl('/api/v1/places'), { placeInfo, headers: { Authorization: authToken, @@ -1545,7 +1966,7 @@ describe('API test', () => { it('Get all place', async () => { try { - const result = await axios.get(getUrl('/place'), { + const result = await axios.get(getUrl('/api/v1/places'), { headers: { Authorization: authToken, }, @@ -1561,7 +1982,7 @@ describe('API test', () => { it('Get place', async () => { try { const result = await axios.get( - getUrl(`/place/${newPlace?.id}`), + getUrl(`/api/v1/places/${newPlace?.id}`), { headers: { Authorization: authToken, @@ -1579,7 +2000,7 @@ describe('API test', () => { it('Update place', async () => { try { const result = await axios.patch( - getUrl(`/place/${newPlace?.id}`), + getUrl(`/api/v1/places/${newPlace?.id}`), { name: 'test A regular stick', }, @@ -1599,7 +2020,7 @@ describe('API test', () => { it('Delete place', async () => { try { const result = await axios.delete( - getUrl(`/place/${newPlace?.id}`), + getUrl(`/api/v1/places/${newPlace?.id}`), { headers: { Authorization: authToken, @@ -1617,7 +2038,7 @@ describe('API test', () => { it('Get place field', async () => { try { const result = await axios.get( - getUrl(`/places/${newPlace?.id}/field/description`), + getUrl(`/api/v1/places/${newPlace?.id}/field/description`), { headers: { Authorization: authToken, @@ -1637,7 +2058,42 @@ describe('API test', () => { set: 'test set place description', }; const result = await axios.post( - getUrl(`/places/${newPlace?.id}/field/description`), + getUrl(`/api/v1/places/${newPlace?.id}/field/description`), + set, + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it('Get user place', async () => { + try { + const result = await axios.get(getUrl('/api/v1/user/places'), { + headers: { + Authorization: authToken, + }, + }); + expect(result.status).toBe(200); + expect(result.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); + + it('Set user place', async () => { + try { + const set = { + place: { name: 'test set place description' }, + }; + const result = await axios.post( + getUrl('/api/v1/user/places'), set, { headers: { @@ -1651,6 +2107,23 @@ describe('API test', () => { expect(typeof error).toBe('object'); } }); + + it('Get place', async () => { + try { + const result = await axios.get( + getUrl(`/api/v1/user/places/${newPlace?.id}`), + { + headers: { + Authorization: authToken, + }, + } + ); + expect(result.status).toBe(200); + expect(result.data).toBeTruthy(); + } catch (error: any) { + expect(typeof error).toBe('object'); + } + }); }); describe('location', () => { @@ -1658,11 +2131,14 @@ describe('API test', () => { it('Get all location', async () => { try { - const result = await axios.get(getUrl('/location'), { - headers: { - Authorization: authToken, - }, - }); + const result = await axios.get( + getUrl(`/api/v1/users/${selfUser.id}/location`), + { + headers: { + Authorization: authToken, + }, + } + ); newlocation = result.data; expect(result.status).toBe(200); @@ -1676,7 +2152,7 @@ describe('API test', () => { it('Update location', async () => { try { const result = await axios.patch( - getUrl('/location'), + getUrl('/api/v1/user/location'), { networkAddress: 'network address', }, @@ -1695,10 +2171,10 @@ describe('API test', () => { }); describe('current', () => { - it('Update current', async () => { + it('Update current place', async () => { try { const result = await axios.post( - getUrl('/current'), + getUrl('/api/v1/places/current'), { currentAPIKeyTokenId: 'test', }, @@ -1720,7 +2196,7 @@ describe('API test', () => { it('Get token', async () => { try { const result = await axios.get( - getUrl(`/account/${selfUser?.id}/tokens`), + getUrl(`/api/v1/account/${selfUser?.id}/tokens`), { headers: { Authorization: authToken, @@ -1737,4 +2213,3 @@ describe('API test', () => { }); }); }); - From e6fb7631413a982e9334a8d3d578368864ef3164 Mon Sep 17 00:00:00 2001 From: khilanlalani1503 Date: Thu, 28 Jul 2022 19:55:51 +0530 Subject: [PATCH 024/128] docker configurations added --- .../conf.d/vircadia_conf.conf | 6 +-- .../conf.d/vircadia_web_conf.conf | 43 ------------------- vircadia_metaverse_v2_api/docker-compose.yml | 31 +++++++------ vircadia_metaverse_v2_api/package.json | 4 +- vircadia_metaverse_v2_api/src/appconfig.ts | 2 +- 5 files changed, 25 insertions(+), 61 deletions(-) delete mode 100644 vircadia_metaverse_v2_api/conf.d/vircadia_web_conf.conf diff --git a/vircadia_metaverse_v2_api/conf.d/vircadia_conf.conf b/vircadia_metaverse_v2_api/conf.d/vircadia_conf.conf index 880d8f1e..d6048995 100644 --- a/vircadia_metaverse_v2_api/conf.d/vircadia_conf.conf +++ b/vircadia_metaverse_v2_api/conf.d/vircadia_conf.conf @@ -10,14 +10,14 @@ server gzip_disable "MSIE [1-6]\."; location / { - proxy_pass http://api:3030; + proxy_pass http://api:3040; } location ~ /\.(?:ht|git|svn|env) { deny all; } - listen [::]:3030 ssl http2; # managed by Certbot - listen 3030 ssl http2; # managed by Certbot + listen [::]:3040 ssl http2; # managed by Certbot + listen 3040 ssl http2; # managed by Certbot ssl_certificate /etc/letsencrypt/live/staging-2.digisomni.com/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/staging-2.digisomni.com/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot diff --git a/vircadia_metaverse_v2_api/conf.d/vircadia_web_conf.conf b/vircadia_metaverse_v2_api/conf.d/vircadia_web_conf.conf deleted file mode 100644 index 84cc727b..00000000 --- a/vircadia_metaverse_v2_api/conf.d/vircadia_web_conf.conf +++ /dev/null @@ -1,43 +0,0 @@ -server { - listen 80; - listen [::]:80; - server_name staging-2.digisomni.com; - - location ~ /\.(?:ht|git|svn|env) { - deny all; - } - return 308 https://staging-2.digisomni.com$request_uri; -} - -server -{ - - server_name staging-2.digisomni.com; - root /opt/goobieverse-website/dist/spa; - index index.html; - client_max_body_size 100m; - - gzip on; - gzip_vary on; - gzip_min_length 10240; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/json text/json; - gzip_disable "MSIE [1-6]\."; - - location / { - try_files $uri $uri/ $uri.html =404; - } - - location ~ /\.(?:ht|git|svn|env) { - deny all; - } - - listen [::]:443 ssl http2; # managed by Certbot - listen 443 ssl http2; # managed by Certbot - ssl_certificate /etc/letsencrypt/live/staging-2.digisomni.com/fullchain.pem; # managed by Certbot - ssl_certificate_key /etc/letsencrypt/live/staging-2.digisomni.com/privkey.pem; # managed by Certbot - include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot -} - - diff --git a/vircadia_metaverse_v2_api/docker-compose.yml b/vircadia_metaverse_v2_api/docker-compose.yml index 1a4eef20..3d47083a 100644 --- a/vircadia_metaverse_v2_api/docker-compose.yml +++ b/vircadia_metaverse_v2_api/docker-compose.yml @@ -6,36 +6,43 @@ services: container_name: mongodb_container image: mongo:latest restart: always - # ports: - # - 27018:27017 + #ports: + # - 27018:27017 volumes: - mongo_db:/data/db - + redis: + container_name: redis_container + restart: always + image: redis/redis-stack:latest + # command: redis-server --save 20 1 --loglevel warning + volumes: + - redis_server:/data/redis # Node API service api: - #container_name: vircadia-metaverse-v2 + #container_name: vircadia-metaverse build: . # ports: # - 3030:3030 volumes: - .:/usr/src/api environment: - PORT: 3030 - MONGODB_URL: mongodb://mongodb_container:27017 + PORT: 3040 + MONGODB_URL: mongodb://mongodb_container:27018 + REDIS_URL: redis://redis_container:6378 NAME: Vircadia depends_on: - - mongo_db + - mongo_db + - redis nginx: image: nginx:latest volumes: - ./conf.d:/etc/nginx/conf.d - /etc/letsencrypt:/etc/letsencrypt - - /opt/goobieverse-website/dist/spa:/opt/goobieverse-website/dist/spa depends_on: - - api + - api ports: - - 3030:3030 - - 443:443 - - 80:80 + - 3040:3040 + - 443:443 volumes: mongo_db: {} + redis_server: {} diff --git a/vircadia_metaverse_v2_api/package.json b/vircadia_metaverse_v2_api/package.json index fb28eabb..9998ecab 100644 --- a/vircadia_metaverse_v2_api/package.json +++ b/vircadia_metaverse_v2_api/package.json @@ -120,9 +120,9 @@ "shx": "^0.3.3", "ts-jest": "^27.0.7", "ts-node-dev": "^1.1.8", - "typedoc": "^0.22.12", + "typedoc": "^0.23.9", "typedoc-github-wiki-theme": "^1.0.0", - "typedoc-plugin-markdown": "^3.11.14", + "typedoc-plugin-markdown": "^3.11.10", "typedoc-plugin-no-inherit": "^1.3.1", "typescript": "^4.5.4" } diff --git a/vircadia_metaverse_v2_api/src/appconfig.ts b/vircadia_metaverse_v2_api/src/appconfig.ts index d73d3ab8..dc030460 100644 --- a/vircadia_metaverse_v2_api/src/appconfig.ts +++ b/vircadia_metaverse_v2_api/src/appconfig.ts @@ -41,7 +41,7 @@ dotenv.config({ const server = { local: process.env.LOCAL === 'true', hostName: process.env.SERVER_HOST, - port: process.env.PORT ?? 3030, + port: process.env.PORT ?? 3040, paginate: { default: 10, max: 100, From 1d63b2d1f50f68be503e5560459f96e3e86d5df9 Mon Sep 17 00:00:00 2001 From: khilanlalani1503 Date: Thu, 28 Jul 2022 20:33:55 +0530 Subject: [PATCH 025/128] docker configurations added --- vircadia_metaverse_v2_api/docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vircadia_metaverse_v2_api/docker-compose.yml b/vircadia_metaverse_v2_api/docker-compose.yml index 3d47083a..740bf5dd 100644 --- a/vircadia_metaverse_v2_api/docker-compose.yml +++ b/vircadia_metaverse_v2_api/docker-compose.yml @@ -3,7 +3,7 @@ version: '3.9' services: # MongoDB service mongo_db: - container_name: mongodb_container + container_name: mongodb_container_v1 image: mongo:latest restart: always #ports: @@ -11,7 +11,7 @@ services: volumes: - mongo_db:/data/db redis: - container_name: redis_container + container_name: redis_container_v1 restart: always image: redis/redis-stack:latest # command: redis-server --save 20 1 --loglevel warning @@ -27,8 +27,8 @@ services: - .:/usr/src/api environment: PORT: 3040 - MONGODB_URL: mongodb://mongodb_container:27018 - REDIS_URL: redis://redis_container:6378 + MONGODB_URL: mongodb://mongodb_container_v1:27018 + REDIS_URL: redis://redis_container_v1:6378 NAME: Vircadia depends_on: - mongo_db From 25b3c03825d851b29d27cd14d39ca2ca8956f715 Mon Sep 17 00:00:00 2001 From: khilanlalani1503 Date: Fri, 29 Jul 2022 11:40:13 +0530 Subject: [PATCH 026/128] docker configurations added --- vircadia_metaverse_v2_api/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vircadia_metaverse_v2_api/docker-compose.yml b/vircadia_metaverse_v2_api/docker-compose.yml index 740bf5dd..c11c511d 100644 --- a/vircadia_metaverse_v2_api/docker-compose.yml +++ b/vircadia_metaverse_v2_api/docker-compose.yml @@ -42,7 +42,7 @@ services: - api ports: - 3040:3040 - - 443:443 + - 445:445 volumes: mongo_db: {} redis_server: {} From 9e19508ced2182d4483519ddf0de806c6a6bd0e4 Mon Sep 17 00:00:00 2001 From: khilanlalani1503 Date: Fri, 29 Jul 2022 12:25:58 +0530 Subject: [PATCH 027/128] docker configurations added --- vircadia_metaverse_v2_api/public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vircadia_metaverse_v2_api/public/index.html b/vircadia_metaverse_v2_api/public/index.html index 4b15c5a3..f884185c 100644 --- a/vircadia_metaverse_v2_api/public/index.html +++ b/vircadia_metaverse_v2_api/public/index.html @@ -1,7 +1,7 @@ - Goobieverse + Vircadia