From 2b496f8433a4235a101c737bd7a008716975d5df Mon Sep 17 00:00:00 2001 From: charleswrayjr Date: Sat, 13 Sep 2025 01:32:17 -0500 Subject: [PATCH] Adding auth, media, and messaging. --- .idea/data_source_mapping.xml | 8 - .idea/zencoder-chat-index.xml | 1 - package-lock.json | 917 +++++++++++++++++- package.json | 1 + src/controllers/auth.controller.js | 95 +- src/controllers/media.controller.js | 128 +++ src/controllers/message.controller.js | 127 +++ src/controllers/message_group.controller.js | 89 ++ .../message_group_members.controller.js | 88 ++ src/controllers/post.controller.js | 129 +++ src/middleware/routeHelpers.js | 9 +- src/models/index.js | 870 +++++++---------- src/models/media.model.js | 107 ++ src/models/message.model.js | 108 +++ src/models/message_group.model.js | 64 ++ src/models/message_group_members.model.js | 118 +++ src/models/post.model.js | 108 +++ src/models/user.model.js | 295 +++--- src/routes/auth.routes.js | 3 +- src/routes/media.routes.js | 23 + src/routes/message.routes.js | 23 + src/routes/message_group.routes.js | 21 + src/routes/message_group_members.routes.js | 21 + src/routes/post.routes.js | 23 + 24 files changed, 2663 insertions(+), 713 deletions(-) delete mode 100644 .idea/data_source_mapping.xml create mode 100644 src/controllers/media.controller.js create mode 100644 src/controllers/message.controller.js create mode 100644 src/controllers/message_group.controller.js create mode 100644 src/controllers/message_group_members.controller.js create mode 100644 src/controllers/post.controller.js create mode 100644 src/models/media.model.js create mode 100644 src/models/message.model.js create mode 100644 src/models/message_group.model.js create mode 100644 src/models/message_group_members.model.js create mode 100644 src/models/post.model.js create mode 100644 src/routes/media.routes.js create mode 100644 src/routes/message.routes.js create mode 100644 src/routes/message_group.routes.js create mode 100644 src/routes/message_group_members.routes.js create mode 100644 src/routes/post.routes.js diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml deleted file mode 100644 index 4b94a0b..0000000 --- a/.idea/data_source_mapping.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/zencoder-chat-index.xml b/.idea/zencoder-chat-index.xml index 3708afa..2009da4 100644 --- a/.idea/zencoder-chat-index.xml +++ b/.idea/zencoder-chat-index.xml @@ -1,7 +1,6 @@ - \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0ec54a9..a0b3a3b 100755 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,12 @@ "name": "phs-api", "version": "1.0.0", "dependencies": { + "bcrypt": "^5.0.1", "body-parser": "^2.2.0", "cookie-parser": "~1.4.4", "cors": "^2.8.5", "debug": "~2.6.9", + "dockerode": "^4.0.7", "express": "5.0.0", "express-compression": "^1.0.2", "http-errors": "~1.6.3", @@ -541,6 +543,11 @@ "node": ">=6.9.0" } }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==" + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -578,6 +585,35 @@ "tslib": "^2.4.0" } }, + "node_modules/@grpc/grpc-js": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.4.tgz", + "integrity": "sha512-GsFaMXCkMqkKIvwCQjCrwH+GHbPKBjhwo/8ZuUkWHqbI73Kky9I+pQltrlT0+MWpedCoosda53lgjYfyEPgxBg==", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.15", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", + "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -979,6 +1015,67 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -1013,6 +1110,60 @@ "url": "https://opencollective.com/pkgr" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, "node_modules/@scarf/scarf": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", @@ -1122,7 +1273,6 @@ "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", - "dev": true, "dependencies": { "undici-types": "~7.10.0" } @@ -1404,6 +1554,11 @@ "win32" ] }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -1427,6 +1582,38 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1458,7 +1645,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1487,6 +1673,24 @@ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1629,8 +1833,26 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, "node_modules/basic-auth": { "version": "2.0.1", @@ -1643,6 +1865,19 @@ "node": ">= 0.8" } }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -1651,6 +1886,21 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -1805,6 +2055,29 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -1941,6 +2214,14 @@ "is-regex": "^1.0.3" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/ci-info": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", @@ -1966,7 +2247,6 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -1980,7 +2260,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1988,14 +2267,12 @@ "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -2009,7 +2286,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -2021,7 +2297,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -2054,7 +2329,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2065,8 +2339,15 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } }, "node_modules/compressible": { "version": "2.0.18", @@ -2082,8 +2363,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { "version": "2.0.0", @@ -2099,6 +2379,11 @@ "typedarray": "^0.0.6" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + }, "node_modules/constantinople": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", @@ -2256,6 +2541,11 @@ "node": ">=0.10.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -2264,6 +2554,14 @@ "node": ">= 0.6" } }, + "node_modules/detect-libc": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2273,6 +2571,58 @@ "node": ">=8" } }, + "node_modules/docker-modem": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz", + "integrity": "sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==", + "dependencies": { + "debug": "^4.1.1", + "readable-stream": "^3.5.0", + "split-ca": "^1.0.1", + "ssh2": "^1.15.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/docker-modem/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/docker-modem/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/dockerode": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.8.tgz", + "integrity": "sha512-HdPBprWmwfHMHi12AVIFDhXIqIS+EpiOVkZaAZxgML4xf5McqEZjJZtahTPkLDxWOt84ApfWPAH9EoQwOiaAIQ==", + "dependencies": { + "@balena/dockerignore": "^1.0.2", + "@grpc/grpc-js": "^1.11.1", + "@grpc/proto-loader": "^0.7.13", + "docker-modem": "^5.0.6", + "protobufjs": "^7.3.2", + "tar-fs": "~2.1.3", + "uuid": "^10.0.0" + }, + "engines": { + "node": ">= 8.0" + } + }, "node_modules/doctypes": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", @@ -2342,6 +2692,14 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2382,7 +2740,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "engines": { "node": ">=6" } @@ -2830,6 +3187,11 @@ "node": ">= 0.8" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -2843,11 +3205,37 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -2860,15 +3248,77 @@ "darwin" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/gensync": { @@ -2884,7 +3334,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -3015,6 +3464,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3046,6 +3500,39 @@ "node": ">= 0.6" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3066,6 +3553,25 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -3099,7 +3605,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3151,7 +3656,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -4061,6 +4565,11 @@ "node": ">=8" } }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -4132,6 +4641,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4291,6 +4805,34 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -4302,6 +4844,11 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, "node_modules/morgan": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", @@ -4413,6 +4960,30 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4425,6 +4996,20 @@ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4446,6 +5031,18 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4637,7 +5234,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4927,6 +5523,29 @@ "asap": "~2.0.3" } }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -5051,6 +5670,15 @@ "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==" }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/pure-rand": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", @@ -5182,7 +5810,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5232,6 +5859,61 @@ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -5416,6 +6098,11 @@ "node": ">= 18" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "node_modules/setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", @@ -5558,6 +6245,11 @@ "node": ">=14.0.0" } }, + "node_modules/split-ca": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", + "integrity": "sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==" + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -5898,6 +6590,77 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -5986,6 +6749,11 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==" }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -6040,8 +6808,7 @@ "node_modules/undici-types": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", - "dev": true + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" }, "node_modules/universalify": { "version": "0.1.2", @@ -6136,6 +6903,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -6175,6 +6954,20 @@ "makeerror": "1.0.12" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6190,6 +6983,51 @@ "node": ">= 8" } }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/with": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", @@ -6322,7 +7160,6 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, "engines": { "node": ">=10" } @@ -6337,7 +7174,6 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -6355,7 +7191,6 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, "engines": { "node": ">=12" } @@ -6364,7 +7199,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { "node": ">=8" } @@ -6372,14 +7206,12 @@ "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6393,7 +7225,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, diff --git a/package.json b/package.json index 4372ab0..b279d6e 100755 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "start-debug": "NODE_ENV=\"DEVELOPMENT\" nodemon --trace-warnings --inspect=0.0.0.0:9229 app.js" }, "dependencies": { + "bcrypt": "^5.0.1", "body-parser": "^2.2.0", "cookie-parser": "~1.4.4", "cors": "^2.8.5", diff --git a/src/controllers/auth.controller.js b/src/controllers/auth.controller.js index bf429b6..0dcd1dd 100644 --- a/src/controllers/auth.controller.js +++ b/src/controllers/auth.controller.js @@ -1,5 +1,96 @@ +/** + * @file Authentication controller for handling authentication requests + */ + +const db = require('../models'); +const createError = require('http-errors'); + +const updateUserAndReturn = async (validUser, res) => { + const token = await validUser.createToken(); + res.status(200).send({ success: true, user: validUser.to_safe_json(), token }); +}; + module.exports = { - login: (req, res, next) => { - console.log('login'); + create: async (req, res, next) => { + const { user_id, password, password_confirmation } = req.body; + if (!user_id || !password || !password_confirmation || password !== password_confirmation) { + return next(createError(400, 'Invalid parameters: user_id, password, and password_confirmation are required and passwords must match.')); + } + try { + const user = await db.user.find_one({ id: user_id }); + if (!user) { + return next(createError(400, 'There is no user with this id.')); + } + const existing = await db.authentication.find_by_user_id(user_id); + if (existing) { + return next(createError(400, 'Authentication for this user already exists.')); + } + await user.hashPassword(password); + const auth = await db.authentication.find_by_user_id(user_id); + res.status(200).send(auth.to_safe_json()); + } catch (e) { + logger.error(`Create auth error: ${e.message}`); + next(e); + } + }, + + update: async (req, res, next) => { + try { + const auth = await db.authentication.find_one(req.params); + if (!auth) return next(createError(404, 'Authentication record not found')); + const updated = await auth.update(req.body); + res.status(200).send(updated.to_safe_json()); + } catch (e) { + logger.error(`Update auth error: ${e.message}`); + next(createError(400, 'Invalid parameters.')); + } + }, + + show: async (req, res, next) => { + try { + const auth = await db.authentication.find_one(req.params); + if (!auth) return next(createError(404, 'Authentication record not found')); + res.status(200).send(auth.to_safe_json()); + } catch (e) { + logger.error(`Show auth error: ${e.message}`); + next(e); + } + }, + + index: async (req, res, next) => { + try { + const auths = await db.authentication.find_many(req.query); + if (!auths.length) return next(createError(404, 'No authentication records found')); + res.status(200).send(auths.map(a => a.to_safe_json())); + } catch (e) { + logger.error(`Index auth error: ${e.message}`); + next(e); + } + }, + + authenticate: async (req, res, next) => { + try { + const { email, password } = req.body; + if (!email || !password) { + return next(createError(400, !email ? 'You must provide an email to login.' : 'You must provide a password to login.')); + } + const user = await db.user.find_by_email(email); + if (!user || !user.is_active || user.is_deleted) { + return res.status(401).send({ success: false, user: null, token: null }); + } + const auth = await db.authentication.find_by_user_id(user.id); + if (auth.is_locked) { + return res.status(401).send({ message: 'Your account is locked due to suspicious activity. Please contact us to continue.' }); + } + const isValid = await user.comparePassword(password); + if (!isValid) { + await user.failLogin(); + return res.status(401).send({ success: false, user: null, token: null }); + } + return updateUserAndReturn(user, res); + } catch (e) { + logger.error(`Authenticate error: ${e.message}`); + next(e); + } }, }; \ No newline at end of file diff --git a/src/controllers/media.controller.js b/src/controllers/media.controller.js new file mode 100644 index 0000000..d4fdde2 --- /dev/null +++ b/src/controllers/media.controller.js @@ -0,0 +1,128 @@ +/** + * @file Media controller for handling media-related API requests + */ + +const db = require('../models'); +const createError = require('http-errors'); + +/** + * Media controller + * @type {Object} + */ +module.exports = { + /** + * Create a new media + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async create(req, res, next) { + try { + const media_data = req.body; + const media = await db.media.create(media_data); + res.json(media); + } catch (error) { + logger.error(`Create media error: ${error.message}`); + next(createError(error.status || 409, error.message)); + } + }, + + /** + * Find media by file path + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_by_file_path(req, res, next) { + try { + const { file_path } = req.params; + const media = await db.media.find_by_file_path(file_path); + if (!media) return next(createError(404, 'Media not found')); + res.json(media); + } catch (error) { + logger.error(`Find media by file path error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Find one media by ID + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_one(req, res, next) { + try { + const { id } = req.params; + const media = await db.media.find_one({ id: parseInt(id) }); + if (!media) return next(createError(404, 'Media not found')); + res.json(media); + } catch (error) { + logger.error(`Find media error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Find many media + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_many(req, res, next) { + try { + const { limit = '100', offset = '0', ...where } = req.query; + const medias = await db.media.find_many(where, [], null, parseInt(limit), parseInt(offset)); + res.json(medias); + } catch (error) { + logger.error(`Find many media error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Update a media + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async update(req, res, next) { + try { + const { id } = req.params; + const media_data = req.body; + const media = await db.media.instance().find_one({ id: parseInt(id) }); + if (!media) return next(createError(404, 'Media not found')); + const updated_media = await media.update(media_data); + res.json(updated_media); + } catch (error) { + logger.error(`Update media error: ${error.message}`); + next(createError(error.status || 400, error.message)); + } + }, + + /** + * Soft delete a media + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async soft_delete(req, res, next) { + try { + const { id } = req.params; + const { deleted_by_id } = req.body; + const media = await db.media.instance().find_one({ id: parseInt(id) }); + if (!media) return next(createError(404, 'Media not found')); + const deleted_media = await media.soft_delete(deleted_by_id); + res.json(deleted_media); + } catch (error) { + logger.error(`Soft delete media error: ${error.message}`); + next(createError(error.status || 400, error.message)); + } + } +}; \ No newline at end of file diff --git a/src/controllers/message.controller.js b/src/controllers/message.controller.js new file mode 100644 index 0000000..b72e7ce --- /dev/null +++ b/src/controllers/message.controller.js @@ -0,0 +1,127 @@ +/** + * @file Message controller for handling message-related API requests + */ + +const db = require('../models'); +const createError = require('http-errors'); +const logger = global.logger; + +/** + * Message controller + * @type {Object} + */ +module.exports = { + /** + * Create a new message + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async create(req, res, next) { + try { + const message_data = req.body; + const message = await db.message.create(message_data); + res.json(message); + } catch (error) { + logger.error(`Create message error: ${error.message}`); + next(createError(error.status || 409, error.message)); + } + }, + + /** + * Find messages by group ID + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_by_group_id(req, res, next) { + try { + const { group_id } = req.params; + const { limit = 100, offset = 0 } = req.query; + const messages = await db.message.find_by_group_id(parseInt(group_id), [], null, parseInt(limit), parseInt(offset)); + res.json(messages); + } catch (error) { + logger.error(`Find messages by group ID error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Find messages by recipient ID + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_by_recipient_id(req, res, next) { + try { + const { recipient_id } = req.params; + const { limit = 100, offset = 0 } = req.query; + const messages = await db.message.find_by_recipient_id(parseInt(recipient_id), [], null, parseInt(limit), parseInt(offset)); + res.json(messages); + } catch (error) { + logger.error(`Find messages by recipient ID error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Find one message by ID + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_one(req, res, next) { + try { + const { id } = req.params; + const message = await db.message.find_one({ id: parseInt(id) }); + if (!message) return next(createError(404, 'Message not found')); + res.json(message); + } + catch (error) { + logger.error(`Find message error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Find many messages + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_many(req, res, next) { + try { + const { limit = 100, offset = 0, ...where } = req.query; + const messages = await db.message.find_many(where, [], null, parseInt(limit), parseInt(offset)); + res.json(messages); + } catch (error) { + logger.error(`Find many messages error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Mark a message as read + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async mark_as_read(req, res, next) { + try { + const { id } = req.params; + const message = await db.message.instance().find_one({ id: parseInt(id) }); + if (!message) return next(createError(404, 'Message not found')); + const updated_message = await message.mark_as_read(); + res.json(updated_message); + } catch (error) { + logger.error(`Mark message as read error: ${error.message}`); + next(createError(error.status || 400, error.message)); + } + } +}; \ No newline at end of file diff --git a/src/controllers/message_group.controller.js b/src/controllers/message_group.controller.js new file mode 100644 index 0000000..55909d3 --- /dev/null +++ b/src/controllers/message_group.controller.js @@ -0,0 +1,89 @@ +/** + * @file Message group controller for handling message group-related API requests + */ + +const db = require('../models'); +const createError = require('http-errors'); +const logger = global.logger; + +/** + * Message group controller + * @type {Object} + */ +module.exports = { + /** + * Create a new message group + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async create(req, res, next) { + try { + const message_group_data = req.body; + const message_group = await db.message_group.create(message_group_data); + res.json(message_group); + } catch (error) { + logger.error(`Create message group error: ${error.message}`); + next(createError(error.status || 409, error.message)); + } + }, + + /** + * Find one message group by ID + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_one(req, res, next) { + try { + const { id } = req.params; + const message_group = await db.message_group.find_one({ id: parseInt(id) }); + if (!message_group) return next(createError(404, 'Message group not found')); + res.json(message_group); + } catch (error) { + logger.error(`Find message group error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Find many message groups + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_many(req, res, next) { + try { + const { limit = 100, offset = 0, ...where } = req.query; + const message_groups = await db.message_group.find_many(where, [], null, parseInt(limit), parseInt(offset)); + res.json(message_groups); + } catch (error) { + logger.error(`Find many message groups error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Update a message group + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async update(req, res, next) { + try { + const { id } = req.params; + const message_group_data = req.body; + const message_group = await db.message_group.instance().find_one({ id: parseInt(id) }); + if (!message_group) return next(createError(404, 'Message group not found')); + const updated_message_group = await message_group.update(message_group_data); + res.json(updated_message_group); + } catch (error) { + logger.error(`Update message group error: ${error.message}`); + next(createError(error.status || 400, error.message)); + } + } +}; \ No newline at end of file diff --git a/src/controllers/message_group_members.controller.js b/src/controllers/message_group_members.controller.js new file mode 100644 index 0000000..1024871 --- /dev/null +++ b/src/controllers/message_group_members.controller.js @@ -0,0 +1,88 @@ +/** + * @file Message group members controller for handling message group member-related API requests + */ + +const db = require('../models'); +const createError = require('http-errors'); +const logger = global.logger; + +/** + * Message group members controller + * @type {Object} + */ +module.exports = { + /** + * Add a message group member relation + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async add_relation(req, res, next) { + try { + const { group_id, user_id } = req.body; + const relation = await db.message_group_members.add_relation(group_id, user_id); + res.json(relation); + } catch (error) { + logger.error(`Add message group member relation error: ${error.message}`); + next(createError(error.status || 409, error.message)); + } + }, + + /** + * Remove a message group member relation + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async remove_relation(req, res, next) { + try { + const { user_id, group_id } = req.body; + const relation = await db.message_group_members.remove_relation(user_id, group_id); + if (!relation) return next(createError(404, 'Relation not found')); + res.json(relation); + } catch (error) { + logger.error(`Remove message group member relation error: ${error.message}`); + next(createError(error.status || 400, error.message)); + } + }, + + /** + * Find message group member relation by IDs + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_by_ids(req, res, next) { + try { + const { group_id, user_id } = req.params; + const relation = await db.message_group_members.find_by_ids(parseInt(group_id), parseInt(user_id)); + if (!relation) return next(createError(404, 'Relation not found')); + res.json(relation); + } catch (error) { + logger.error(`Find message group member by IDs error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Find message group member relations by group ID + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_by_group_id(req, res, next) { + try { + const { group_id } = req.params; + const { limit = 100, offset = 0 } = req.query; + const relations = await db.message_group_members.find_by_group_id(parseInt(group_id), [], null, parseInt(limit), parseInt(offset)); + res.json(relations); + } catch (error) { + logger.error(`Find message group members by group ID error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + } +}; \ No newline at end of file diff --git a/src/controllers/post.controller.js b/src/controllers/post.controller.js new file mode 100644 index 0000000..09f21a2 --- /dev/null +++ b/src/controllers/post.controller.js @@ -0,0 +1,129 @@ +/** + * @file Post controller for handling post-related API requests + */ + +const db = require('../models'); +const createError = require('http-errors'); +const logger = global.logger; + +/** + * Post controller + * @type {Object} + */ +module.exports = { + /** + * Create a new post + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async create(req, res, next) { + try { + const post_data = req.body; + const post = await db.post.create(post_data); + res.json(post); + } catch (error) { + logger.error(`Create post error: ${error.message}`); + next(createError(error.status || 409, error.message)); + } + }, + + /** + * Find post by title + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_by_title(req, res, next) { + try { + const { title } = req.params; + const post = await db.post.find_by_title(title); + if (!post) return next(createError(404, 'Post not found')); + res.json(post); + } catch (error) { + logger.error(`Find post by title error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Find one post by ID + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_one(req, res, next) { + try { + const { id } = req.params; + const post = await db.post.find_one({ id: parseInt(id) }); + if (!post) return next(createError(404, 'Post not found')); + res.json(post); + } catch (error) { + logger.error(`Find post error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Find many posts + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async find_many(req, res, next) { + try { + const { limit = 100, offset = 0, ...where } = req.query; + const posts = await db.post.find_many(where, [], null, parseInt(limit), parseInt(offset)); + res.json(posts); + } catch (error) { + logger.error(`Find many posts error: ${error.message}`); + next(createError(error.status || 500, error.message)); + } + }, + + /** + * Update a post + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async update(req, res, next) { + try { + const { id } = req.params; + const post_data = req.body; + const post = await db.post.instance().find_one({ id: parseInt(id) }); + if (!post) return next(createError(404, 'Post not found')); + const updated_post = await post.update(post_data); + res.json(updated_post); + } catch (error) { + logger.error(`Update post error: ${error.message}`); + next(createError(error.status || 400, error.message)); + } + }, + + /** + * Soft delete a post + * @param {Object} req - Express request object + * @param {Object} res - Express response object + * @param {Function} next - Express next middleware function + * @returns {Promise} + */ + async soft_delete(req, res, next) { + try { + const { id } = req.params; + const { deleted_by_id } = req.body; + const post = await db.post.instance().find_one({ id: parseInt(id) }); + if (!post) return next(createError(404, 'Post not found')); + const deleted_post = await post.soft_delete(deleted_by_id); + res.json(deleted_post); + } catch (error) { + logger.error(`Soft delete post error: ${error.message}`); + next(createError(error.status || 400, error.message)); + } + } +}; \ No newline at end of file diff --git a/src/middleware/routeHelpers.js b/src/middleware/routeHelpers.js index 939138a..9bcc4ba 100755 --- a/src/middleware/routeHelpers.js +++ b/src/middleware/routeHelpers.js @@ -38,4 +38,11 @@ const allowApi = function (apiKey, callback) { return checkKey; }; -module.exports = { validateAuth, allowApi }; \ No newline at end of file +const restrictToRoles = (roles) => (req, res, next) => { + if (!req.user || !req.user.roles.some(r => roles.includes(r))) { + return next(createError(403, 'Forbidden: Insufficient role permissions')); + } + next(); +}; + +module.exports = { validateAuth, allowApi, restrictToRoles }; \ No newline at end of file diff --git a/src/models/index.js b/src/models/index.js index d6d6f72..59e2c8e 100644 --- a/src/models/index.js +++ b/src/models/index.js @@ -5,768 +5,646 @@ */ const model_cache = new Map(); -const { NotFoundError, FailedToCreateError, ValidationError } = require( './model' ); +const { NotFoundError } = require( './model' ); /** * Get a model class from cache or require it * @param {string} name - Model name * @returns {Function} Model class */ -const get_model = ( name ) => { - if (!model_cache.has( name )) { - model_cache.set( name, require( `./${ name }.model` ) ); +const get_model = (name) => { + if (!model_cache.has(name)) { + model_cache.set(name, require(`./${name}.model`)); } - return model_cache.get( name ); + return model_cache.get(name); }; /** * @returns {Function} User model class */ -const get_user_model = () => get_model( 'user' ); +const get_user_model = () => get_model('user'); /** * @returns {Function} Phone number model class */ -const get_phone_number_model = () => get_model( 'phone_number' ); +const get_phone_number_model = () => get_model('phone_number'); /** * @returns {Function} User phone numbers model class */ -const get_user_phone_numbers_model = () => get_model( 'user_phone_numbers' ); +const get_user_phone_numbers_model = () => get_model('user_phone_numbers'); /** * @returns {Function} Address model class */ -const get_address_model = () => get_model( 'address' ); +const get_address_model = () => get_model('address'); /** * @returns {Function} User addresses model class */ -const get_user_addresses_model = () => get_model( 'user_addresses' ); +const get_user_addresses_model = () => get_model('user_addresses'); /** * @returns {Function} Authentication model class */ -const get_authentication_model = () => get_model( 'authentication' ); +const get_authentication_model = () => get_model('authentication'); /** * @returns {Function} Role model class */ -const get_role_model = () => get_model( 'role' ); +const get_role_model = () => get_model('role'); /** * @returns {Function} User roles model class */ -const get_user_roles_model = () => get_model( 'user_roles' ); +const get_user_roles_model = () => get_model('user_roles'); + +/** + * @returns {Function} Media model class + */ +const get_media_model = () => get_model('media'); + +/** + * @returns {Function} Post model class + */ +const get_post_model = () => get_model('post'); + +/** + * @returns {Function} Message group model class + */ +const get_message_group_model = () => get_model('message_group'); + +/** + * @returns {Function} Message group members model class + */ +const get_message_group_members_model = () => get_model('message_group_members'); + +/** + * @returns {Function} Message model class + */ +const get_message_model = () => get_model('message'); /** * Unified database interface * @type {Object} */ const db = { - user:{ - /** - * Create a new user - * @param {Object} user_data - User data - * @returns {Promise} Created user instance - * @throws {ValidationError} If required fields are missing - * @throws {FailedToCreateError} If creation fails - */ - async create( user_data ) { + user: { + async create(user_data) { try { - return await get_user_model().create( user_data ); + return await get_user_model().create(user_data); } catch (error) { - logger.error( `Failed to create user: ${ error.message }` ); + logger.error(`Failed to create user: ${error.message}`); throw error; } }, - /** - * Find user by email - * @param {string} email - Email to search for - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} User instance or null - */ - async find_by_email( email, excludes ) { + async find_by_email(email, excludes) { try { - return await get_user_model().find_by_email( email, excludes ); + return await get_user_model().find_by_email(email, excludes); } catch (error) { - logger.error( `Failed to find user by email: ${ error.message }` ); + logger.error(`Failed to find user by email: ${error.message}`); throw error; } }, - /** - * Find user by nickname - * @param {string} nickname - Nickname to search for - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} User instance or null - */ - async find_by_nickname( nickname, excludes ) { + async find_by_nickname(nickname, excludes) { try { - return await get_user_model().find_by_nickname( nickname, excludes ); + return await get_user_model().find_by_nickname(nickname, excludes); } catch (error) { - logger.error( `Failed to find user by nickname: ${ error.message }` ); + logger.error(`Failed to find user by nickname: ${error.message}`); throw error; } }, - /** - * Find active users - * @param {string[]} [excludes] - Fields to exclude - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit] - Maximum number of records - * @param {number} [offset] - Number of records to skip - * @returns {Promise} Array of active users - */ - async find_active( excludes, order_by, limit, offset ) { + async find_active(excludes, order_by, limit, offset) { try { - return await get_user_model().find_active( excludes, order_by, limit, offset ); + return await get_user_model().find_active(excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to find active users: ${ error.message }` ); + logger.error(`Failed to find active users: ${error.message}`); throw error; } }, - /** - * Find deleted users - * @param {string[]} [excludes] - Fields to exclude - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit] - Maximum number of records - * @param {number} [offset] - Number of records to skip - * @returns {Promise} Array of deleted users - */ - async find_deleted( excludes, order_by, limit, offset ) { + async find_deleted(excludes, order_by, limit, offset) { try { - return await get_user_model().find_deleted( excludes, order_by, limit, offset ); + return await get_user_model().find_deleted(excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to find deleted users: ${ error.message }` ); + logger.error(`Failed to find deleted users: ${error.message}`); throw error; } }, - /** - * Find one user - * @param {Object} where - Conditions - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} User instance or null - */ - async find_one( where, excludes ) { + async find_one(where, excludes) { try { - return await new (get_user_model())().find_one( where, excludes ); + return await new (get_user_model())().find_one(where, excludes); } catch (error) { - logger.error( `Failed to find one user: ${ error.message }` ); + logger.error(`Failed to find one user: ${error.message}`); throw error; } }, - /** - * Find many users - * @param {Object} [where] - Conditions - * @param {string[]} [excludes] - Fields to exclude - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit] - Maximum number of records - * @param {number} [offset] - Number of records to skip - * @returns {Promise} Array of users - */ - async find_many( where, excludes, order_by, limit, offset ) { + async find_many(where, excludes, order_by, limit, offset) { try { - return await new (get_user_model())().find_many( where, excludes, order_by, limit, offset ); + return await new (get_user_model())().find_many(where, excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to find many users: ${ error.message }` ); + logger.error(`Failed to find many users: ${error.message}`); throw error; } }, - /** - * Deactivate user - * @param {number|string} id - User ID - * @param {number|string} deactivated_by_id - ID of user performing deactivation - * @returns {Promise} Updated user instance - * @throws {NotFoundError} If user not found - */ - async deactivate( id, deactivated_by_id ) { + async deactivate(id, deactivated_by_id) { try { - const user = await new (get_user_model())().find_one( { id }, [] ); - if (!user) throw new NotFoundError( 'User not found' ); - return await user.deactivate( deactivated_by_id ); + const user = await new (get_user_model())().find_one({ id }, []); + if (!user) throw new NotFoundError('User not found'); + return await user.deactivate(deactivated_by_id); } catch (error) { - logger.error( `Failed to deactivate user: ${ error.message }` ); + logger.error(`Failed to deactivate user: ${error.message}`); throw error; } }, - /** - * Reactivate user - * @param {number|string} id - User ID - * @returns {Promise} Updated user instance - * @throws {NotFoundError} If user not found - */ - async reactivate( id ) { + async reactivate(id) { try { - const user = await new (get_user_model())().find_one( { id }, [] ); - if (!user) throw new NotFoundError( 'User not found' ); + const user = await new (get_user_model())().find_one({ id }, []); + if (!user) throw new NotFoundError('User not found'); return await user.reactivate(); } catch (error) { - logger.error( `Failed to reactivate user: ${ error.message }` ); + logger.error(`Failed to reactivate user: ${error.message}`); throw error; } }, - /** - * Soft delete user - * @param {number|string} id - User ID - * @param {number|string} deleted_by_id - ID of user performing deletion - * @returns {Promise} Updated user instance - * @throws {NotFoundError} If user not found - */ - async soft_delete( id, deleted_by_id ) { + async soft_delete(id, deleted_by_id) { try { - const user = await new (get_user_model())().find_one( { id }, [] ); - if (!user) throw new NotFoundError( 'User not found' ); - return await user.soft_delete( deleted_by_id ); + const user = await new (get_user_model())().find_one({ id }, []); + if (!user) throw new NotFoundError('User not found'); + return await user.soft_delete(deleted_by_id); } catch (error) { - logger.error( `Failed to soft delete user: ${ error.message }` ); + logger.error(`Failed to soft delete user: ${error.message}`); throw error; } }, - /** - * Create a new User instance - * @returns {User} User instance - */ - instance:() => new (get_user_model())() + instance: () => new (get_user_model())() }, - phone_number:{ - /** - * Create a new phone number - * @param {Object} phone_data - Phone number data - * @returns {Promise} Created phone number instance - * @throws {ValidationError} If required fields are missing - * @throws {FailedToCreateError} If creation fails - */ - async create( phone_data ) { + phone_number: { + async create(phone_data) { try { - return await get_phone_number_model().create( phone_data ); + return await get_phone_number_model().create(phone_data); } catch (error) { - logger.error( `Failed to create phone number: ${ error.message }` ); + logger.error(`Failed to create phone number: ${error.message}`); throw error; } }, - /** - * Find phone number by number - * @param {string} number - Phone number to search for - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Phone number instance or null - */ - async find_by_number( number, excludes ) { + async find_by_number(number, excludes) { try { - return await get_phone_number_model().find_by_number( number, excludes ); + return await get_phone_number_model().find_by_number(number, excludes); } catch (error) { - logger.error( `Failed to find phone number: ${ error.message }` ); + logger.error(`Failed to find phone number: ${error.message}`); throw error; } }, - /** - * Find one phone number - * @param {Object} where - Conditions - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Phone number instance or null - */ - async find_one( where, excludes ) { + async find_one(where, excludes) { try { - return await new (get_phone_number_model())().find_one( where, excludes ); + return await new (get_phone_number_model())().find_one(where, excludes); } catch (error) { - logger.error( `Failed to find one phone number: ${ error.message }` ); + logger.error(`Failed to find one phone number: ${error.message}`); throw error; } }, - /** - * Find many phone numbers - * @param {Object} [where] - Conditions - * @param {string[]} [excludes] - Fields to exclude - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit] - Maximum number of records - * @param {number} [offset] - Number of records to skip - * @returns {Promise} Array of phone numbers - */ - async find_many( where, excludes, order_by, limit, offset ) { + async find_many(where, excludes, order_by, limit, offset) { try { - return await new (get_phone_number_model())().find_many( where, excludes, order_by, limit, offset ); + return await new (get_phone_number_model())().find_many(where, excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to find many phone numbers: ${ error.message }` ); + logger.error(`Failed to find many phone numbers: ${error.message}`); throw error; } }, - /** - * Create a new PhoneNumber instance - * @returns {PhoneNumber} PhoneNumber instance - */ - instance:() => new (get_phone_number_model())() + instance: () => new (get_phone_number_model())() }, - user_phone_numbers:{ - /** - * Add a user-phone number relation - * @param {number|string} user_id - User ID - * @param {number|string} phone_number_id - Phone number ID - * @returns {Promise} Created relation instance - * @throws {ValidationError} If IDs are invalid - * @throws {FailedToCreateError} If creation fails - */ - async add_relation( user_id, phone_number_id ) { + user_phone_numbers: { + async add_relation(user_id, phone_number_id) { try { - return await get_user_phone_numbers_model().add_relation( user_id, phone_number_id ); + return await get_user_phone_numbers_model().add_relation(user_id, phone_number_id); } catch (error) { - logger.error( `Failed to add user-phone number relation: ${ error.message }` ); + logger.error(`Failed to add user-phone number relation: ${error.message}`); throw error; } }, - /** - * Remove a user-phone number relation - * @param {number|string} phone_number_id - Phone number ID - * @param {number|string|undefined} [user_id] - User ID - * @returns {Promise} Deleted relation instance or null - * @throws {ValidationError} If IDs are invalid - */ - async remove_relation( phone_number_id, user_id ) { + async remove_relation(phone_number_id, user_id) { try { - return await get_user_phone_numbers_model().remove_relation( phone_number_id, user_id ); + return await get_user_phone_numbers_model().remove_relation(phone_number_id, user_id); } catch (error) { - logger.error( `Failed to remove user-phone number relation: ${ error.message }` ); + logger.error(`Failed to remove user-phone number relation: ${error.message}`); throw error; } }, - /** - * Find user-phone number relation by IDs - * @param {number|string} user_id - User ID - * @param {number|string} phone_number_id - Phone number ID - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Relation instance or null - */ - async find_by_ids( user_id, phone_number_id, excludes ) { + async find_by_ids(user_id, phone_number_id, excludes) { try { - return await get_user_phone_numbers_model().find_by_ids( user_id, phone_number_id, excludes ); + return await get_user_phone_numbers_model().find_by_ids(user_id, phone_number_id, excludes); } catch (error) { - logger.error( `Failed to find user-phone number by IDs: ${ error.message }` ); + logger.error(`Failed to find user-phone number by IDs: ${error.message}`); throw error; } }, - /** - * Find user-phone number relations by user ID - * @param {number|string} user_id - User ID - * @param {string[]} [excludes] - Fields to exclude - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit] - Maximum number of records - * @param {number} [offset] - Number of records to skip - * @returns {Promise} Array of relations - */ - async find_by_user_id( user_id, excludes, order_by, limit, offset ) { + async find_by_user_id(user_id, excludes, order_by, limit, offset) { try { - return await get_user_phone_numbers_model().find_by_user_id( user_id, excludes, order_by, limit, offset ); + return await get_user_phone_numbers_model().find_by_user_id(user_id, excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to find user-phone numbers by user ID: ${ error.message }` ); + logger.error(`Failed to find user-phone numbers by user ID: ${error.message}`); throw error; } }, - /** - * Create a new UserPhoneNumber instance - * @returns {UserPhoneNumber} UserPhoneNumber instance - */ - instance:() => new (get_user_phone_numbers_model())() + instance: () => new (get_user_phone_numbers_model())() }, - address:{ - /** - * Create a new address - * @param {Object} address_data - Address data - * @returns {Promise
} Created address instance - * @throws {ValidationError} If required fields are missing - * @throws {FailedToCreateError} If creation fails - */ - async create( address_data ) { + address: { + async create(address_data) { try { - return await get_address_model().create( address_data ); + return await get_address_model().create(address_data); } catch (error) { - logger.error( `Failed to create address: ${ error.message }` ); + logger.error(`Failed to create address: ${error.message}`); throw error; } }, - /** - * Find address by zip code - * @param {string} zip_code - Zip code to search for - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Address instance or null - */ - async find_by_zip_code( zip_code, excludes ) { + async find_by_zip_code(zip_code, excludes) { try { - return await get_address_model().find_by_zip_code( zip_code, excludes ); + return await get_address_model().find_by_zip_code(zip_code, excludes); } catch (error) { - logger.error( `Failed to find address by zip code: ${ error.message }` ); + logger.error(`Failed to find address by zip code: ${error.message}`); throw error; } }, - /** - * Find one address - * @param {Object} where - Conditions - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Address instance or null - */ - async find_one( where, excludes ) { + async find_one(where, excludes) { try { - return await new (get_address_model())().find_one( where, excludes ); + return await new (get_address_model())().find_one(where, excludes); } catch (error) { - logger.error( `Failed to find one address: ${ error.message }` ); + logger.error(`Failed to find one address: ${error.message}`); throw error; } }, - /** - * Find many addresses - * @param {Object} [where] - Conditions - * @param {string[]} [excludes] - Fields to exclude - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit] - Maximum number of records - * @param {number} [offset] - Number of records to skip - * @returns {Promise} Array of addresses - */ - async find_many( where, excludes, order_by, limit, offset ) { + async find_many(where, excludes, order_by, limit, offset) { try { - return await new (get_address_model())().find_many( where, excludes, order_by, limit, offset ); + return await new (get_address_model())().find_many(where, excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to find many addresses: ${ error.message }` ); + logger.error(`Failed to find many addresses: ${error.message}`); throw error; } }, - /** - * Create a new Address instance - * @returns {Address} Address instance - */ - instance:() => new (get_address_model())() + instance: () => new (get_address_model())() }, - user_addresses:{ - /** - * Add a user-address relation - * @param {number|string} user_id - User ID - * @param {number|string} address_id - Address ID - * @returns {Promise} Created relation instance - * @throws {ValidationError} If IDs are invalid - * @throws {FailedToCreateError} If creation fails - */ - async add_relation( user_id, address_id ) { + user_addresses: { + async add_relation(user_id, address_id) { try { - return await get_user_addresses_model().add_relation( user_id, address_id ); + return await get_user_addresses_model().add_relation(user_id, address_id); } catch (error) { - logger.error( `Failed to add user-address relation: ${ error.message }` ); + logger.error(`Failed to add user-address relation: ${error.message}`); throw error; } }, - /** - * Remove a user-address relation - * @param {number|string} address_id - Address ID - * @param {number|string|undefined} [user_id] - User ID - * @returns {Promise} Deleted relation instance or null - * @throws {ValidationError} If IDs are invalid - */ - async remove_relation( address_id, user_id ) { + async remove_relation(address_id, user_id) { try { - return await get_user_addresses_model().remove_relation( address_id, user_id ); + return await get_user_addresses_model().remove_relation(address_id, user_id); } catch (error) { - logger.error( `Failed to remove user-address relation: ${ error.message }` ); + logger.error(`Failed to remove user-address relation: ${error.message}`); throw error; } }, - /** - * Find user-address relation by IDs - * @param {number|string} user_id - User ID - * @param {number|string} address_id - Address ID - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Relation instance or null - */ - async find_by_ids( user_id, address_id, excludes ) { + async find_by_ids(user_id, address_id, excludes) { try { - return await get_user_addresses_model().find_by_ids( user_id, address_id, excludes ); + return await get_user_addresses_model().find_by_ids(user_id, address_id, excludes); } catch (error) { - logger.error( `Failed to find user-address by IDs: ${ error.message }` ); + logger.error(`Failed to find user-address by IDs: ${error.message}`); throw error; } }, - /** - * Find user-address relations by user ID - * @param {number|string} user_id - User ID - * @param {string[]} [excludes] - Fields to exclude - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit] - Maximum number of records - * @param {number} [offset] - Number of records to skip - * @returns {Promise} Array of relations - */ - async find_by_user_id( user_id, excludes, order_by, limit, offset ) { + async find_by_user_id(user_id, excludes, order_by, limit, offset) { try { - return await get_user_addresses_model().find_by_user_id( user_id, excludes, order_by, limit, offset ); + return await get_user_addresses_model().find_by_user_id(user_id, excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to find user-addresses by user ID: ${ error.message }` ); + logger.error(`Failed to find user-addresses by user ID: ${error.message}`); throw error; } }, - /** - * Create a new UserAddress instance - * @returns {UserAddress} UserAddress instance - */ - instance:() => new (get_user_addresses_model())() + instance: () => new (get_user_addresses_model())() }, - authentication:{ - /** - * Create a new authentication record - * @param {Object} auth_data - Authentication data - * @returns {Promise} Created authentication instance - * @throws {ValidationError} If required fields are missing - * @throws {FailedToCreateError} If creation fails - */ - async create( auth_data ) { + authentication: { + async create(auth_data) { try { - return await get_authentication_model().create( auth_data ); + return await get_authentication_model().create(auth_data); } catch (error) { - logger.error( `Failed to create authentication record: ${ error.message }` ); + logger.error(`Failed to create authentication record: ${error.message}`); throw error; } }, - /** - * Find authentication record by user ID - * @param {number|string} user_id - User ID - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Authentication instance or null - */ - async find_by_user_id( user_id, excludes ) { + async find_by_user_id(user_id, excludes) { try { - return await get_authentication_model().find_by_user_id( user_id, excludes ); + return await get_authentication_model().find_by_user_id(user_id, excludes); } catch (error) { - logger.error( `Failed to find authentication by user ID: ${ error.message }` ); + logger.error(`Failed to find authentication by user ID: ${error.message}`); throw error; } }, - /** - * Find authentication record by reset token - * @param {string} token - Password reset token - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Authentication instance or null - */ - async find_by_reset_token( token, excludes ) { + async find_by_reset_token(token, excludes) { try { - return await get_authentication_model().find_by_reset_token( token, excludes ); + return await get_authentication_model().find_by_reset_token(token, excludes); } catch (error) { - logger.error( `Failed to find authentication by reset token: ${ error.message }` ); + logger.error(`Failed to find authentication by reset token: ${error.message}`); throw error; } }, - /** - * Find one authentication record - * @param {Object} where - Conditions - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Authentication instance or null - */ - async find_one( where, excludes ) { + async find_one(where, excludes) { try { - return await new (get_authentication_model())().find_one( where, excludes ); + return await new (get_authentication_model())().find_one(where, excludes); } catch (error) { - logger.error( `Failed to find one authentication record: ${ error.message }` ); + logger.error(`Failed to find one authentication record: ${error.message}`); throw error; } }, - /** - * Find many authentication records - * @param {Object} [where] - Conditions - * @param {string[]} [excludes] - Fields to exclude - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit] - Maximum number of records - * @param {number} [offset] - Number of records to skip - * @returns {Promise} Array of authentication records - */ - async find_many( where, excludes, order_by, limit, offset ) { + async find_many(where, excludes, order_by, limit, offset) { try { - return await new (get_authentication_model())().find_many( where, excludes, order_by, limit, offset ); + return await new (get_authentication_model())().find_many(where, excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to find many authentication records: ${ error.message }` ); + logger.error(`Failed to find many authentication records: ${error.message}`); throw error; } }, - /** - * Lock authentication account - * @param {number|string} id - Authentication ID - * @returns {Promise} Updated authentication instance - * @throws {NotFoundError} If authentication record not found - */ - async lock_account( id ) { + async lock_account(id) { try { - const auth = await new (get_authentication_model())().find_one( { id }, [] ); - if (!auth) throw new NotFoundError( 'Authentication record not found' ); + const auth = await new (get_authentication_model())().find_one({ id }, []); + if (!auth) throw new NotFoundError('Authentication record not found'); return await auth.lock_account(); } catch (error) { - logger.error( `Failed to lock account: ${ error.message }` ); + logger.error(`Failed to lock account: ${error.message}`); throw error; } }, - /** - * Unlock authentication account - * @param {number|string} id - Authentication ID - * @returns {Promise} Updated authentication instance - * @throws {NotFoundError} If authentication record not found - */ - async unlock_account( id ) { + async unlock_account(id) { try { - const auth = await new (get_authentication_model())().find_one( { id }, [] ); - if (!auth) throw new NotFoundError( 'Authentication record not found' ); + const auth = await new (get_authentication_model())().find_one({ id }, []); + if (!auth) throw new NotFoundError('Authentication record not found'); return await auth.unlock_account(); } catch (error) { - logger.error( `Failed to unlock account: ${ error.message }` ); + logger.error(`Failed to unlock account: ${error.message}`); throw error; } }, - /** - * Soft delete authentication record - * @param {number|string} id - Authentication ID - * @param {number|string} deleted_by_id - ID of user performing deletion - * @returns {Promise} Updated authentication instance - * @throws {NotFoundError} If authentication record not found - */ - async soft_delete( id, deleted_by_id ) { + async soft_delete(id, deleted_by_id) { try { - const auth = await new (get_authentication_model())().find_one( { id }, [] ); - if (!auth) throw new NotFoundError( 'Authentication record not found' ); - return await auth.soft_delete( deleted_by_id ); + const auth = await new (get_authentication_model())().find_one({ id }, []); + if (!auth) throw new NotFoundError('Authentication record not found'); + return await auth.soft_delete(deleted_by_id); } catch (error) { - logger.error( `Failed to soft delete authentication: ${ error.message }` ); + logger.error(`Failed to soft delete authentication: ${error.message}`); throw error; } }, - /** - * Create a new Authentication instance - * @returns {Authentication} Authentication instance - */ - instance:() => new (get_authentication_model())() + instance: () => new (get_authentication_model())() }, - role:{ - /** - * Create a new role - * @param {Object} role_data - Role data - * @returns {Promise} Created role instance - * @throws {ValidationError} If required fields are missing - * @throws {FailedToCreateError} If creation fails - */ - async create( role_data ) { + role: { + async create(role_data) { try { - return await get_role_model().create( role_data ); + return await get_role_model().create(role_data); } catch (error) { - logger.error( `Failed to create role: ${ error.message }` ); + logger.error(`Failed to create role: ${error.message}`); throw error; } }, - /** - * Find role by name - * @param {string} name - Role name to search for - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Role instance or null - */ - async find_by_name( name, excludes ) { + async find_by_name(name, excludes) { try { - return await get_role_model().find_by_name( name, excludes ); + return await get_role_model().find_by_name(name, excludes); } catch (error) { - logger.error( `Failed to find role by name: ${ error.message }` ); + logger.error(`Failed to find role by name: ${error.message}`); throw error; } }, - /** - * Find one role - * @param {Object} where - Conditions - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Role instance or null - */ - async find_one( where, excludes ) { + async find_one(where, excludes) { try { - return await new (get_role_model())().find_one( where, excludes ); + return await new (get_role_model())().find_one(where, excludes); } catch (error) { - logger.error( `Failed to find one role: ${ error.message }` ); + logger.error(`Failed to find one role: ${error.message}`); throw error; } }, - /** - * Find many roles - * @param {Object} [where] - Conditions - * @param {string[]} [excludes] - Fields to exclude - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit] - Maximum number of records - * @param {number} [offset] - Number of records to skip - * @returns {Promise} Array of roles - */ - async find_many( where, excludes, order_by, limit, offset ) { + async find_many(where, excludes, order_by, limit, offset) { try { - return await new (get_role_model())().find_many( where, excludes, order_by, limit, offset ); + return await new (get_role_model())().find_many(where, excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to find many roles: ${ error.message }` ); + logger.error(`Failed to find many roles: ${error.message}`); throw error; } }, - /** - * Create a new Role instance - * @returns {Role} Role instance - */ - instance:() => new (get_role_model())() + instance: () => new (get_role_model())() }, - user_roles:{ - /** - * Add a user-role relation - * @param {number|string} user_id - User ID - * @param {number|string} role_id - Role ID - * @returns {Promise} Created relation instance - * @throws {ValidationError} If IDs are invalid - * @throws {FailedToCreateError} If creation fails - */ - async add_relation( user_id, role_id ) { + user_roles: { + async add_relation(user_id, role_id) { + try { + return await get_user_roles_model().add_relation(user_id, role_id); + } catch (error) { + logger.error(`Failed to add user-role relation: ${error.message}`); + throw error; + } + }, + async remove_relation(role_id, user_id) { + try { + return await get_user_roles_model().remove_relation(role_id, user_id); + } catch (error) { + logger.error(`Failed to remove user-role relation: ${error.message}`); + throw error; + } + }, + async find_by_ids(user_id, role_id, excludes) { + try { + return await get_user_roles_model().find_by_ids(user_id, role_id, excludes); + } catch (error) { + logger.error(`Failed to find user-role by IDs: ${error.message}`); + throw error; + } + }, + async find_by_user_id(user_id, excludes, order_by, limit, offset) { + try { + return await get_user_roles_model().find_by_user_id(user_id, excludes, order_by, limit, offset); + } catch (error) { + logger.error(`Failed to find user-roles by user ID: ${error.message}`); + throw error; + } + }, + instance: () => new (get_user_roles_model())() + }, + media: { + async create(media_data) { + try { + return await get_media_model().create(media_data); + } catch (error) { + logger.error(`Failed to create media: ${error.message}`); + throw error; + } + }, + async find_by_file_path(file_path, excludes) { + try { + return await get_media_model().find_by_file_path(file_path, excludes); + } catch (error) { + logger.error(`Failed to find media by file path: ${error.message}`); + throw error; + } + }, + async find_one(where, excludes) { + try { + return await new (get_media_model())().find_one(where, excludes); + } catch (error) { + logger.error(`Failed to find one media: ${error.message}`); + throw error; + } + }, + async find_many(where, excludes, order_by, limit, offset) { + try { + return await new (get_media_model())().find_many(where, excludes, order_by, limit, offset); + } catch (error) { + logger.error(`Failed to find many media: ${error.message}`); + throw error; + } + }, + instance: () => new (get_media_model())() + }, + post: { + async create(post_data) { + try { + return await get_post_model().create(post_data); + } catch (error) { + logger.error(`Failed to create post: ${error.message}`); + throw error; + } + }, + async find_by_title(title, excludes) { + try { + return await get_post_model().find_by_title(title, excludes); + } catch (error) { + logger.error(`Failed to find post by title: ${error.message}`); + throw error; + } + }, + async find_one(where, excludes) { + try { + return await new (get_post_model())().find_one(where, excludes); + } catch (error) { + logger.error(`Failed to find one post: ${error.message}`); + throw error; + } + }, + async find_many(where, excludes, order_by, limit, offset) { + try { + return await new (get_post_model())().find_many(where, excludes, order_by, limit, offset); + } catch (error) { + logger.error(`Failed to find many posts: ${error.message}`); + throw error; + } + }, + instance: () => new (get_post_model())() + }, + message_group: { + async create(message_group_data) { + try { + return await get_message_group_model().create(message_group_data); + } catch (error) { + logger.error(`Failed to create message group: ${error.message}`); + throw error; + } + }, + async find_one(where, excludes) { + try { + return await new (get_message_group_model())().find_one(where, excludes); + } catch (error) { + logger.error(`Failed to find one message group: ${error.message}`); + throw error; + } + }, + async find_many(where, excludes, order_by, limit, offset) { + try { + return await new (get_message_group_model())().find_many(where, excludes, order_by, limit, offset); + } catch (error) { + logger.error(`Failed to find many message groups: ${error.message}`); + throw error; + } + }, + instance: () => new (get_message_group_model())() + }, + message_group_members: { + async add_relation(group_id, user_id) { + try { + return await get_message_group_members_model().add_relation(group_id, user_id); + } catch (error) { + logger.error(`Failed to add message group member relation: ${error.message}`); + throw error; + } + }, + async remove_relation(user_id, group_id) { + try { + return await get_message_group_members_model().remove_relation(user_id, group_id); + } catch (error) { + logger.error(`Failed to remove message group member relation: ${error.message}`); + throw error; + } + }, + async find_by_ids(group_id, user_id, excludes) { + try { + return await get_message_group_members_model().find_by_ids(group_id, user_id, excludes); + } catch (error) { + logger.error(`Failed to find message group member by IDs: ${error.message}`); + throw error; + } + }, + async find_by_group_id(group_id, excludes, order_by, limit, offset) { + try { + return await get_message_group_members_model().find_by_group_id(group_id, excludes, order_by, limit, offset); + } catch (error) { + logger.error(`Failed to find message group members by group ID: ${error.message}`); + throw error; + } + }, + instance: () => new (get_message_group_members_model())() + }, + message: { + async create(message_data) { + try { + return await get_message_model().create(message_data); + } catch (error) { + logger.error(`Failed to create message: ${error.message}`); + throw error; + } + }, + async find_by_group_id(group_id, excludes, order_by, limit, offset) { + try { + return await get_message_model().find_by_group_id(group_id, excludes, order_by, limit, offset); + } catch (error) { + logger.error(`Failed to find messages by group ID: ${error.message}`); + throw error; + } + }, + async find_by_recipient_id(recipient_id, excludes, order_by, limit, offset) { try { - return await get_user_roles_model().add_relation( user_id, role_id ); + return await get_message_model().find_by_recipient_id(recipient_id, excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to add user-role relation: ${ error.message }` ); + logger.error(`Failed to find messages by recipient ID: ${error.message}`); throw error; } }, - /** - * Remove a user-role relation - * @param {number|string} role_id - Role ID - * @param {number|string|undefined} [user_id] - User ID - * @returns {Promise} Deleted relation instance or null - * @throws {ValidationError} If IDs are invalid - */ - async remove_relation( role_id, user_id ) { + async find_one(where, excludes) { try { - return await get_user_roles_model().remove_relation( role_id, user_id ); + return await new (get_message_model())().find_one(where, excludes); } catch (error) { - logger.error( `Failed to remove user-role relation: ${ error.message }` ); + logger.error(`Failed to find one message: ${error.message}`); throw error; } }, - /** - * Find user-role relation by IDs - * @param {number|string} user_id - User ID - * @param {number|string} role_id - Role ID - * @param {string[]} [excludes] - Fields to exclude - * @returns {Promise} Relation instance or null - */ - async find_by_ids( user_id, role_id, excludes ) { + async find_many(where, excludes, order_by, limit, offset) { try { - return await get_user_roles_model().find_by_ids( user_id, role_id, excludes ); + return await new (get_message_model())().find_many(where, excludes, order_by, limit, offset); } catch (error) { - logger.error( `Failed to find user-role by IDs: ${ error.message }` ); + logger.error(`Failed to find many messages: ${error.message}`); throw error; } }, - /** - * Find user-role relations by user ID - * @param {number|string} user_id - User ID - * @param {string[]} [excludes] - Fields to exclude - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit] - Maximum number of records - * @param {number} [offset] - Number of records to skip - * @returns {Promise} Array of relations - */ - async find_by_user_id( user_id, excludes, order_by, limit, offset ) { + async mark_as_read(id) { try { - return await get_user_roles_model().find_by_user_id( user_id, excludes, order_by, limit, offset ); + const message = await new (get_message_model())().find_one({ id }, []); + if (!message) throw new NotFoundError('Message not found'); + return await message.mark_as_read(); } catch (error) { - logger.error( `Failed to find user-roles by user ID: ${ error.message }` ); + logger.error(`Failed to mark message as read: ${error.message}`); throw error; } }, - /** - * Create a new UserRole instance - * @returns {UserRole} UserRole instance - */ - instance:() => new (get_user_roles_model())() + instance: () => new (get_message_model())() } }; diff --git a/src/models/media.model.js b/src/models/media.model.js new file mode 100644 index 0000000..586b110 --- /dev/null +++ b/src/models/media.model.js @@ -0,0 +1,107 @@ +/** + * @file Media model for phase.media table + */ + +const { Model, NotFoundError, ValidationError, FailedToCreateError } = require('./model'); + +/** + * @typedef {Object} Media + * @property {number} id - Media ID (primary key) + * @property {number} user_id - User ID (foreign key) + * @property {string} file_path - File path + * @property {string} file_type - File type (e.g., 'image', 'video') + * @property {string} visibility - Visibility (e.g., 'private', 'family', 'public') + * @property {number|null} created_by_id - ID of user who created this media + * @property {Date} created_at - Creation timestamp + * @property {boolean} is_deleted - Soft delete flag + * @property {number|null} deleted_by_id - ID of user who deleted this media + * @property {Date|null} deleted_at - Deletion timestamp + */ + +/** + * Media model class + * @extends Model + */ +class Media extends Model { + /** + * Create a Media instance + * @param {Partial} [props] - Media properties + */ + constructor(props) { + super(props); + this.table = 'phase.media'; + this.prepend = 'm'; + this.default_columns = [ + 'id', 'user_id', 'file_path', 'file_type', 'visibility', + 'created_by_id', 'created_at', 'is_deleted', 'deleted_by_id', 'deleted_at' + ]; + this.update_exclude_columns = ['id', 'created_at', 'is_deleted', 'deleted_at', 'deleted_by_id']; + this.base_query = ` + SELECT m.id, m.user_id, m.file_path, m.file_type, m.visibility, + m.created_by_id, m.created_at, m.is_deleted, m.deleted_by_id, m.deleted_at + FROM phase.media m + WHERE m.is_deleted = false + `; + this.base_list_query = ` + SELECT m.id, m.user_id, m.file_path, m.file_type, m.visibility, + m.created_by_id, m.created_at + FROM phase.media m + WHERE m.is_deleted = false + `; + this.default_order_by = 'ORDER BY m.created_at DESC'; + this.instance = _props => new Media(_props); + } + + /** + * Create a new media + * @param {Omit} media_data - Media data + * @returns {Promise} Created media instance + * @throws {ValidationError} If required fields are missing + * @throws {FailedToCreateError} If creation fails + */ + static async create(media_data) { + const { user_id, file_path, file_type, visibility = 'private', created_by_id = null } = media_data; + if (!user_id || !file_path || !file_type) { + throw new ValidationError('Missing required fields: user_id, file_path, file_type'); + } + const query_str = ` + INSERT INTO phase.media (user_id, file_path, file_type, visibility, created_by_id) + VALUES ($1, $2, $3, $4, $5) RETURNING *; + `; + const values = [user_id, file_path, file_type, visibility, created_by_id]; + const result = await phsdb.query(query_str, values, { plain: true }); + if (!result) throw new FailedToCreateError('Failed to create media'); + return new Media(result); + } + + /** + * Find media by file path + * @param {string} file_path - File path to search for + * @param {string[]} [excludes] - Fields to exclude from result + * @returns {Promise} Media instance or null + */ + static async find_by_file_path(file_path, excludes = []) { + return await new Media().find_one({ file_path }, excludes); + } + + /** + * Soft delete media + * @param {number|string} deleted_by_id - ID of user performing deletion + * @returns {Promise} Updated media instance + * @throws {ValidationError} If deleted_by_id is invalid + * @throws {NotFoundError} If record not found + */ + async soft_delete(deleted_by_id) { + const deleted_by_id_int = parseInt(deleted_by_id, 10); + if (isNaN(deleted_by_id_int)) { + throw new ValidationError('deleted_by_id must be a valid integer'); + } + return await this.update({ + is_deleted: true, + deleted_at: new Date().toISOString(), + deleted_by_id: deleted_by_id_int + }); + } +} + +module.exports = Media; \ No newline at end of file diff --git a/src/models/message.model.js b/src/models/message.model.js new file mode 100644 index 0000000..32ff965 --- /dev/null +++ b/src/models/message.model.js @@ -0,0 +1,108 @@ +/** + * @file Message model for phase.messages table + */ + +const { Model, NotFoundError, ValidationError, FailedToCreateError } = require('./model'); + +/** + * @typedef {Object} Message + * @property {number} id - Message ID (primary key) + * @property {number} sender_id - Sender ID (foreign key to users) + * @property {number|null} group_id - Group ID (foreign key to message_groups) + * @property {number|null} recipient_id - Recipient ID (foreign key to users) + * @property {string} content - Message content + * @property {boolean} read - Whether the message has been read + * @property {Date|null} read_at - Timestamp when the message was read + * @property {Date} created_at - Creation timestamp + */ + +/** + * Message model class + * @extends Model + */ +class Message extends Model { + /** + * Create a Message instance + * @param {Partial} [props] - Message properties + */ + constructor(props) { + super(props); + this.table = 'phase.messages'; + this.prepend = 'm'; + this.default_columns = [ + 'id', 'sender_id', 'group_id', 'recipient_id', 'content', + 'read', 'read_at', 'created_at' + ]; + this.update_exclude_columns = ['id', 'created_at']; + this.base_query = ` + SELECT m.id, m.sender_id, m.group_id, m.recipient_id, m.content, + m.read, m.read_at, m.created_at + FROM phase.messages m + `; + this.base_list_query = this.base_query; + this.default_order_by = 'ORDER BY m.created_at DESC'; + this.instance = _props => new Message(_props); + } + + /** + * Create a new message + * @param {Omit} message_data - Message data + * @returns {Promise} Created message instance + * @throws {ValidationError} If required fields are missing + * @throws {FailedToCreateError} If creation fails + */ + static async create(message_data) { + const { sender_id, group_id, recipient_id, content } = message_data; + if (!sender_id || !content || (group_id == null && recipient_id == null)) { + throw new ValidationError('Missing required fields: sender_id, content, and either group_id or recipient_id'); + } + const query_str = ` + INSERT INTO phase.messages (sender_id, group_id, recipient_id, content, read, read_at) + VALUES ($1, $2, $3, $4, FALSE, NULL) RETURNING *; + `; + const values = [sender_id, group_id, recipient_id, content]; + const result = await phsdb.query(query_str, values, { plain: true }); + if (!result) throw new FailedToCreateError('Failed to create message'); + return new Message(result); + } + + /** + * Find messages by group ID + * @param {number|string} group_id - Group ID + * @param {string[]} [excludes] - Fields to exclude from result + * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration + * @param {number} [limit=100] - Maximum number of records + * @param {number} [offset=0] - Number of records to skip + * @returns {Promise} Array of messages + */ + static async find_by_group_id(group_id, excludes = [], order_by = null, limit = 100, offset = 0) { + return await new Message().find_many({ group_id }, excludes, order_by, limit, offset); + } + + /** + * Find messages by recipient ID + * @param {number|string} recipient_id - Recipient ID + * @param {string[]} [excludes] - Fields to exclude from result + * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration + * @param {number} [limit=100] - Maximum number of records + * @param {number} [offset=0] - Number of records to skip + * @returns {Promise} Array of messages + */ + static async find_by_recipient_id(recipient_id, excludes = [], order_by = null, limit = 100, offset = 0) { + return await new Message().find_many({ recipient_id }, excludes, order_by, limit, offset); + } + + /** + * Mark a message as read + * @returns {Promise} Updated message instance + * @throws {NotFoundError} If record not found + */ + async mark_as_read() { + return await this.update({ + read: true, + read_at: new Date().toISOString() + }); + } +} + +module.exports = Message; \ No newline at end of file diff --git a/src/models/message_group.model.js b/src/models/message_group.model.js new file mode 100644 index 0000000..2d715b3 --- /dev/null +++ b/src/models/message_group.model.js @@ -0,0 +1,64 @@ +/** + * @file Message group model for phase.message_groups table + */ + +const { Model, ValidationError, FailedToCreateError } = require('./model'); + +/** + * @typedef {Object} MessageGroup + * @property {number} id - Message group ID (primary key) + * @property {string} name - Message group name + * @property {number} created_by_id - ID of user who created this message group + * @property {Date} created_at - Creation timestamp + */ + +/** + * Message group model class + * @extends Model + */ +class MessageGroup extends Model { + /** + * Create a MessageGroup instance + * @param {Partial} [props] - Message group properties + */ + constructor(props) { + super(props); + this.table = 'phase.message_groups'; + this.prepend = 'mg'; + this.default_columns = [ + 'id', 'name', 'created_by_id', 'created_at' + ]; + this.update_exclude_columns = ['id', 'created_at']; + this.base_query = ` + SELECT mg.id, mg.name, mg.created_by_id, mg.created_at + FROM phase.message_groups mg + `; + this.base_list_query = this.base_query; + this.default_order_by = 'ORDER BY mg.created_at DESC'; + this.instance = _props => new MessageGroup(_props); + } + + /** + * Create a new message group + * @param {Omit} message_group_data - Message group data + * @returns {Promise} Created message group instance + * @throws {ValidationError} If required fields are missing + * @throws {FailedToCreateError} If creation fails + */ + static async create(message_group_data) { + const { name, created_by_id } = message_group_data; + if (!name || !created_by_id) { + throw new ValidationError('Missing required fields: name, created_by_id'); + } + const query_str = ` + INSERT INTO phase.message_groups (name, created_by_id) + VALUES ($1, $2) RETURNING *; + `; + const values = [name, created_by_id]; + const result = await phsdb.query(query_str, values, { plain: true }); + if (!result) throw new FailedToCreateError('Failed to create message group'); + return new MessageGroup(result); + } +} + +module.exports = MessageGroup; \ No newline at end of file diff --git a/src/models/message_group_members.model.js b/src/models/message_group_members.model.js new file mode 100644 index 0000000..9e7a182 --- /dev/null +++ b/src/models/message_group_members.model.js @@ -0,0 +1,118 @@ +/** + * @file Message group members model for phase.message_group_members table + */ + +const { Model, ValidationError, FailedToCreateError } = require('./model'); + +/** + * @typedef {Object} MessageGroupMember + * @property {number} group_id - Message group ID (foreign key) + * @property {number} user_id - User ID (foreign key) + */ + +/** + * Message group member model class + * @extends Model + */ +class MessageGroupMember extends Model { + /** + * Create a MessageGroupMember instance + * @param {Partial} [props] - Message group member properties + */ + constructor(props) { + super(props); + this.table = 'phase.message_group_members'; + this.prepend = 'mgm'; + this.default_columns = ['group_id', 'user_id']; + this.update_exclude_columns = ['group_id', 'user_id']; + this.base_query = ` + SELECT mgm.group_id, mgm.user_id + FROM phase.message_group_members mgm + `; + this.base_list_query = this.base_query; + this.default_order_by = 'ORDER BY mgm.group_id ASC'; + this.instance = _props => new MessageGroupMember(_props); + } + + /** + * Add a message group member relation + * @param {number|string} group_id - Message group ID + * @param {number|string} user_id - User ID + * @returns {Promise} Created relation instance + * @throws {ValidationError} If IDs are invalid + * @throws {FailedToCreateError} If creation fails + */ + static async add_relation(group_id, user_id) { + const group_id_int = parseInt(group_id, 10); + const user_id_int = parseInt(user_id, 10); + if (isNaN(group_id_int) || isNaN(user_id_int)) { + throw new ValidationError('group_id and user_id must be valid integers'); + } + const query_str = ` + INSERT INTO phase.message_group_members (group_id, user_id) + VALUES ($1, $2) RETURNING *; + `; + const values = [group_id_int, user_id_int]; + const result = await phsdb.query(query_str, values, { plain: true }); + if (!result) throw new FailedToCreateError('Failed to add message group member relation'); + return new MessageGroupMember(result); + } + + /** + * Remove a message group member relation + * @param {number|string} user_id - User ID + * @param {number|string|undefined} [group_id=undefined] - Message group ID + * @returns {Promise} Deleted relation instance or null + * @throws {ValidationError} If IDs are invalid + */ + static async remove_relation(user_id, group_id = undefined) { + const group_id_int = parseInt(group_id, 10); + const user_id_int = parseInt(user_id, 10); + if (isNaN(user_id_int)) { + throw new ValidationError('user_id must be a valid integer'); + } + let query_str = ` + DELETE FROM phase.message_group_members + WHERE user_id = $1 ${group_id_int ? 'AND group_id = $2' : ''} RETURNING *; + `; + const values = [user_id_int]; + if (!isNaN(group_id_int)) values.push(group_id_int); + const result = await phsdb.query(query_str, values, { plain: !isNaN(group_id_int) }); + return result ? new MessageGroupMember(result) : null; + } + + /** + * Find message group member relation by IDs + * @param {number|string} group_id - Message group ID + * @param {number|string} user_id - User ID + * @param {string[]} [excludes] - Fields to exclude from result + * @returns {Promise} Relation instance or null + */ + static async find_by_ids(group_id, user_id, excludes = []) { + return await new MessageGroupMember().find_one( + { group_id, user_id }, + excludes + ); + } + + /** + * Find message group member relations by group ID + * @param {number|string} group_id - Message group ID + * @param {string[]} [excludes] - Fields to exclude from result + * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration + * @param {number} [limit=100] - Maximum number of records + * @param {number} [offset=0] - Number of records to skip + * @returns {Promise} Array of relations + */ + static async find_by_group_id(group_id, excludes = [], order_by = null, limit = 100, offset = 0) { + return await new MessageGroupMember().find_many( + { group_id }, + excludes, + order_by, + limit, + offset + ); + } +} + +module.exports = MessageGroupMember; \ No newline at end of file diff --git a/src/models/post.model.js b/src/models/post.model.js new file mode 100644 index 0000000..cba10d8 --- /dev/null +++ b/src/models/post.model.js @@ -0,0 +1,108 @@ +/** + * @file Post model for phase.posts table + */ + +const { Model, NotFoundError, ValidationError, FailedToCreateError } = require('./model'); + +/** + * @typedef {Object} Post + * @property {number} id - Post ID (primary key) + * @property {number} user_id - User ID (foreign key) + * @property {string} title - Post title + * @property {string} content - Post content + * @property {string} post_type - Post type (e.g., 'blog', 'vlog') + * @property {string} visibility - Visibility (e.g., 'private', 'family', 'public') + * @property {number|null} created_by_id - ID of user who created this post + * @property {Date} created_at - Creation timestamp + * @property {boolean} is_deleted - Soft delete flag + * @property {number|null} deleted_by_id - ID of user who deleted this post + * @property {Date|null} deleted_at - Deletion timestamp + */ + +/** + * Post model class + * @extends Model + */ +class Post extends Model { + /** + * Create a Post instance + * @param {Partial} [props] - Post properties + */ + constructor(props) { + super(props); + this.table = 'phase.posts'; + this.prepend = 'p'; + this.default_columns = [ + 'id', 'user_id', 'title', 'content', 'post_type', 'visibility', + 'created_by_id', 'created_at', 'is_deleted', 'deleted_by_id', 'deleted_at' + ]; + this.update_exclude_columns = ['id', 'created_at', 'is_deleted', 'deleted_at', 'deleted_by_id']; + this.base_query = ` + SELECT p.id, p.user_id, p.title, p.content, p.post_type, p.visibility, + p.created_by_id, p.created_at, p.is_deleted, p.deleted_by_id, p.deleted_at + FROM phase.posts p + WHERE p.is_deleted = false + `; + this.base_list_query = ` + SELECT p.id, p.user_id, p.title, p.content, p.post_type, p.visibility, + p.created_by_id, p.created_at + FROM phase.posts p + WHERE p.is_deleted = false + `; + this.default_order_by = 'ORDER BY p.created_at DESC'; + this.instance = _props => new Post(_props); + } + + /** + * Create a new post + * @param {Omit} post_data - Post data + * @returns {Promise} Created post instance + * @throws {ValidationError} If required fields are missing + * @throws {FailedToCreateError} If creation fails + */ + static async create(post_data) { + const { user_id, title, content, post_type, visibility = 'private', created_by_id = null } = post_data; + if (!user_id || !title || !content || !post_type) { + throw new ValidationError('Missing required fields: user_id, title, content, post_type'); + } + const query_str = ` + INSERT INTO phase.posts (user_id, title, content, post_type, visibility, created_by_id) + VALUES ($1, $2, $3, $4, $5, $6) RETURNING *; + `; + const values = [user_id, title, content, post_type, visibility, created_by_id]; + const result = await phsdb.query(query_str, values, { plain: true }); + if (!result) throw new FailedToCreateError('Failed to create post'); + return new Post(result); + } + + /** + * Find post by title + * @param {string} title - Title to search for + * @param {string[]} [excludes] - Fields to exclude from result + * @returns {Promise} Post instance or null + */ + static async find_by_title(title, excludes = []) { + return await new Post().find_one({ title }, excludes); + } + + /** + * Soft delete post + * @param {number|string} deleted_by_id - ID of user performing deletion + * @returns {Promise} Updated post instance + * @throws {ValidationError} If deleted_by_id is invalid + * @throws {NotFoundError} If record not found + */ + async soft_delete(deleted_by_id) { + const deleted_by_id_int = parseInt(deleted_by_id, 10); + if (isNaN(deleted_by_id_int)) { + throw new ValidationError('deleted_by_id must be a valid integer'); + } + return await this.update({ + is_deleted: true, + deleted_at: new Date().toISOString(), + deleted_by_id: deleted_by_id_int + }); + } +} + +module.exports = Post; \ No newline at end of file diff --git a/src/models/user.model.js b/src/models/user.model.js index 94edaea..877db28 100644 --- a/src/models/user.model.js +++ b/src/models/user.model.js @@ -2,7 +2,12 @@ * @file User model for phase.users table */ -const { Model, ValidationError } = require( './model' ); +const db = require('../models'); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); +const config = require('../config/default.json'); +const { Model, ValidationError } = require('./model'); +const createHttpError = require('http-errors'); /** * @typedef {Object} User @@ -28,12 +33,8 @@ const { Model, ValidationError } = require( './model' ); * @extends Model */ class User extends Model { - /** - * Create a User instance - * @param {Partial} [props] - User properties - */ - constructor( props ) { - super( props ); + constructor(props) { + super(props); this.table = 'phase.users'; this.prepend = 'u'; this.default_columns = [ @@ -43,57 +44,29 @@ class User extends Model { ]; this.update_exclude_columns = ['id', 'created_at', 'is_deleted', 'deleted_at', 'deleted_by_id']; this.base_query = ` - SELECT u.id, - u.email, - u.first_name, - u.middle_name, - u.last_name, - u.initials, - u.nickname, - u.created_by_id, - u.created_at, - u.is_deleted, - u.deleted_by_id, - u.deleted_at, - u.is_active, - u.deactivated_by_id, - u.deactivated_at + SELECT u.id, u.email, u.first_name, u.middle_name, u.last_name, u.initials, u.nickname, + u.created_by_id, u.created_at, u.is_deleted, u.deleted_by_id, u.deleted_at, + u.is_active, u.deactivated_by_id, u.deactivated_at FROM phase.users u WHERE u.is_deleted = false `; this.base_list_query = ` - SELECT u.id, - u.email, - u.first_name, - u.middle_name, - u.last_name, - u.initials, - u.nickname, - u.created_by_id, - u.created_at, - u.is_active, - u.deactivated_by_id, - u.deactivated_at + SELECT u.id, u.email, u.first_name, u.middle_name, u.last_name, u.initials, u.nickname, + u.created_by_id, u.created_at, u.is_active, u.deactivated_by_id, u.deactivated_at FROM phase.users u WHERE u.is_deleted = false `; this.default_order_by = 'ORDER BY u.email ASC'; - this.instance = _props => new User( _props ); + this.instance = _props => new User(_props); } - /** - * Create a new user - * @param {Omit} user_data - User data - * @returns {Promise} Created user instance - * @throws {ValidationError} If required fields are missing - */ - static async create( user_data ) { + static async create(user_data) { const { email, first_name, middle_name = '', last_name, initials = null, nickname = null, created_by_id = null, is_active = true, deactivated_by_id = null, deactivated_at = null } = user_data; if (!email || !first_name || !last_name) { - throw new ValidationError( 'Missing required fields: email, first_name, last_name' ); + throw new ValidationError('Missing required fields: email, first_name, last_name'); } const query_str = ` INSERT INTO phase.users (email, first_name, middle_name, last_name, initials, nickname, created_by_id, @@ -105,148 +78,150 @@ class User extends Model { email, first_name, middle_name, last_name, initials, nickname, created_by_id, is_active, deactivated_by_id, deactivated_at ]; - const result = await phsdb.query( query_str, values, { plain:true } ); - if (!result) throw new ValidationError( 'Failed to create user' ); - return new User( result ); - }; + const result = await phsdb.query(query_str, values, { plain: true }); + if (!result) throw new ValidationError('Failed to create user'); + return new User(result); + } async get_user_roles() { const query_str = ` - SELECT r.* + SELECT r.name FROM phase.user_roles ur - inner join phase.roles r on r.id = ur.role_id - WHERE ur.user_id = $1 and r.is_deleted = false + INNER JOIN phase.roles r ON r.id = ur.role_id + WHERE ur.user_id = $1 AND r.is_deleted = false `; - return await phsdb.query( query_str, [this.id], { plain:true } ); - }; - - /** - * Find user by email - * @param {string} email - Email to search for - * @param {string[]} [excludes] - Fields to exclude from result - * @returns {Promise} User instance or null - */ - static async find_by_email( email, excludes = [] ) { - return await new User().find_one( { email }, excludes ); - } - - // noinspection JSUnusedGlobalSymbols - /** - * Find user by nickname - * @param {string} nickname - Nickname to search for - * @param {string[]} [excludes] - Fields to exclude from result - * @returns {Promise} User instance or null - */ - static async find_by_nickname( nickname, excludes = [] ) { - return await new User().find_one( { nickname }, excludes ); - } - - /** - * Find active users - * @param {string[]} [excludes] - Fields to exclude from result - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit=100] - Maximum number of records - * @param {number} [offset=0] - Number of records to skip - * @returns {Promise} Array of active users - */ - static async find_active( excludes = [], order_by = null, limit = 100, offset = 0 ) { - return await new User().find_many( { is_active:true }, excludes, order_by, limit, offset ); - }; - - // noinspection JSUnusedGlobalSymbols - /** - * Find deleted users - * @param {string[]} [excludes] - Fields to exclude from result - * @param {{name: string, direction?: 'asc'|'desc'}||null} [order_by] - Order by configuration - * @param {number} [limit=100] - Maximum number of records - * @param {number} [offset=0] - Number of records to skip - * @returns {Promise} Array of deleted users - */ - static async find_deleted( excludes = [], order_by = null, limit = 100, offset = 0 ) { - const query_str = this.prototype.base_list_query.replace( 'WHERE u.is_deleted = false', 'WHERE u.is_deleted = true' ); + const result = await phsdb.query(query_str, [this.id]); + return result.map(r => r.name); + } + + static async find_by_email(email, excludes = []) { + return await new User().find_one({ email }, excludes); + } + + static async find_by_nickname(nickname, excludes = []) { + return await new User().find_one({ nickname }, excludes); + } + + static async find_active(excludes = [], order_by = null, limit = 100, offset = 0) { + return await new User().find_many({ is_active: true }, excludes, order_by, limit, offset); + } + + static async find_deleted(excludes = [], order_by = null, limit = 100, offset = 0) { + const query_str = this.prototype.base_list_query.replace('WHERE u.is_deleted = false', 'WHERE u.is_deleted = true'); const instance = this.prototype.instance; - const { keys, values } = this.prototype.build_where( {}, this.prototype.default_columns ); - values.push( limit, offset ); + const { keys, values } = this.prototype.build_where({}, this.prototype.default_columns); + values.push(limit, offset); const results = await phsdb.query( ` - ${ query_str } - ${ this.prototype.where_clause( keys, this.prototype.prepend ) } - ${ order_by ? `ORDER BY ${ order_by.name } ${ order_by.direction ?? 'asc' }` : this.prototype.default_order_by ?? '' } - LIMIT $${ values.length - 1 } OFFSET $${ values.length } + ${query_str} + ${this.prototype.where_clause(keys, this.prototype.prepend)} + ${order_by ? `ORDER BY ${order_by.name} ${order_by.direction ?? 'asc'}` : this.prototype.default_order_by ?? ''} + LIMIT $${values.length - 1} OFFSET $${values.length} `, values ); - return results.map( result => { - const found = instance( result ); - excludes?.forEach( e => delete found[e] ); + return results.map(result => { + const found = instance(result); + excludes?.forEach(e => delete found[e]); return found; - } ); - } - - /** - * Deactivate user - * @param {number|string} deactivated_by_id - ID of user performing deactivation - * @returns {Promise} Updated user instance - * @throws {ValidationError} If deactivated_by_id is invalid - */ - async deactivate( deactivated_by_id ) { - const deactivated_by_id_int = parseInt( deactivated_by_id, 10 ); - if (isNaN( deactivated_by_id_int )) { - throw new ValidationError( 'deactivated_by_id must be a valid integer' ); + }); + } + + async deactivate(deactivated_by_id) { + const deactivated_by_id_int = parseInt(deactivated_by_id, 10); + if (isNaN(deactivated_by_id_int)) { + throw new ValidationError('deactivated_by_id must be a valid integer'); } - return await this.update( { - is_active:false, - deactivated_at:new Date().toISOString(), - deactivated_by_id:deactivated_by_id_int - } ); + return await this.update({ + is_active: false, + deactivated_at: new Date().toISOString(), + deactivated_by_id: deactivated_by_id_int + }); } - /** - * Reactivate user - * @returns {Promise} Updated user instance - */ async reactivate() { - return await this.update( { - is_active:true, - deactivated_at:null, - deactivated_by_id:null - } ); - } - - /** - * Soft delete user - * @param {number|string} deleted_by_id - ID of user performing deletion - * @returns {Promise} Updated user instance - * @throws {ValidationError} If deleted_by_id is invalid - */ - async soft_delete( deleted_by_id ) { - const deleted_by_id_int = parseInt( deleted_by_id, 10 ); - if (isNaN( deleted_by_id_int )) { - throw new ValidationError( 'deleted_by_id must be a valid integer' ); + return await this.update({ + is_active: true, + deactivated_at: null, + deactivated_by_id: null + }); + } + + async comparePassword(password) { + const auth = await db.authentication.find_by_user_id(this.id); + return auth ? bcrypt.compareSync(password, auth.password) : false; + } + + async createToken() { + const auth = await db.authentication.find_by_user_id(this.id); + const roles = await this.get_user_roles(); + const tokenPayload = { id: this.id, email: this.email, roles }; + const token = jwt.sign(tokenPayload, config.keys.secret, { expiresIn: '24h' }); + const { exp } = jwt.decode(token); + const token_expiry = new Date(exp * 1000).toISOString(); + await auth.update({ + password_failures_since_last_success: 0, + password_verification_token: token, + password_verification_token_expiry: token_expiry + }); + return token; + } + + async failLogin() { + const auth = await db.authentication.find_by_user_id(this.id); + await auth.update({ + password_failures_since_last_success: (auth.password_failures_since_last_success || 0) + 1, + password_verification_token: null, + password_verification_token_expiry: null, + last_password_failure: new Date().toISOString() + }); + return true; + } + + async lockAccount() { + const auth = await db.authentication.find_by_user_id(this.id); + await auth.update({ + is_locked: true, + locked_date: new Date().toISOString() + }); + return true; + } + + async hashPassword(password) { + const auth = await db.authentication.find_by_user_id(this.id); + if (!auth) throw createHttpError(400, 'No authentication record found for this user.'); + const salt = await bcrypt.genSalt(10); + const hash = await bcrypt.hash(password, salt); + await auth.update({ + password_failures_since_last_success: 0, + password_changed_date: new Date().toISOString(), + password: hash, + password_salt: salt, + ms_password: false, + password_reset_token: null, + password_reset_expire_date: null + }); + return { salt, hash }; + } + + async soft_delete(deleted_by_id) { + const deleted_by_id_int = parseInt(deleted_by_id, 10); + if (isNaN(deleted_by_id_int)) { + throw new ValidationError('deleted_by_id must be a valid integer'); } - return await this.update( { - is_deleted:true, - deleted_at:new Date().toISOString(), - deleted_by_id:deleted_by_id_int - } ); + return await this.update({ + is_deleted: true, + deleted_at: new Date().toISOString(), + deleted_by_id: deleted_by_id_int + }); } - /** - * Check if user is active - * @returns {boolean} True if user is active - */ is_active() { return this.is_active === true; } - // noinspection JSUnusedGlobalSymbols - /** - * Get user data without sensitive information - * @returns {Omit} User data - */ to_safe_json() { const { password, ...safe_data } = this.toJSON(); - // noinspection JSValidateTypes return safe_data; } } diff --git a/src/routes/auth.routes.js b/src/routes/auth.routes.js index 805421b..4449d5d 100644 --- a/src/routes/auth.routes.js +++ b/src/routes/auth.routes.js @@ -1,9 +1,8 @@ const express = require('express'); const router = express.Router(); -const { validateAuth } = require('../middleware/routeHelpers'); const authController = require('../controllers/auth.controller'); -module.exports = (passport) => { +module.exports = () => { router.post('/login', authController.login); return router; }; \ No newline at end of file diff --git a/src/routes/media.routes.js b/src/routes/media.routes.js new file mode 100644 index 0000000..6d830e9 --- /dev/null +++ b/src/routes/media.routes.js @@ -0,0 +1,23 @@ +/** + * @file Media routes configuration + */ + +const express = require('express'); +const router = express.Router(); +const { validateAuth } = require('../middleware/routeHelpers'); +const media_controller = require('../controllers/media.controller'); + +/** + * Configure media routes + * @param {Object} passport - Passport instance for authentication + * @returns {Object} Express router with media routes + */ +module.exports = (passport) => { + router.post('/create', validateAuth(passport), media_controller.create); + router.get('/file_path/:file_path', validateAuth(passport), media_controller.find_by_file_path); + router.get('/:id', validateAuth(passport), media_controller.find_one); + router.get('/', validateAuth(passport), media_controller.find_many); + router.put('/:id', validateAuth(passport), media_controller.update); + router.put('/:id/soft_delete', validateAuth(passport), media_controller.soft_delete); + return router; +}; \ No newline at end of file diff --git a/src/routes/message.routes.js b/src/routes/message.routes.js new file mode 100644 index 0000000..8d750f0 --- /dev/null +++ b/src/routes/message.routes.js @@ -0,0 +1,23 @@ +/** + * @file Message routes configuration + */ + +const express = require('express'); +const router = express.Router(); +const { validateAuth } = require('../middleware/routeHelpers'); +const message_controller = require('../controllers/message.controller'); + +/** + * Configure message routes + * @param {Object} passport - Passport instance for authentication + * @returns {Object} Express router with message routes + */ +module.exports = (passport) => { + router.post('/create', validateAuth(passport), message_controller.create); + router.get('/group/:group_id', validateAuth(passport), message_controller.find_by_group_id); + router.get('/recipient/:recipient_id', validateAuth(passport), message_controller.find_by_recipient_id); + router.get('/:id', validateAuth(passport), message_controller.find_one); + router.get('/', validateAuth(passport), message_controller.find_many); + router.put('/:id/mark_as_read', validateAuth(passport), message_controller.mark_as_read); + return router; +}; \ No newline at end of file diff --git a/src/routes/message_group.routes.js b/src/routes/message_group.routes.js new file mode 100644 index 0000000..73135ab --- /dev/null +++ b/src/routes/message_group.routes.js @@ -0,0 +1,21 @@ +/** + * @file Message group routes configuration + */ + +const express = require('express'); +const router = express.Router(); +const { validateAuth } = require('../middleware/routeHelpers'); +const message_group_controller = require('../controllers/message_group.controller'); + +/** + * Configure message group routes + * @param {Object} passport - Passport instance for authentication + * @returns {Object} Express router with message group routes + */ +module.exports = (passport) => { + router.post('/create', validateAuth(passport), message_group_controller.create); + router.get('/:id', validateAuth(passport), message_group_controller.find_one); + router.get('/', validateAuth(passport), message_group_controller.find_many); + router.put('/:id', validateAuth(passport), message_group_controller.update); + return router; +}; \ No newline at end of file diff --git a/src/routes/message_group_members.routes.js b/src/routes/message_group_members.routes.js new file mode 100644 index 0000000..5739665 --- /dev/null +++ b/src/routes/message_group_members.routes.js @@ -0,0 +1,21 @@ +/** + * @file Message group members routes configuration + */ + +const express = require('express'); +const router = express.Router(); +const { validateAuth } = require('../middleware/routeHelpers'); +const message_group_members_controller = require('../controllers/message_group_members.controller'); + +/** + * Configure message group members routes + * @param {Object} passport - Passport instance for authentication + * @returns {Object} Express router with message group members routes + */ +module.exports = (passport) => { + router.post('/add', validateAuth(passport), message_group_members_controller.add_relation); + router.delete('/remove', validateAuth(passport), message_group_members_controller.remove_relation); + router.get('/ids/:group_id/:user_id', validateAuth(passport), message_group_members_controller.find_by_ids); + router.get('/group/:group_id', validateAuth(passport), message_group_members_controller.find_by_group_id); + return router; +}; \ No newline at end of file diff --git a/src/routes/post.routes.js b/src/routes/post.routes.js new file mode 100644 index 0000000..0813c48 --- /dev/null +++ b/src/routes/post.routes.js @@ -0,0 +1,23 @@ +/** + * @file Post routes configuration + */ + +const express = require('express'); +const router = express.Router(); +const { validateAuth } = require('../middleware/routeHelpers'); +const post_controller = require('../controllers/post.controller'); + +/** + * Configure post routes + * @param {Object} passport - Passport instance for authentication + * @returns {Object} Express router with post routes + */ +module.exports = (passport) => { + router.post('/create', validateAuth(passport), post_controller.create); + router.get('/title/:title', validateAuth(passport), post_controller.find_by_title); + router.get('/:id', validateAuth(passport), post_controller.find_one); + router.get('/', validateAuth(passport), post_controller.find_many); + router.put('/:id', validateAuth(passport), post_controller.update); + router.put('/:id/soft_delete', validateAuth(passport), post_controller.soft_delete); + return router; +}; \ No newline at end of file -- 2.43.0