diff --git a/README.md b/README.md index e2ad5947e..85df1bf36 100644 --- a/README.md +++ b/README.md @@ -43,5 +43,6 @@ You may need to install the [gcloud CLI](https://cloud.google.com/sdk/gcloud). 1. Temporarily change the default project in the .firebaserc file to the environment you want to import from. 2. Run `npm run import:firestore` -3. Restore the default project in the .firebaserc file. -4. Run `npm run dev:data` +3. Run `npm run import:auth` +4. Restore the default project in the .firebaserc file. +5. Run `npm run dev:data` diff --git a/env/env.sh b/env/env.sh index 819627109..4b222ea65 100755 --- a/env/env.sh +++ b/env/env.sh @@ -75,13 +75,13 @@ if [[ $2 == "-deploy" ]]; then # Deploy to the appropriate environment based on the branch name case $CURRENT_BRANCH in dev) - firebase deploy + firebase deploy --debug ;; staging) - firebase deploy + firebase deploy --debug ;; production) - firebase deploy + firebase deploy --debug ;; *) echo "Not deploying to Firebase. Branch name does not match an environment." diff --git a/functions/package-lock.json b/functions/package-lock.json index 4d6ef3acf..a0a82bec1 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -9,6 +9,7 @@ "version": "2.0.0", "dependencies": { "@faker-js/faker": "^8.0.2", + "@google-cloud/firestore": "^6.6.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^2.1.0", "@google-cloud/opentelemetry-cloud-trace-propagator": "^0.17.0", "@google-cloud/secret-manager": "^5.0.1", @@ -485,7 +486,6 @@ }, "node_modules/@babel/parser": { "version": "7.22.7", - "devOptional": true, "license": "MIT", "bin": { "parser": "bin/babel-parser.js" @@ -1676,13 +1676,205 @@ "license": "Apache-2.0" }, "node_modules/@google-cloud/firestore": { - "version": "6.6.1", - "license": "Apache-2.0", - "optional": true, + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.8.0.tgz", + "integrity": "sha512-JRpk06SmZXLGz0pNx1x7yU3YhkUXheKgH5hbDZ4kMsdhtfV5qPLJLRI4wv69K0cZorIk+zTMOwptue7hizo0eA==", "dependencies": { "fast-deep-equal": "^3.1.1", "functional-red-black-tree": "^1.0.1", "google-gax": "^3.5.7", + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/firestore/node_modules/@grpc/grpc-js": { + "version": "1.8.22", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.22.tgz", + "integrity": "sha512-oAjDdN7fzbUi+4hZjKG96MR6KTEubAeMpQEb+77qy+3r0Ua5xTFuie6JOLr4ZZgl5g+W5/uRTS2M1V8mVAFPuA==", + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@google-cloud/firestore/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@google-cloud/firestore/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/@google-cloud/firestore/node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/firestore/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@google-cloud/firestore/node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/firestore/node_modules/google-gax": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.6.1.tgz", + "integrity": "sha512-g/lcUjGcB6DSw2HxgEmCDOrI/CByOwqRvsuUvNalHUK2iPPPlmAIpbMbl62u0YufGMr8zgE3JL7th6dCb1Ry+w==", + "dependencies": { + "@grpc/grpc-js": "~1.8.0", + "@grpc/proto-loader": "^0.7.0", + "@types/long": "^4.0.0", + "@types/rimraf": "^3.0.2", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^8.0.2", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.2.4", + "protobufjs-cli": "1.1.1", + "retry-request": "^5.0.0" + }, + "bin": { + "compileProtos": "build/tools/compileProtos.js", + "minifyProtoJson": "build/tools/minify.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/firestore/node_modules/google-gax/node_modules/protobufjs": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "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/@google-cloud/firestore/node_modules/google-gax/node_modules/protobufjs-cli": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz", + "integrity": "sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==", + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" + } + }, + "node_modules/@google-cloud/firestore/node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/firestore/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/firestore/node_modules/proto3-json-serializer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", + "integrity": "sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw==", + "dependencies": { "protobufjs": "^7.0.0" }, "engines": { @@ -1854,103 +2046,6 @@ "node": ">=14.0.0" } }, - "node_modules/@google-cloud/secret-manager/node_modules/@grpc/grpc-js": { - "version": "1.9.5", - "license": "Apache-2.0", - "dependencies": { - "@grpc/proto-loader": "^0.7.8", - "@types/node": ">=12.12.47" - }, - "engines": { - "node": "^8.13.0 || >=10.10.0" - } - }, - "node_modules/@google-cloud/secret-manager/node_modules/debug": { - "version": "4.3.4", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@google-cloud/secret-manager/node_modules/google-gax": { - "version": "4.0.4", - "license": "Apache-2.0", - "dependencies": { - "@grpc/grpc-js": "~1.9.0", - "@grpc/proto-loader": "^0.7.0", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "google-auth-library": "^9.0.0", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^2.0.0", - "protobufjs": "7.2.5", - "retry-request": "^6.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@google-cloud/secret-manager/node_modules/long": { - "version": "5.2.3", - "license": "Apache-2.0" - }, - "node_modules/@google-cloud/secret-manager/node_modules/ms": { - "version": "2.1.2", - "license": "MIT" - }, - "node_modules/@google-cloud/secret-manager/node_modules/proto3-json-serializer": { - "version": "2.0.0", - "license": "Apache-2.0", - "dependencies": { - "protobufjs": "^7.0.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@google-cloud/secret-manager/node_modules/protobufjs": { - "version": "7.2.5", - "hasInstallScript": true, - "license": "BSD-3-Clause", - "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/@google-cloud/secret-manager/node_modules/retry-request": { - "version": "6.0.0", - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@google-cloud/storage": { "version": "6.10.1", "license": "Apache-2.0", @@ -2031,24 +2126,25 @@ } }, "node_modules/@grpc/grpc-js": { - "version": "1.8.21", - "license": "Apache-2.0", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.11.3.tgz", + "integrity": "sha512-i9UraDzFHMR+Iz/MhFLljT+fCpgxZ3O6CxwGJ8YuNYHJItIHUzKJpW2LvoFZNnGPwqc9iWy9RAucxV0JoR9aUQ==", "dependencies": { - "@grpc/proto-loader": "^0.7.0", - "@types/node": ">=12.12.47" + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" }, "engines": { - "node": "^8.13.0 || >=10.10.0" + "node": ">=12.10.0" } }, "node_modules/@grpc/proto-loader": { - "version": "0.7.8", - "license": "Apache-2.0", + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", "dependencies": { - "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", - "long": "^4.0.0", - "protobufjs": "^7.2.4", + "long": "^5.0.0", + "protobufjs": "^7.2.5", "yargs": "^17.7.2" }, "bin": { @@ -2703,10 +2799,19 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "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/@jsdoc/salty": { - "version": "0.2.5", - "license": "Apache-2.0", - "optional": true, + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz", + "integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==", "dependencies": { "lodash": "^4.17.21" }, @@ -3592,7 +3697,6 @@ "node_modules/@tootallnate/once": { "version": "2.0.0", "license": "MIT", - "optional": true, "engines": { "node": ">= 10" } @@ -3674,6 +3778,11 @@ "@types/node": "*" } }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==" + }, "node_modules/@types/chai": { "version": "4.3.5", "dev": true, @@ -3757,8 +3866,8 @@ }, "node_modules/@types/glob": { "version": "8.1.0", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", "dependencies": { "@types/minimatch": "^5.1.2", "@types/node": "*" @@ -3827,9 +3936,9 @@ } }, "node_modules/@types/linkify-it": { - "version": "3.0.2", - "license": "MIT", - "optional": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" }, "node_modules/@types/lodash": { "version": "4.14.197", @@ -3841,18 +3950,18 @@ "license": "MIT" }, "node_modules/@types/markdown-it": { - "version": "12.2.3", - "license": "MIT", - "optional": true, + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dependencies": { - "@types/linkify-it": "*", - "@types/mdurl": "*" + "@types/linkify-it": "^5", + "@types/mdurl": "^2" } }, "node_modules/@types/mdurl": { - "version": "1.0.2", - "license": "MIT", - "optional": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" }, "node_modules/@types/mime": { "version": "1.3.2", @@ -3860,8 +3969,8 @@ }, "node_modules/@types/minimatch": { "version": "5.1.2", - "license": "MIT", - "optional": true + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" }, "node_modules/@types/mocha": { "version": "10.0.1", @@ -3931,6 +4040,30 @@ "version": "1.2.4", "license": "MIT" }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/@types/retry": { "version": "0.12.5", "dev": true, @@ -3938,8 +4071,8 @@ }, "node_modules/@types/rimraf": { "version": "3.0.2", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", "dependencies": { "@types/glob": "*", "@types/node": "*" @@ -3981,6 +4114,11 @@ "license": "MIT", "peer": true }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" + }, "node_modules/@types/uuid": { "version": "9.0.1", "dev": true, @@ -4676,7 +4814,6 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "devOptional": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -4834,7 +4971,6 @@ }, "node_modules/argparse": { "version": "2.0.1", - "devOptional": true, "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { @@ -5127,8 +5263,8 @@ }, "node_modules/bluebird": { "version": "3.7.2", - "license": "MIT", - "optional": true + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/blueimp-md5": { "version": "2.19.0", @@ -5175,7 +5311,7 @@ }, "node_modules/brace-expansion": { "version": "1.1.11", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -5600,8 +5736,8 @@ }, "node_modules/catharsis": { "version": "0.9.0", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", "dependencies": { "lodash": "^4.17.15" }, @@ -5959,7 +6095,7 @@ }, "node_modules/concat-map": { "version": "0.0.1", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/concordance": { @@ -6290,7 +6426,6 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "devOptional": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -6465,17 +6600,6 @@ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", @@ -6619,9 +6743,12 @@ "optional": true }, "node_modules/entities": { - "version": "2.1.0", - "license": "BSD-2-Clause", - "optional": true, + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } @@ -7124,7 +7251,6 @@ }, "node_modules/escape-string-regexp": { "version": "2.0.0", - "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -7132,8 +7258,8 @@ }, "node_modules/escodegen": { "version": "1.14.3", - "license": "BSD-2-Clause", - "optional": true, + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", "dependencies": { "esprima": "^4.0.1", "estraverse": "^4.2.0", @@ -7153,8 +7279,8 @@ }, "node_modules/escodegen/node_modules/levn": { "version": "0.3.0", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dependencies": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -7165,8 +7291,8 @@ }, "node_modules/escodegen/node_modules/optionator": { "version": "0.8.3", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dependencies": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -7181,15 +7307,16 @@ }, "node_modules/escodegen/node_modules/prelude-ls": { "version": "1.1.2", - "optional": true, + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "engines": { "node": ">= 0.8.0" } }, "node_modules/escodegen/node_modules/type-check": { "version": "0.3.2", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dependencies": { "prelude-ls": "~1.1.2" }, @@ -7512,7 +7639,6 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.2", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -7578,7 +7704,6 @@ }, "node_modules/espree": { "version": "9.6.1", - "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", @@ -7594,7 +7719,6 @@ }, "node_modules/esprima": { "version": "4.0.1", - "devOptional": true, "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", @@ -7644,7 +7768,6 @@ }, "node_modules/estraverse": { "version": "4.3.0", - "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -7652,7 +7775,6 @@ }, "node_modules/esutils": { "version": "2.0.3", - "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -7824,7 +7946,6 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "devOptional": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -7865,7 +7986,6 @@ }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "devOptional": true, "license": "MIT" }, "node_modules/fast-redact": { @@ -8477,8 +8597,7 @@ }, "node_modules/functional-red-black-tree": { "version": "1.0.1", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/functions-have-names": { "version": "1.2.3", @@ -8510,8 +8629,9 @@ } }, "node_modules/gcp-metadata": { - "version": "6.0.0", - "license": "Apache-2.0", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", "dependencies": { "gaxios": "^6.0.0", "json-bigint": "^1.0.0" @@ -8520,22 +8640,69 @@ "node": ">=14" } }, + "node_modules/gcp-metadata/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gcp-metadata/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/gcp-metadata/node_modules/gaxios": { - "version": "6.0.3", - "license": "Apache-2.0", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", "dependencies": { "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", + "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" }, "engines": { "node": ">=14" } }, + "node_modules/gcp-metadata/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gcp-metadata/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/gcp-metadata/node_modules/node-fetch": { - "version": "2.6.12", - "license": "MIT", + "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" }, @@ -8673,7 +8840,7 @@ }, "node_modules/glob": { "version": "7.2.0", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -8760,37 +8927,84 @@ } }, "node_modules/google-auth-library": { - "version": "9.0.0", - "license": "Apache-2.0", + "version": "9.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.14.1.tgz", + "integrity": "sha512-Rj+PMjoNFGFTmtItH7gHfbHpGVSb3vmnGK3nwNBqxQF9NoBpttSZI/rc0WiM63ma2uGDQtYEkMHkK9U6937NiA==", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.0.0", - "gcp-metadata": "^6.0.0", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" + "jws": "^4.0.0" }, "engines": { "node": ">=14" } }, + "node_modules/google-auth-library/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/google-auth-library/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/google-auth-library/node_modules/gaxios": { - "version": "6.0.3", - "license": "Apache-2.0", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", "dependencies": { "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", + "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" }, "engines": { "node": ">=14" } }, + "node_modules/google-auth-library/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/google-auth-library/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/google-auth-library/node_modules/node-fetch": { - "version": "2.6.12", - "license": "MIT", + "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" }, @@ -8807,148 +9021,72 @@ } }, "node_modules/google-gax": { - "version": "3.6.1", - "license": "Apache-2.0", - "optional": true, + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", "dependencies": { - "@grpc/grpc-js": "~1.8.0", - "@grpc/proto-loader": "^0.7.0", + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", "@types/long": "^4.0.0", - "@types/rimraf": "^3.0.2", "abort-controller": "^3.0.0", "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^8.0.2", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "7.2.4", - "protobufjs-cli": "1.1.1", - "retry-request": "^5.0.0" - }, - "bin": { - "compileProtos": "build/tools/compileProtos.js", - "minifyProtoJson": "build/tools/minify.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-gax/node_modules/brace-expansion": { - "version": "2.0.1", - "license": "MIT", - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/google-gax/node_modules/estraverse": { - "version": "5.3.0", - "license": "BSD-2-Clause", - "optional": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/google-gax/node_modules/gcp-metadata": { - "version": "5.3.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" }, "engines": { - "node": ">=12" + "node": ">=14" } }, - "node_modules/google-gax/node_modules/glob": { - "version": "8.1.0", - "license": "ISC", - "optional": true, + "node_modules/google-gax/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": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/google-gax/node_modules/google-auth-library": { - "version": "8.9.0", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "arrify": "^2.0.0", - "base64-js": "^1.3.0", - "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.3.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" + "node": "4.x || >=6.0.0" }, - "engines": { - "node": ">=12" - } - }, - "node_modules/google-gax/node_modules/gtoken": { - "version": "6.1.2", - "license": "MIT", - "optional": true, - "dependencies": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", - "jws": "^4.0.0" + "peerDependencies": { + "encoding": "^0.1.0" }, - "engines": { - "node": ">=12.0.0" + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/google-gax/node_modules/minimatch": { - "version": "5.1.6", - "license": "ISC", - "optional": true, + "node_modules/google-gax/node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", "dependencies": { - "brace-expansion": "^2.0.1" + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" } }, - "node_modules/google-gax/node_modules/protobufjs-cli": { - "version": "1.1.1", - "license": "BSD-3-Clause", - "optional": true, + "node_modules/google-gax/node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", "dependencies": { - "chalk": "^4.0.0", - "escodegen": "^1.13.0", - "espree": "^9.0.0", - "estraverse": "^5.1.0", - "glob": "^8.0.0", - "jsdoc": "^4.0.0", - "minimist": "^1.2.0", - "semver": "^7.1.2", - "tmp": "^0.2.1", - "uglify-js": "^3.7.7" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "protobufjs": "^7.0.0" + "node": ">=14" } }, "node_modules/google-libphonenumber": { @@ -8961,7 +9099,6 @@ "node_modules/google-p12-pem": { "version": "4.0.1", "license": "MIT", - "optional": true, "dependencies": { "node-forge": "^1.3.1" }, @@ -9253,17 +9390,6 @@ "entities": "^4.4.0" } }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -9290,7 +9416,6 @@ "node_modules/http-proxy-agent": { "version": "5.0.0", "license": "MIT", - "optional": true, "dependencies": { "@tootallnate/once": "2", "agent-base": "6", @@ -9303,7 +9428,6 @@ "node_modules/http-proxy-agent/node_modules/debug": { "version": "4.3.4", "license": "MIT", - "optional": true, "dependencies": { "ms": "2.1.2" }, @@ -9318,8 +9442,7 @@ }, "node_modules/http-proxy-agent/node_modules/ms": { "version": "2.1.2", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/https-proxy-agent": { "version": "5.0.0", @@ -9862,8 +9985,8 @@ }, "node_modules/is-stream-ended": { "version": "0.1.4", - "license": "MIT", - "optional": true + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" }, "node_modules/is-string": { "version": "1.0.7", @@ -10836,8 +10959,8 @@ }, "node_modules/js2xmlparser": { "version": "4.0.2", - "license": "Apache-2.0", - "optional": true, + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", "dependencies": { "xmlcreate": "^2.0.4" } @@ -10848,20 +10971,20 @@ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" }, "node_modules/jsdoc": { - "version": "4.0.2", - "license": "Apache-2.0", - "optional": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.3.tgz", + "integrity": "sha512-Nu7Sf35kXJ1MWDZIMAuATRQTg1iIPdzh7tqJ6jjvaU/GfDf+qi5UV8zJR3Mo+/pYFvm8mzay4+6O5EWigaQBQw==", "dependencies": { "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^12.2.3", + "@types/markdown-it": "^14.1.1", "bluebird": "^3.7.2", "catharsis": "^0.9.0", "escape-string-regexp": "^2.0.0", "js2xmlparser": "^4.0.2", "klaw": "^3.0.0", - "markdown-it": "^12.3.2", - "markdown-it-anchor": "^8.4.1", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", "marked": "^4.0.10", "mkdirp": "^1.0.4", "requizzle": "^0.2.3", @@ -11110,8 +11233,8 @@ }, "node_modules/klaw": { "version": "3.0.0", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", "dependencies": { "graceful-fs": "^4.1.9" } @@ -11182,11 +11305,11 @@ "peer": true }, "node_modules/linkify-it": { - "version": "3.0.3", - "license": "MIT", - "optional": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "dependencies": { - "uc.micro": "^1.0.1" + "uc.micro": "^2.0.0" } }, "node_modules/lint-staged": { @@ -11503,8 +11626,9 @@ } }, "node_modules/long": { - "version": "4.0.0", - "license": "Apache-2.0" + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" }, "node_modules/loupe": { "version": "2.3.6", @@ -11630,24 +11754,25 @@ } }, "node_modules/markdown-it": { - "version": "12.3.2", - "license": "MIT", - "optional": true, + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", "dependencies": { "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, "bin": { - "markdown-it": "bin/markdown-it.js" + "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/markdown-it-anchor": { "version": "8.6.7", - "license": "Unlicense", - "optional": true, + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", "peerDependencies": { "@types/markdown-it": "*", "markdown-it": "*" @@ -11655,8 +11780,8 @@ }, "node_modules/marked": { "version": "4.3.0", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "bin": { "marked": "bin/marked.js" }, @@ -11676,9 +11801,9 @@ } }, "node_modules/mdurl": { - "version": "1.0.1", - "license": "MIT", - "optional": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" }, "node_modules/media-typer": { "version": "0.3.0", @@ -11772,7 +11897,7 @@ }, "node_modules/minimatch": { "version": "3.1.2", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -12856,17 +12981,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/parseurl": { "version": "1.3.3", "license": "MIT", @@ -12884,7 +12998,7 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13413,20 +13527,21 @@ "license": "MIT" }, "node_modules/proto3-json-serializer": { - "version": "1.1.0", - "license": "Apache-2.0", - "optional": true, + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", "dependencies": { - "protobufjs": "^7.0.0" + "protobufjs": "^7.2.5" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/protobufjs": { - "version": "7.2.4", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", "hasInstallScript": true, - "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -13445,10 +13560,6 @@ "node": ">=12.0.0" } }, - "node_modules/protobufjs/node_modules/long": { - "version": "5.2.3", - "license": "Apache-2.0" - }, "node_modules/protochain": { "version": "1.0.5", "license": "ISC" @@ -13484,6 +13595,14 @@ "version": "1.4.1", "license": "MIT" }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.0.2", "dev": true, @@ -13751,8 +13870,8 @@ }, "node_modules/requizzle": { "version": "0.2.4", - "license": "MIT", - "optional": true, + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", "dependencies": { "lodash": "^4.17.21" } @@ -13870,7 +13989,6 @@ "node_modules/retry-request": { "version": "5.0.2", "license": "MIT", - "optional": true, "dependencies": { "debug": "^4.1.1", "extend": "^3.0.2" @@ -13882,7 +14000,6 @@ "node_modules/retry-request/node_modules/debug": { "version": "4.3.4", "license": "MIT", - "optional": true, "dependencies": { "ms": "2.1.2" }, @@ -13897,8 +14014,7 @@ }, "node_modules/retry-request/node_modules/ms": { "version": "2.1.2", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/reusify": { "version": "1.0.4", @@ -13924,7 +14040,7 @@ }, "node_modules/rimraf": { "version": "3.0.2", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -14504,7 +14620,6 @@ "node_modules/stream-events": { "version": "1.0.5", "license": "MIT", - "optional": true, "dependencies": { "stubs": "^3.0.0" } @@ -14738,8 +14853,7 @@ }, "node_modules/stubs": { "version": "3.0.0", - "license": "MIT", - "optional": true + "license": "MIT" }, "node_modules/supports-color": { "version": "7.2.0", @@ -15017,14 +15131,11 @@ } }, "node_modules/tmp": { - "version": "0.2.1", - "license": "MIT", - "optional": true, - "dependencies": { - "rimraf": "^3.0.0" - }, + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", "engines": { - "node": ">=8.17.0" + "node": ">=14.14" } }, "node_modules/tmpl": { @@ -15319,9 +15430,9 @@ } }, "node_modules/uc.micro": { - "version": "1.0.6", - "license": "MIT", - "optional": true + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" }, "node_modules/ufo": { "version": "1.1.2", @@ -15329,9 +15440,9 @@ "license": "MIT" }, "node_modules/uglify-js": { - "version": "3.17.4", - "license": "BSD-2-Clause", - "optional": true, + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -15354,9 +15465,9 @@ } }, "node_modules/underscore": { - "version": "1.13.6", - "license": "MIT", - "optional": true + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==" }, "node_modules/undici": { "version": "6.15.0", @@ -15499,8 +15610,13 @@ } }, "node_modules/uuid": { - "version": "9.0.0", - "license": "MIT", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } @@ -15898,9 +16014,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "license": "MIT", - "optional": true, + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "engines": { "node": ">=0.10.0" } @@ -16020,8 +16136,8 @@ }, "node_modules/xmlcreate": { "version": "2.0.4", - "license": "Apache-2.0", - "optional": true + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" }, "node_modules/xorshift": { "version": "1.2.0", diff --git a/functions/package.json b/functions/package.json index 8b55bf5cc..b7a3e69f7 100644 --- a/functions/package.json +++ b/functions/package.json @@ -26,7 +26,8 @@ "test-e2e": "NODE_ENV=local-dev FIRESTORE_EMULATOR_HOST=127.0.0.1:8080 vitest", "coverage": "V8_COVERAGE=true npm run test-e2e -- run --coverage", "lint": "eslint .", - "import:firestore": "fire-import firestore" + "import:firestore": "fire-import firestore", + "import:auth": "firebase auth:export ./firebaseExport/auth_export/accounts.json" }, "lint-staged": { "src/**/*": [ @@ -36,6 +37,7 @@ }, "dependencies": { "@faker-js/faker": "^8.0.2", + "@google-cloud/firestore": "^6.6.0", "@google-cloud/opentelemetry-cloud-trace-exporter": "^2.1.0", "@google-cloud/opentelemetry-cloud-trace-propagator": "^0.17.0", "@google-cloud/secret-manager": "^5.0.1", diff --git a/functions/src/commons/controllers/markCommonSeenForUser.ts b/functions/src/commons/controllers/markCommonSeenForUser.ts new file mode 100644 index 000000000..3dac0739c --- /dev/null +++ b/functions/src/commons/controllers/markCommonSeenForUser.ts @@ -0,0 +1,48 @@ +import { Request, Response, NextFunction } from "express"; +import { markCommonSeenForUserSchema } from "../../shared/schemas"; +import { + DbOperationType, + UnauthorizedError, + responseExecutor, + runManagerTransaction, +} from "../../util"; +import { commonsServices } from "../services"; + +export const markCommonSeenForUser = async ( + req: Request, + res: Response, + next: NextFunction +) => { + await responseExecutor( + async () => { + if (!req.user) throw new UnauthorizedError(); + const payload = { + ...req.body, + userId: req.user.uid, + }; + + const validatedPayload = await markCommonSeenForUserSchema.validate( + payload + ); + + const seen = await runManagerTransaction(async (manager) => { + const dbConfig = { + manager, + type: DbOperationType.TransactionManager, + } as const; + return commonsServices.markCommonSeenForUser( + validatedPayload, + dbConfig + ); + }); + + return seen; + }, + { + req, + res, + next, + successMessage: "Seen marked", + } + ); +}; diff --git a/functions/src/commons/controllers/markCommonUnseenForUser.ts b/functions/src/commons/controllers/markCommonUnseenForUser.ts new file mode 100644 index 000000000..9e049d595 --- /dev/null +++ b/functions/src/commons/controllers/markCommonUnseenForUser.ts @@ -0,0 +1,48 @@ +import { Request, Response, NextFunction } from "express"; +import { markCommonUnseenForUserSchema } from "../../shared/schemas"; +import { + DbOperationType, + UnauthorizedError, + responseExecutor, + runManagerTransaction, +} from "../../util"; +import { commonsServices } from "../services"; + +export const markCommonUnseenForUser = async ( + req: Request, + res: Response, + next: NextFunction +) => { + await responseExecutor( + async () => { + if (!req.user) throw new UnauthorizedError(); + const payload = { + ...req.body, + userId: req.user.uid, + }; + + const validatedPayload = await markCommonUnseenForUserSchema.validate( + payload + ); + + const unseen = await runManagerTransaction(async (manager) => { + const dbConfig = { + manager, + type: DbOperationType.TransactionManager, + } as const; + return commonsServices.markCommonUnseenForUser( + validatedPayload, + dbConfig + ); + }); + + return unseen; + }, + { + req, + res, + next, + successMessage: "Unseen marked", + } + ); +}; diff --git a/functions/src/commons/controllers/markFeedObjectSeenForUser.ts b/functions/src/commons/controllers/markFeedObjectSeenForUser.ts index f8ff6b059..4808c272d 100644 --- a/functions/src/commons/controllers/markFeedObjectSeenForUser.ts +++ b/functions/src/commons/controllers/markFeedObjectSeenForUser.ts @@ -31,7 +31,7 @@ export const markFeedObjectSeenForUser = async ( type: DbOperationType.TransactionManager, } as const; return commonsServices.markFeedObjectSeenForUser( - validatedPayload, + { ...validatedPayload, updateInbox: true }, dbConfig ); }); diff --git a/functions/src/commons/controllers/markFeedObjectUnseenForUser.ts b/functions/src/commons/controllers/markFeedObjectUnseenForUser.ts index 91449b721..d9d7bbdc2 100644 --- a/functions/src/commons/controllers/markFeedObjectUnseenForUser.ts +++ b/functions/src/commons/controllers/markFeedObjectUnseenForUser.ts @@ -1,6 +1,11 @@ import { Request, Response, NextFunction } from "express"; import { markFeedObjectUnseenForUser as markFeedObjectUnseenForUserSchema } from "../../shared/schemas"; -import { UnauthorizedError, responseExecutor } from "../../util"; +import { + DbOperationType, + UnauthorizedError, + responseExecutor, + runManagerTransaction, +} from "../../util"; import { commonsServices } from "../services"; export const markFeedObjectUnseenForUser = async ( @@ -20,7 +25,16 @@ export const markFeedObjectUnseenForUser = async ( payload ); - await commonsServices.markFeedObjectUnseenForUser(validatedPayload); + await runManagerTransaction(async (manager) => { + const dbConfig = { + manager, + type: DbOperationType.TransactionManager, + } as const; + await commonsServices.markFeedObjectUnseenForUser( + validatedPayload, + dbConfig + ); + }); }, { req, diff --git a/functions/src/commons/eventHandlers/onCommonCreated.ts b/functions/src/commons/eventHandlers/onCommonCreated.ts index 671ce8499..6a347b07e 100644 --- a/functions/src/commons/eventHandlers/onCommonCreated.ts +++ b/functions/src/commons/eventHandlers/onCommonCreated.ts @@ -12,6 +12,11 @@ import { DocumentData, Timestamp } from "firebase-admin/firestore"; import { createDiscussionService } from "../../discussion/services"; import { commonFeedDb } from "../db/commonFeed"; import { follow } from "../services/follow"; +import { + DbOperationConfig, + DbOperationType, + runManagerTransaction, +} from "../../util"; export const onCommonCreated = async ( event: Omit & { type: EVENT_TYPES.COMMON_CREATED } @@ -67,20 +72,34 @@ async function createAndPinDiscussion( circleVisibility: [], ...payload, }; - const discussion = await createDiscussionService(discussionPayload); - const commonFeedObject = await commonFeedDb.get( - { - id: discussion.id, - commonId: common.id, - }, - true - ); - const pinnedFeedItems = [ - { pinnedAt: Timestamp.now(), feedObjectId: commonFeedObject.id }, - ...(common?.pinnedFeedItems || []), - ]; - await commonsDb.update({ pinnedFeedItems }, common.id, true); + const discussion = await runManagerTransaction(async (manager) => { + const dbConfig: DbOperationConfig = { + manager, + type: DbOperationType.TransactionManager, + }; + const discussion = await createDiscussionService( + discussionPayload, + dbConfig + ); + + const commonFeedObject = await commonFeedDb.get( + { + id: discussion.id, + commonId: common.id, + }, + true, + dbConfig + ); + + const pinnedFeedItems = [ + { pinnedAt: Timestamp.now(), feedObjectId: commonFeedObject.id }, + ...(common?.pinnedFeedItems || []), + ]; + await commonsDb.update({ pinnedFeedItems }, common.id, true, dbConfig); + + return discussion; + }); return discussion; } diff --git a/functions/src/commons/index.ts b/functions/src/commons/index.ts index 41a294493..a8367af6b 100644 --- a/functions/src/commons/index.ts +++ b/functions/src/commons/index.ts @@ -26,6 +26,8 @@ import { } from "./controllers"; import { ExpressAppConfig } from "../shared/interfaces"; import { unlinkStream } from "./controllers/unlinkStream"; +import { markCommonSeenForUser } from "./controllers/markCommonSeenForUser"; +import { markCommonUnseenForUser } from "./controllers/markCommonUnseenForUser"; const commonsRouter = commonRouter(); @@ -36,6 +38,9 @@ commonsRouter.get("/:commonId/feed-items", getFeedItems); // Authenticated routes commonsRouter.use(authenticate); +commonsRouter.post("/mark-seen-for-user", markCommonSeenForUser); +commonsRouter.post("/mark-unseen-for-user", markCommonUnseenForUser); + commonsRouter.post( "/mark-feed-object-seen-for-user", markFeedObjectSeenForUser diff --git a/functions/src/commons/services/index.ts b/functions/src/commons/services/index.ts index 722f15459..018b28c64 100644 --- a/functions/src/commons/services/index.ts +++ b/functions/src/commons/services/index.ts @@ -22,6 +22,8 @@ import { } from "./setupNotionIntegration"; import { removeNotionIntegration } from "./removeNotionIntegration"; import { moveStream } from "./moveStream"; +import { markCommonSeenForUser } from "./markCommonSeenForUser"; +import { markCommonUnseenForUser } from "./markCommonUnseenForUser"; export const commonsServices = { create, @@ -46,4 +48,6 @@ export const commonsServices = { setupAndValidateNotionIntegration, removeNotionIntegration, moveStream, + markCommonSeenForUser, + markCommonUnseenForUser, }; diff --git a/functions/src/commons/services/markCommonSeenForUser.ts b/functions/src/commons/services/markCommonSeenForUser.ts new file mode 100644 index 000000000..94c787d9a --- /dev/null +++ b/functions/src/commons/services/markCommonSeenForUser.ts @@ -0,0 +1,56 @@ +import { MarkCommonSeenForUser } from "../../shared/schemas"; +import { userDb } from "../../users/db"; +import { getInboxCounter } from "../../users/business/getInboxCounter"; +import { TransactionManagerOperationConfig, whereOperation } from "../../util"; +import { CommonFeedSubcollection } from "../db/commonFeed/CommonFeedSubcollection"; +import { markFeedObjectSeenForUser } from "./markFeedObjectSeenForUser"; +import { discussionMessageCollection } from "../../discussion/db/discussionMessage"; +import { FeedObjectTypes, LastSeenEntity } from "../../shared/interfaces"; + +export const markCommonSeenForUser = async ( + payload: MarkCommonSeenForUser, + dbConfig: TransactionManagerOperationConfig +) => { + const { userId, commonId } = payload; + const feedObjects = ( + await whereOperation( + dbConfig, + CommonFeedSubcollection(commonId).where("isDeleted", "==", false) + ) + ).docs; + + await Promise.all( + feedObjects.map(async (feedObjectDoc) => { + const feedObject = feedObjectDoc.data(); + let discussionId: string | undefined; + if (feedObject?.data.type === FeedObjectTypes.Discussion) { + discussionId = feedObject.data.id; + } else { + discussionId = feedObject.data.discussionId ?? undefined; + } + if (!discussionId) return; + const lastMessage = ( + await discussionMessageCollection + .where("discussionId", "==", discussionId) + .orderBy("createdAt", "desc") + .limit(1) + .get() + ).docs[0]?.data(); + await markFeedObjectSeenForUser( + { + feedObjectId: feedObject.id, + commonId, + lastSeenId: lastMessage.id, + type: LastSeenEntity.DiscussionMessage, + userId, + updateInbox: false, + }, + dbConfig + ); + }) + ); + + await userDb.update(userId, { + inboxCounter: await getInboxCounter(userId), + }); +}; diff --git a/functions/src/commons/services/markCommonUnseenForUser.ts b/functions/src/commons/services/markCommonUnseenForUser.ts new file mode 100644 index 000000000..6cd50b47c --- /dev/null +++ b/functions/src/commons/services/markCommonUnseenForUser.ts @@ -0,0 +1,55 @@ +import { MarkCommonUnseenForUser } from "../../shared/schemas"; +import { userDb } from "../../users/db"; +import { getInboxCounter } from "../../users/business/getInboxCounter"; +import { TransactionManagerOperationConfig, whereOperation } from "../../util"; +import { CommonFeedSubcollection } from "../db/commonFeed/CommonFeedSubcollection"; +import { discussionMessageCollection } from "../../discussion/db/discussionMessage"; +import { FeedObjectTypes, LastSeenEntity } from "../../shared/interfaces"; +import { markFeedObjectUnseenForUser } from "./markFeedObjectUnseenForUser"; + +export const markCommonUnseenForUser = async ( + payload: MarkCommonUnseenForUser, + dbConfig: TransactionManagerOperationConfig +) => { + const { userId, commonId } = payload; + const feedObjects = ( + await whereOperation( + dbConfig, + CommonFeedSubcollection(commonId).where("isDeleted", "==", false) + ) + ).docs; + + await Promise.all( + feedObjects.map(async (feedObjectDoc) => { + const feedObject = feedObjectDoc.data(); + let discussionId: string | undefined; + if (feedObject?.data.type === FeedObjectTypes.Discussion) { + discussionId = feedObject.data.id; + } else { + discussionId = feedObject.data.discussionId ?? undefined; + } + if (!discussionId) return; + const lastMessage = ( + await discussionMessageCollection + .where("discussionId", "==", discussionId) + .orderBy("createdAt", "desc") + .limit(1) + .get() + ).docs[0]?.data(); + await markFeedObjectUnseenForUser( + { + feedObjectId: feedObject.id, + commonId, + lastSeenId: lastMessage.id, + type: LastSeenEntity.DiscussionMessage, + userId, + }, + dbConfig + ); + }) + ); + + await userDb.update(userId, { + inboxCounter: await getInboxCounter(userId), + }); +}; diff --git a/functions/src/commons/services/markFeedObjectSeenForUser.ts b/functions/src/commons/services/markFeedObjectSeenForUser.ts index 575109059..fd491d577 100644 --- a/functions/src/commons/services/markFeedObjectSeenForUser.ts +++ b/functions/src/commons/services/markFeedObjectSeenForUser.ts @@ -12,7 +12,9 @@ import { TransactionManagerOperationConfig } from "../../util"; import { getDiscussionFeedItems } from "../business/getFeedItems"; export const markFeedObjectSeenForUser = async ( - payload: MarkFeedObjectSeenForUser, + payload: MarkFeedObjectSeenForUser & { + updateInbox?: boolean; + }, dbConfig: TransactionManagerOperationConfig ) => { const { userId, lastSeenId, type } = payload; @@ -171,7 +173,7 @@ export const markFeedObjectSeenForUser = async ( }) ); - if (shouldUpdateInbox) { + if (shouldUpdateInbox && payload.updateInbox) { await userDb.update(userId, { inboxCounter: await getInboxCounter(userId), }); diff --git a/functions/src/commons/services/markFeedObjectUnseenForUser.ts b/functions/src/commons/services/markFeedObjectUnseenForUser.ts index c8a65314d..9bb89204f 100644 --- a/functions/src/commons/services/markFeedObjectUnseenForUser.ts +++ b/functions/src/commons/services/markFeedObjectUnseenForUser.ts @@ -3,100 +3,90 @@ import { commonFeedDb } from "../db/commonFeed"; import { commonFeedObjectUserUniqueDb } from "../db/CommonFeedObjectUserUnique"; import { Timestamp } from "firebase-admin/firestore"; import { CommonMembersSubcollectionDb } from "../db/commonMembers"; -import { - CommonError, - DbOperationType, - runManagerTransaction, -} from "../../util"; +import { CommonError, TransactionManagerOperationConfig } from "../../util"; import { FeedObjectTypes } from "../../shared/interfaces"; import { discussionDb } from "../../discussion/db"; import { getDiscussionFeedItems } from "../business/getFeedItems"; export const markFeedObjectUnseenForUser = async ( - payload: MarkFeedObjectSeenForUser + payload: MarkFeedObjectSeenForUser, + dbConfig: TransactionManagerOperationConfig ) => { const { userId } = payload; - await runManagerTransaction(async (manager) => { - const dbConfig = { - manager, - type: DbOperationType.TransactionManager, - } as const; - const selectedCommonFeedObject = await commonFeedDb.getByObjectId( - payload.feedObjectId, - payload.commonId, + const selectedCommonFeedObject = await commonFeedDb.getByObjectId( + payload.feedObjectId, + payload.commonId, + true, + dbConfig + ); + + let commonFeedObjects = [ + [selectedCommonFeedObject, payload.commonId] as const, + ]; + if (selectedCommonFeedObject.data.type === FeedObjectTypes.Discussion) { + const discussion = await discussionDb.getDiscussion( + selectedCommonFeedObject.data.id, true, dbConfig ); + commonFeedObjects = await getDiscussionFeedItems(discussion, dbConfig); + } - let commonFeedObjects = [ - [selectedCommonFeedObject, payload.commonId] as const, - ]; - if (selectedCommonFeedObject.data.type === FeedObjectTypes.Discussion) { - const discussion = await discussionDb.getDiscussion( - selectedCommonFeedObject.data.id, - true, + await Promise.all( + commonFeedObjects.map(async ([feedObject, commonId]) => { + // eslint-disable-next-line prefer-const + let feedObjectUserUnique = await commonFeedObjectUserUniqueDb.get( + feedObject.id, + userId, + commonId, + false, dbConfig ); - commonFeedObjects = await getDiscussionFeedItems(discussion, dbConfig); - } - await Promise.all( - commonFeedObjects.map(async ([feedObject, commonId]) => { - // eslint-disable-next-line prefer-const - let feedObjectUserUnique = await commonFeedObjectUserUniqueDb.get( - feedObject.id, - userId, + if (!feedObjectUserUnique) { + feedObjectUserUnique = await commonFeedObjectUserUniqueDb.add( + { + createdAt: Timestamp.now(), + feedObjectId: feedObject.id, + userId, + count: 0, + seenOnce: false, + seen: false, + hasUnseenMention: false, + }, commonId, - false, dbConfig ); + } - if (!feedObjectUserUnique) { - feedObjectUserUnique = await commonFeedObjectUserUniqueDb.add( - { - createdAt: Timestamp.now(), - feedObjectId: feedObject.id, - userId, - count: 0, - seenOnce: false, - seen: false, - hasUnseenMention: false, - }, - commonId, - dbConfig - ); - } - - if (!feedObjectUserUnique) { - return; - } - const commonMember = await CommonMembersSubcollectionDb.get( - { commonId, userId }, - false, - dbConfig + if (!feedObjectUserUnique) { + return; + } + const commonMember = await CommonMembersSubcollectionDb.get( + { commonId, userId }, + false, + dbConfig + ); + if (!commonMember) { + return; + } + const unreadCount = commonMember.unreadCountByStream[feedObject.id] || 0; + if (unreadCount > 0) { + throw new CommonError( + "Cannot mark feed object unseen if there are unread messages" ); - if (!commonMember) { - return; - } - const unreadCount = - commonMember.unreadCountByStream[feedObject.id] || 0; - if (unreadCount > 0) { - throw new CommonError( - "Cannot mark feed object unseen if there are unread messages" - ); - } + } - await commonFeedObjectUserUniqueDb.update( - commonId, - feedObjectUserUnique.id, - { - seen: false, - }, - true, - dbConfig - ); - }) - ); - }); + await commonFeedObjectUserUniqueDb.update( + commonId, + feedObjectUserUnique.id, + { + seen: false, + }, + true, + dbConfig + ); + }) + ); }; diff --git a/functions/src/discussion/controllers/discussion/create.ts b/functions/src/discussion/controllers/discussion/create.ts index f004cc507..0389753e8 100644 --- a/functions/src/discussion/controllers/discussion/create.ts +++ b/functions/src/discussion/controllers/discussion/create.ts @@ -1,5 +1,10 @@ import { Request, Response, NextFunction } from "express"; -import { responseExecutor } from "../../../util"; +import { + DbOperationConfig, + DbOperationType, + responseExecutor, + runManagerTransaction, +} from "../../../util"; import { createDiscussionDataValidationScheme } from "../../../shared/schemas"; import { createDiscussionService } from "../../services"; import { hasPermission } from "../../../users/business"; @@ -67,7 +72,13 @@ export const createDiscussion = async ( }); } - return await createDiscussionService(validated); + return await runManagerTransaction(async (manager) => { + const dbConfig: DbOperationConfig = { + manager, + type: DbOperationType.TransactionManager, + }; + return createDiscussionService(validated, dbConfig); + }); }, { req, diff --git a/functions/src/discussion/db/discussion/addDiscussion.ts b/functions/src/discussion/db/discussion/addDiscussion.ts index 67fa32bc4..a062180b6 100644 --- a/functions/src/discussion/db/discussion/addDiscussion.ts +++ b/functions/src/discussion/db/discussion/addDiscussion.ts @@ -7,7 +7,12 @@ import { OmittedDiscussionCreationProperties, } from "../../../shared/types"; import { discussionCollection } from "./discussionCollection"; -import { DbOperationConfig, setOperation } from "../../../util"; +import { + ArgumentError, + getOperation, + setOperation, + AnyTransactionOperationConfig, +} from "../../../util"; /** * Add the id to the passed discussion object and @@ -17,11 +22,15 @@ import { DbOperationConfig, setOperation } from "../../../util"; */ export const addDiscussion = async ( - discussion: SharedOmit, - dbConfig?: DbOperationConfig + discussion: SharedOmit & { + id?: string; + }, + dbConfig: AnyTransactionOperationConfig ): Promise => { + const { id: providedId, ...discussionWithoutId } = discussion; const discussionDoc: Discussion = { - id: v4(), + ...discussionWithoutId, + id: providedId || v4(), createdAt: Timestamp.fromDate(new Date()), updatedAt: Timestamp.fromDate(new Date()), isDeleted: false, @@ -29,9 +38,19 @@ export const addDiscussion = async ( messageCount: 0, followers: [], linkedCommonIds: [], - ...discussion, }; + // Check if the discussion with the given id already exists + if (providedId) { + const existingDiscussion = await getOperation( + dbConfig, + discussionCollection.doc(providedId) + ); + if (existingDiscussion.exists) { + throw new ArgumentError("id", providedId); + } + } + await setOperation( dbConfig, discussionCollection.doc(discussionDoc.id), diff --git a/functions/src/discussion/services/discussion/create.ts b/functions/src/discussion/services/discussion/create.ts index 4bdb33888..7a56985a7 100644 --- a/functions/src/discussion/services/discussion/create.ts +++ b/functions/src/discussion/services/discussion/create.ts @@ -2,13 +2,14 @@ import { discussionDb } from "../../db"; import { CreateDiscussionPayload } from "../../../shared/interfaces"; import { createCommonFeedObject } from "../../../commons/business/createCommonFeedObject"; import { EVENT_TYPES } from "../../../shared/enums"; -import { DbOperationConfig, createEvent } from "../../../util/db"; +import { AnyTransactionOperationConfig, createEvent } from "../../../util/db"; export const createDiscussionService = async ( payload: CreateDiscussionPayload, - dbConfig?: DbOperationConfig + dbConfig: AnyTransactionOperationConfig ) => { const discussionPayload = { + id: payload.id, title: payload.title, message: payload.message, ownerId: payload.ownerId, diff --git a/functions/src/proposals/db/proposals/add.ts b/functions/src/proposals/db/proposals/add.ts index c2a855b14..a869aa456 100644 --- a/functions/src/proposals/db/proposals/add.ts +++ b/functions/src/proposals/db/proposals/add.ts @@ -12,9 +12,9 @@ import { PROPOSAL_STATE, MODERATION_FLAGS } from "../../../shared/enums"; import { calculateTotalMembersWithVotingRights } from "../../business/proposals/calculateTotalMembersWithVotingRights"; import { discussionDb } from "../../../discussion/db"; import { - DbOperationType, - NonAtomicOperationConfig, + getOperation, setOperation, + TransactionManagerOperationConfig, } from "../../../util"; export type OmittedConstantProposalKeys = Omit< @@ -30,10 +30,14 @@ export type OmittedConstantProposalKeys = Omit< >; export const addProposal = async ( - proposal: OmittedConstantProposalKeys, - dbConfig: NonAtomicOperationConfig = { type: DbOperationType.NonAtomic } + proposal: OmittedConstantProposalKeys & { + id?: string; + discussionId?: string; + }, + dbConfig: TransactionManagerOperationConfig ) => { - const id = v4(); + const providedId = proposal.id; + const id = providedId ?? v4(); const commonId = proposal.data.args.commonId; const membersWithVotingRight = await calculateTotalMembersWithVotingRights( proposal, @@ -47,20 +51,36 @@ export const addProposal = async ( if (!circleVisibility) { throw new Error("Circle visibility not defined"); } - const discussion = await discussionDb.addDiscussion({ - title: proposal.data.args.title, - message: proposal.data.args.description, - ownerId: proposal.data.args.proposerId, - commonId, - files: proposal.data.args.files, - images: proposal.data.args.images, - circleVisibilityByCommon: { - [commonId]: circleVisibility, + + if (providedId) { + const existingProposal = await getOperation( + dbConfig, + ProposalsCollection.doc(providedId) + ); + if (existingProposal.exists) { + throw new Error("Proposal already exists"); + } + } + + const discussion = await discussionDb.addDiscussion( + { + id: proposal.discussionId, + title: proposal.data.args.title, + message: proposal.data.args.description, + ownerId: proposal.data.args.proposerId, + commonId, + files: proposal.data.args.files, + images: proposal.data.args.images, + circleVisibilityByCommon: { + [commonId]: circleVisibility, + }, + proposalId: id, }, - proposalId: id, - }); + dbConfig + ); const proposalDoc = { + ...proposal, id, createdAt: Timestamp.now(), updatedAt: Timestamp.now(), @@ -90,7 +110,6 @@ export const addProposal = async ( state: proposal.global.discussionDuration ? PROPOSAL_STATE.DISCUSSION : PROPOSAL_STATE.VOTING, - ...proposal, } as Proposal; await setOperation(dbConfig, ProposalsCollection.doc(id), proposalDoc); diff --git a/functions/src/proposals/services/proposals/createAssignCircle.ts b/functions/src/proposals/services/proposals/createAssignCircle.ts index 563baf465..fb988c45b 100644 --- a/functions/src/proposals/services/proposals/createAssignCircle.ts +++ b/functions/src/proposals/services/proposals/createAssignCircle.ts @@ -1,7 +1,12 @@ import { Timestamp } from "firebase-admin/firestore"; import dayjs from "dayjs"; import { EVENT_TYPES, PROPOSALS } from "../../../shared/enums"; -import { createEvent } from "../../../util/db"; +import { + createEvent, + DbOperationConfig, + DbOperationType, + runManagerTransaction, +} from "../../../util/db"; import { proposalsDb } from "../../db/proposals"; import { governanceDb } from "../../../governance/db"; import { AssignCircleArgs } from "../../../shared/schemas"; @@ -66,39 +71,50 @@ export const createAssignCircle = async (payload: AssignCircleArgs) => { const { global, local, limitations } = proposalGovDefinition; - const AssignCircle = (await proposalsDb.add({ - data: { - votingExpiresOn: global.discussionDuration - ? null - : Timestamp.fromDate( - dayjs().add(global.votingDuration, "h").toDate() - ), - discussionExpiresOn: global.discussionDuration - ? Timestamp.fromDate( - dayjs().add(global.discussionDuration, "h").toDate() - ) - : null, - args: { - commonId, - proposerId, - userId, - title, - description, - images, - files, - links, - circleId, - circleVisibilityByCommon: { - [commonId]: circleVisibility, + const AssignCircle = await runManagerTransaction(async (manager) => { + const dbConfig: DbOperationConfig = { + manager, + type: DbOperationType.TransactionManager, + }; + return (await proposalsDb.add( + { + id: payload.id, + discussionId: payload.discussionId, + data: { + votingExpiresOn: global.discussionDuration + ? null + : Timestamp.fromDate( + dayjs().add(global.votingDuration, "h").toDate() + ), + discussionExpiresOn: global.discussionDuration + ? Timestamp.fromDate( + dayjs().add(global.discussionDuration, "h").toDate() + ) + : null, + args: { + commonId, + proposerId, + userId, + title, + description, + images, + files, + links, + circleId, + circleVisibilityByCommon: { + [commonId]: circleVisibility, + }, + }, }, + type: PROPOSALS.ASSIGN_CIRCLE, + global, + local, + limitations, + resolutionType: ProposalResolutionType.IMMEDIATE, }, - }, - type: PROPOSALS.ASSIGN_CIRCLE, - global, - local, - limitations, - resolutionType: ProposalResolutionType.IMMEDIATE, - })) as AssignCircle; + dbConfig + )) as AssignCircle; + }); await createEvent({ userId: proposerId, diff --git a/functions/src/proposals/services/proposals/createCircle.ts b/functions/src/proposals/services/proposals/createCircle.ts index fb90a7c99..24b8af8ce 100644 --- a/functions/src/proposals/services/proposals/createCircle.ts +++ b/functions/src/proposals/services/proposals/createCircle.ts @@ -3,7 +3,12 @@ import dayjs from "dayjs"; import { proposalsDb } from "../../db/proposals"; import { EVENT_TYPES, PROPOSALS } from "../../../shared/enums"; import { CreateCircleArgs } from "../../../shared/schemas"; -import { createEvent } from "../../../util/db"; +import { + createEvent, + DbOperationConfig, + DbOperationType, + runManagerTransaction, +} from "../../../util/db"; import { governanceDb } from "../../../governance/db"; import { CreateCircle, @@ -38,40 +43,52 @@ export const createCircleAction = async (payload: CreateCircleArgs) => { CREATE_CIRCLE: { global, local, limitations }, }, } = governance; - const createCircleAction = (await proposalsDb.add({ - data: { - votingExpiresOn: global.discussionDuration - ? null - : Timestamp.fromDate( - dayjs().add(global.votingDuration, "h").toDate() - ), - discussionExpiresOn: global.discussionDuration - ? Timestamp.fromDate( - dayjs().add(global.discussionDuration, "h").toDate() - ) - : null, - args: { - commonId, - proposerId, - title, - description, - images, - files, - links, - circleDefinition, - existingCircleChanges, - includeInProposals, - circleVisibilityByCommon: { - [commonId]: circleVisibility, + + const createCircleAction = await runManagerTransaction(async (manager) => { + const dbConfig: DbOperationConfig = { + manager, + type: DbOperationType.TransactionManager, + }; + return (await proposalsDb.add( + { + id: payload.id, + discussionId: payload.discussionId, + data: { + votingExpiresOn: global.discussionDuration + ? null + : Timestamp.fromDate( + dayjs().add(global.votingDuration, "h").toDate() + ), + discussionExpiresOn: global.discussionDuration + ? Timestamp.fromDate( + dayjs().add(global.discussionDuration, "h").toDate() + ) + : null, + args: { + commonId, + proposerId, + title, + description, + images, + files, + links, + circleDefinition, + existingCircleChanges, + includeInProposals, + circleVisibilityByCommon: { + [commonId]: circleVisibility, + }, + }, }, + local, + limitations, + global, + type: PROPOSALS.CREATE_CIRCLE, + resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, }, - }, - local, - limitations, - global, - type: PROPOSALS.CREATE_CIRCLE, - resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, - })) as CreateCircle; + dbConfig + )) as CreateCircle; + }); await createEvent({ userId: proposerId, diff --git a/functions/src/proposals/services/proposals/createFundsAllocation.ts b/functions/src/proposals/services/proposals/createFundsAllocation.ts index cb3259a1d..d55d15c83 100644 --- a/functions/src/proposals/services/proposals/createFundsAllocation.ts +++ b/functions/src/proposals/services/proposals/createFundsAllocation.ts @@ -7,7 +7,12 @@ import { SELLER_STATUSES, ACTIONS, } from "../../../shared/enums"; -import { createEvent } from "../../../util/db"; +import { + createEvent, + DbOperationConfig, + DbOperationType, + runManagerTransaction, +} from "../../../util/db"; import { ArgumentError, CommonError } from "../../../util/errors"; import { proposalsDb } from "../../db/proposals"; import { @@ -130,55 +135,66 @@ export const createFundsAllocation = async (payload: FundsAllocationArgs) => { } ); - const fundingAllocation = (await proposalsDb.add({ - data: { - votingExpiresOn: global.discussionDuration - ? null - : Timestamp.fromDate( - dayjs().add(global.votingDuration, "h").toDate() - ), - discussionExpiresOn: global.discussionDuration - ? Timestamp.fromDate( - dayjs().add(global.discussionDuration, "h").toDate() - ) - : null, - args: { - commonId, - proposerId, - amount, - title, - description, - images, - files, - links, - to, - circleVisibilityByCommon: { - [commonId]: circleVisibility, + const fundingAllocation = await runManagerTransaction(async (manager) => { + const dbConfig: DbOperationConfig = { + manager, + type: DbOperationType.TransactionManager, + }; + return (await proposalsDb.add( + { + id: payload.id, + discussionId: payload.discussionId, + data: { + votingExpiresOn: global.discussionDuration + ? null + : Timestamp.fromDate( + dayjs().add(global.votingDuration, "h").toDate() + ), + discussionExpiresOn: global.discussionDuration + ? Timestamp.fromDate( + dayjs().add(global.discussionDuration, "h").toDate() + ) + : null, + args: { + commonId, + proposerId, + amount, + title, + description, + images, + files, + links, + to, + circleVisibilityByCommon: { + [commonId]: circleVisibility, + }, + ...(subcommonId && + to === ALLOCATE_FUNDS_TO.subcommon && { subcommonId }), + ...(otherMemberId && + to === ALLOCATE_FUNDS_TO.otherMember && { otherMemberId }), + }, + legal: { + payoutDocs: [], + payoutDocsUserComment: null, + totalInvoicesAmount: null, + payoutDocsRejectionReason: null, + }, + tracker: { + status: FUNDING_ALLOCATION_STATUS.PENDING_PROPOSAL_APPROVAL, + invoicesNotUploadedNotificationsCounter: 0, + trusteeApprovedAt: null, + withdrawnAt: null, + }, }, - ...(subcommonId && - to === ALLOCATE_FUNDS_TO.subcommon && { subcommonId }), - ...(otherMemberId && - to === ALLOCATE_FUNDS_TO.otherMember && { otherMemberId }), + type: PROPOSALS.FUNDS_ALLOCATION, + global, + local, + limitations, + resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, }, - legal: { - payoutDocs: [], - payoutDocsUserComment: null, - totalInvoicesAmount: null, - payoutDocsRejectionReason: null, - }, - tracker: { - status: FUNDING_ALLOCATION_STATUS.PENDING_PROPOSAL_APPROVAL, - invoicesNotUploadedNotificationsCounter: 0, - trusteeApprovedAt: null, - withdrawnAt: null, - }, - }, - type: PROPOSALS.FUNDS_ALLOCATION, - global, - local, - limitations, - resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, - })) as FundsAllocation; + dbConfig + )) as FundsAllocation; + }); await createEvent({ userId: proposerId, diff --git a/functions/src/proposals/services/proposals/createMemberAdmittance.ts b/functions/src/proposals/services/proposals/createMemberAdmittance.ts index 4848db139..8e8b477b6 100644 --- a/functions/src/proposals/services/proposals/createMemberAdmittance.ts +++ b/functions/src/proposals/services/proposals/createMemberAdmittance.ts @@ -16,7 +16,13 @@ import { import { MemberAdmittanceArgs } from "../../../shared/schemas"; import { ArgumentError, CommonError } from "../../../util/errors"; import { isCommonMember } from "../../../commons/business"; -import { createEvent, genericWhere } from "../../../util/db"; +import { + createEvent, + DbOperationConfig, + DbOperationType, + genericWhere, + runManagerTransaction, +} from "../../../util/db"; import { governanceDb } from "../../../governance/db"; import { cardDb } from "../../../payments/db/cards"; import { db } from "../../../settings"; @@ -110,42 +116,51 @@ export const createMemberAdmittance = async (payload: MemberAdmittanceArgs) => { throw new CommonError("User doesnt have card", payload); } - const memberAdmittance = (await proposalsDb.add({ - data: { - votingExpiresOn: global.discussionDuration - ? null - : Timestamp.fromDate( - dayjs().add(global.votingDuration, "h").toDate() - ), - discussionExpiresOn: global.discussionDuration - ? Timestamp.fromDate( - dayjs().add(global.discussionDuration, "h").toDate() - ) - : null, - args: { - commonId, - proposerId, - title, - description, - images, - files, - links, - feeMonthly, - feeOneTime, - circleVisibilityByCommon: { - [commonId]: circleVisibility, + const memberAdmittance = await runManagerTransaction(async (manager) => { + const dbConfig: DbOperationConfig = { + manager, + type: DbOperationType.TransactionManager, + }; + return (await proposalsDb.add( + { + data: { + votingExpiresOn: global.discussionDuration + ? null + : Timestamp.fromDate( + dayjs().add(global.votingDuration, "h").toDate() + ), + discussionExpiresOn: global.discussionDuration + ? Timestamp.fromDate( + dayjs().add(global.discussionDuration, "h").toDate() + ) + : null, + args: { + commonId, + proposerId, + title, + description, + images, + files, + links, + feeMonthly, + feeOneTime, + circleVisibilityByCommon: { + [commonId]: circleVisibility, + }, + }, }, + local: { + defaultCircle, + optimisticAdmittance, + }, + global, + limitations, + type: PROPOSALS.MEMBER_ADMITTANCE, + resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, }, - }, - local: { - defaultCircle, - optimisticAdmittance, - }, - global, - limitations, - type: PROPOSALS.MEMBER_ADMITTANCE, - resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, - })) as MemberAdmittance; + dbConfig + )) as MemberAdmittance; + }); await createEvent({ userId: proposerId, diff --git a/functions/src/proposals/services/proposals/createRemoveCircle.ts b/functions/src/proposals/services/proposals/createRemoveCircle.ts index 0d00059f1..d87ad3a0e 100644 --- a/functions/src/proposals/services/proposals/createRemoveCircle.ts +++ b/functions/src/proposals/services/proposals/createRemoveCircle.ts @@ -1,7 +1,12 @@ import { Timestamp } from "firebase-admin/firestore"; import dayjs from "dayjs"; import { EVENT_TYPES, PROPOSALS } from "../../../shared/enums"; -import { createEvent } from "../../../util/db"; +import { + createEvent, + DbOperationConfig, + DbOperationType, + runManagerTransaction, +} from "../../../util/db"; import { proposalsDb } from "../../db/proposals"; import { governanceDb } from "../../../governance/db"; import { RemoveCircleArgs } from "../../../shared/schemas"; @@ -66,39 +71,50 @@ export const createRemoveCircle = async (payload: RemoveCircleArgs) => { const { global, local, limitations } = proposalGovDefinition; - const RemoveCircle = (await proposalsDb.add({ - data: { - votingExpiresOn: global.discussionDuration - ? null - : Timestamp.fromDate( - dayjs().add(global.votingDuration, "h").toDate() - ), - discussionExpiresOn: global.discussionDuration - ? Timestamp.fromDate( - dayjs().add(global.discussionDuration, "h").toDate() - ) - : null, - args: { - commonId, - proposerId, - userId, - title, - description, - images, - files, - links, - circleId, - circleVisibilityByCommon: { - [commonId]: circleVisibility, + const RemoveCircle = await runManagerTransaction(async (manager) => { + const dbConfig: DbOperationConfig = { + manager, + type: DbOperationType.TransactionManager, + }; + return (await proposalsDb.add( + { + id: payload.id, + discussionId: payload.discussionId, + data: { + votingExpiresOn: global.discussionDuration + ? null + : Timestamp.fromDate( + dayjs().add(global.votingDuration, "h").toDate() + ), + discussionExpiresOn: global.discussionDuration + ? Timestamp.fromDate( + dayjs().add(global.discussionDuration, "h").toDate() + ) + : null, + args: { + commonId, + proposerId, + userId, + title, + description, + images, + files, + links, + circleId, + circleVisibilityByCommon: { + [commonId]: circleVisibility, + }, + }, }, + type: PROPOSALS.REMOVE_CIRCLE, + global, + local, + limitations, + resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, }, - }, - type: PROPOSALS.REMOVE_CIRCLE, - global, - local, - limitations, - resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, - })) as RemoveCircle; + dbConfig + )) as RemoveCircle; + }); await createEvent({ userId: proposerId, diff --git a/functions/src/proposals/services/proposals/createSurvey.ts b/functions/src/proposals/services/proposals/createSurvey.ts index b9cd5cad0..97357bef9 100644 --- a/functions/src/proposals/services/proposals/createSurvey.ts +++ b/functions/src/proposals/services/proposals/createSurvey.ts @@ -3,7 +3,12 @@ import dayjs from "dayjs"; import { proposalsDb } from "../../db/proposals"; import { EVENT_TYPES, PROPOSALS } from "../../../shared/enums"; import { SurveyArgs } from "../../../shared/schemas"; -import { createEvent } from "../../../util/db"; +import { + createEvent, + DbOperationConfig, + DbOperationType, + runManagerTransaction, +} from "../../../util/db"; import { governanceDb } from "../../../governance/db"; import { ProposalResolutionType, Survey } from "../../../shared/interfaces"; import { CommonError } from "../../../util/errors"; @@ -29,37 +34,48 @@ export const createSurvey = async (payload: SurveyArgs) => { SURVEY: { global, local, limitations }, }, } = governance; - const survey = (await proposalsDb.add({ - data: { - votingExpiresOn: global.discussionDuration - ? null - : Timestamp.fromDate( - dayjs().add(global.votingDuration, "h").toDate() - ), - discussionExpiresOn: global.discussionDuration - ? Timestamp.fromDate( - dayjs().add(global.discussionDuration, "h").toDate() - ) - : null, - args: { - commonId, - proposerId, - title, - description, - images, - files, - links, - circleVisibilityByCommon: { - [commonId]: circleVisibility, + const survey = await runManagerTransaction(async (manager) => { + const dbConfig: DbOperationConfig = { + manager, + type: DbOperationType.TransactionManager, + }; + return (await proposalsDb.add( + { + id: payload.id, + discussionId: payload.discussionId, + data: { + votingExpiresOn: global.discussionDuration + ? null + : Timestamp.fromDate( + dayjs().add(global.votingDuration, "h").toDate() + ), + discussionExpiresOn: global.discussionDuration + ? Timestamp.fromDate( + dayjs().add(global.discussionDuration, "h").toDate() + ) + : null, + args: { + commonId, + proposerId, + title, + description, + images, + files, + links, + circleVisibilityByCommon: { + [commonId]: circleVisibility, + }, + }, }, + local, + limitations, + global, + type: PROPOSALS.SURVEY, + resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, }, - }, - local, - limitations, - global, - type: PROPOSALS.SURVEY, - resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, - })) as Survey; + dbConfig + )) as Survey; + }); await createEvent({ userId: proposerId, diff --git a/functions/src/proposals/services/proposals/deleteCommon.ts b/functions/src/proposals/services/proposals/deleteCommon.ts index b436ee615..4e8cb05ca 100644 --- a/functions/src/proposals/services/proposals/deleteCommon.ts +++ b/functions/src/proposals/services/proposals/deleteCommon.ts @@ -4,7 +4,13 @@ import { proposalsDb } from "../../db/proposals"; import { ProposalsCollection } from "../../db/proposals/ProposalsCollection"; import { EVENT_TYPES, PROPOSALS, PROPOSAL_STATE } from "../../../shared/enums"; import { DeleteCommonArgs } from "../../../shared/schemas"; -import { createEvent, genericWhere } from "../../../util/db"; +import { + createEvent, + DbOperationConfig, + DbOperationType, + genericWhere, + runManagerTransaction, +} from "../../../util/db"; import { governanceDb } from "../../../governance/db"; import { DeleteCommon, @@ -52,37 +58,48 @@ export const deleteCommon = async (payload: DeleteCommonArgs) => { DELETE_COMMON: { global, local, limitations }, }, } = governance; - const deleteCommon = (await proposalsDb.add({ - data: { - votingExpiresOn: global.discussionDuration - ? null - : Timestamp.fromDate( - dayjs().add(global.votingDuration, "h").toDate() - ), - discussionExpiresOn: global.discussionDuration - ? Timestamp.fromDate( - dayjs().add(global.discussionDuration, "h").toDate() - ) - : null, - args: { - commonId, - proposerId, - title, - description, - images, - files, - links, - circleVisibilityByCommon: { - [commonId]: circleVisibility, + const deleteCommon = await runManagerTransaction(async (manager) => { + const dbConfig: DbOperationConfig = { + manager, + type: DbOperationType.TransactionManager, + }; + return (await proposalsDb.add( + { + id: payload.id, + discussionId: payload.discussionId, + data: { + votingExpiresOn: global.discussionDuration + ? null + : Timestamp.fromDate( + dayjs().add(global.votingDuration, "h").toDate() + ), + discussionExpiresOn: global.discussionDuration + ? Timestamp.fromDate( + dayjs().add(global.discussionDuration, "h").toDate() + ) + : null, + args: { + commonId, + proposerId, + title, + description, + images, + files, + links, + circleVisibilityByCommon: { + [commonId]: circleVisibility, + }, + }, }, + local, + limitations, + global, + type: PROPOSALS.DELETE_COMMON, + resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, }, - }, - local, - limitations, - global, - type: PROPOSALS.DELETE_COMMON, - resolutionType: ProposalResolutionType.WAIT_FOR_EXPIRATION, - })) as DeleteCommon; + dbConfig + )) as DeleteCommon; + }); await createEvent({ userId: proposerId, diff --git a/functions/src/queue/queues/notionPages/jobs/syncNotionPage.ts b/functions/src/queue/queues/notionPages/jobs/syncNotionPage.ts index 005d3386a..bd6377528 100644 --- a/functions/src/queue/queues/notionPages/jobs/syncNotionPage.ts +++ b/functions/src/queue/queues/notionPages/jobs/syncNotionPage.ts @@ -5,6 +5,7 @@ import { notionPageDb } from "../../../../notion/db/notionPage"; import { Client, isFullPage } from "@notionhq/client"; import { RichText as CommonRichText } from "../../../../discussion/business/discussionMessage/utils/textExtractor"; import { + AnyTransactionOperationConfig, DbOperationConfig, DbOperationType, getAppContext, @@ -213,13 +214,19 @@ export async function syncNotionPageJobHandle(job: Job) { const completedPages = await redisClient.incr( `${QueuesEnum.NOTION_API}_extra:syncNotionDb_${job.parent.id}:completedPages` ); - const parentJob = await Job.fromId(queuesManager.notionApiQueue, job.parent.id); + const parentJob = await Job.fromId( + queuesManager.notionApiQueue, + job.parent.id + ); if (parentJob) { const percentComplete = Math.round( - (Math.min(completedPages, parentJob.data.totalPages) - / parentJob.data.totalPages) * 100 + (Math.min(completedPages, parentJob.data.totalPages) / + parentJob.data.totalPages) * + 100 + ); + parentJob.updateProgress( + percentComplete === 100 ? 99 : percentComplete ); - parentJob.updateProgress(percentComplete === 100 ? 99 : percentComplete); } } catch (err) { console.warn("Failed to update syncNotionPage parent job progress", err); @@ -237,7 +244,7 @@ async function createDiscussionFromPage( page: NotionPage; notion: Client; }, - dbConfig: DbOperationConfig + dbConfig: AnyTransactionOperationConfig ) { const governance = await governanceDb.get( { commonId: common.id }, diff --git a/functions/src/shared/interfaces/CreateDiscussionPayload.ts b/functions/src/shared/interfaces/CreateDiscussionPayload.ts index 49962a3a0..e69746cbd 100644 --- a/functions/src/shared/interfaces/CreateDiscussionPayload.ts +++ b/functions/src/shared/interfaces/CreateDiscussionPayload.ts @@ -3,6 +3,8 @@ import { File, Link } from "../schemas"; import { Discussion } from "./discussions"; export interface CreateDiscussionPayload { + id?: string; + title: string; message: string; diff --git a/functions/src/shared/schemas/createDiscussion.ts b/functions/src/shared/schemas/createDiscussion.ts index 56f4e42ee..e22286d9a 100644 --- a/functions/src/shared/schemas/createDiscussion.ts +++ b/functions/src/shared/schemas/createDiscussion.ts @@ -5,6 +5,8 @@ import { link } from "./link"; import { file } from "./file"; export const createDiscussionDataValidationScheme = object({ + id: string().optional(), + title: string().required(), message: string().required(), diff --git a/functions/src/shared/schemas/governance/basicArgsProposal.ts b/functions/src/shared/schemas/governance/basicArgsProposal.ts index 3651406b0..d6fef7c46 100644 --- a/functions/src/shared/schemas/governance/basicArgsProposal.ts +++ b/functions/src/shared/schemas/governance/basicArgsProposal.ts @@ -3,6 +3,9 @@ import { link } from "../link"; import { file } from "../file"; export const basicArgsProposal = object({ + id: string().uuid().optional().default(undefined), + discussionId: string().uuid().optional().default(undefined), + commonId: string().uuid().required(), proposerId: string().required(), diff --git a/functions/src/shared/schemas/index.ts b/functions/src/shared/schemas/index.ts index 265389e0a..c1fb67680 100644 --- a/functions/src/shared/schemas/index.ts +++ b/functions/src/shared/schemas/index.ts @@ -45,3 +45,5 @@ export * from "./removeNotionIntegration"; export * from "./setupNotionIntegration"; export * from "./discussionMessageReaction"; export * from "./chatMessageReaction"; +export * from "./markCommonSeenForUser"; +export * from "./markCommonUnseenForUser"; diff --git a/functions/src/shared/schemas/markCommonSeenForUser.ts b/functions/src/shared/schemas/markCommonSeenForUser.ts new file mode 100644 index 000000000..99ada4592 --- /dev/null +++ b/functions/src/shared/schemas/markCommonSeenForUser.ts @@ -0,0 +1,12 @@ +import { object, string } from "yup"; + +export const markCommonSeenForUserSchema = object({ + commonId: string().uuid().required(), + + userId: string().required(), +}).noUnknown(true); + +export type MarkCommonSeenForUser = { + commonId: string; + userId: string; +}; diff --git a/functions/src/shared/schemas/markCommonUnseenForUser.ts b/functions/src/shared/schemas/markCommonUnseenForUser.ts new file mode 100644 index 000000000..a16070d47 --- /dev/null +++ b/functions/src/shared/schemas/markCommonUnseenForUser.ts @@ -0,0 +1,12 @@ +import { object, string } from "yup"; + +export const markCommonUnseenForUserSchema = object({ + commonId: string().uuid().required(), + + userId: string().required(), +}).noUnknown(true); + +export type MarkCommonUnseenForUser = { + commonId: string; + userId: string; +};