From 4bbaf115022536f54c7f1c39623429984acede8c Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Tue, 10 May 2022 13:50:41 -0400 Subject: [PATCH 001/168] Move username logs to private --- DatabaseSchema.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/DatabaseSchema.md b/DatabaseSchema.md index 68daf48..3e077ae 100644 --- a/DatabaseSchema.md +++ b/DatabaseSchema.md @@ -3,7 +3,6 @@ [vipUsers](#vipUsers) [sponsorTimes](#sponsorTimes) [userNames](#userNames) -[userNameLogs](#userNameLogs) [categoryVotes](#categoryVotes) [lockCategories](#lockCategories) [warnings](#warnings) @@ -66,16 +65,6 @@ | -- | :--: | | userNames_userID | userID | -### userNameLogs - -| Name | Type | | -| -- | :--: | -- | -| userID | TEXT | not null | -| newUserName | TEXT | not null | -| oldUserName | TEXT | not null | -| updatedByAdmin | BOOLEAN | not null | -| updatedAt | INTEGER | not null | - ### categoryVotes | Name | Type | | @@ -209,6 +198,7 @@ [sponsorTimes](#sponsorTimes) [config](#config) [tempVipLog](#tempVipLog) +[userNameLogs](#userNameLogs) ### vote @@ -279,4 +269,14 @@ | issuerUserID | TEXT | not null | | targetUserID | TEXT | not null | | enabled | BOOLEAN | not null | -| updatedAt | INTEGER | not null | \ No newline at end of file +| updatedAt | INTEGER | not null | + +### userNameLogs + +| Name | Type | | +| -- | :--: | -- | +| userID | TEXT | not null | +| newUserName | TEXT | not null | +| oldUserName | TEXT | not null | +| updatedByAdmin | BOOLEAN | not null | +| updatedAt | INTEGER | not null | From 2eb53015bc5df92591c1ca1f336481189e8381e2 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Tue, 10 May 2022 14:04:25 -0400 Subject: [PATCH 002/168] fix table name --- DatabaseSchema.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DatabaseSchema.md b/DatabaseSchema.md index 3e077ae..4a83d19 100644 --- a/DatabaseSchema.md +++ b/DatabaseSchema.md @@ -193,14 +193,14 @@ # Private -[vote](#vote) +[votes](#votes) [categoryVotes](#categoryVotes) [sponsorTimes](#sponsorTimes) [config](#config) [tempVipLog](#tempVipLog) [userNameLogs](#userNameLogs) -### vote +### votes | Name | Type | | | -- | :--: | -- | From 3c340770567ecaf2f267e1f0c332ecb5a8918f9f Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Tue, 10 May 2022 14:05:30 -0400 Subject: [PATCH 003/168] Add missing link --- DatabaseSchema.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DatabaseSchema.md b/DatabaseSchema.md index 4a83d19..8952498 100644 --- a/DatabaseSchema.md +++ b/DatabaseSchema.md @@ -197,7 +197,8 @@ [categoryVotes](#categoryVotes) [sponsorTimes](#sponsorTimes) [config](#config) -[tempVipLog](#tempVipLog) +[ratings](#ratings) +[tempVipLog](#tempVipLog) [userNameLogs](#userNameLogs) ### votes From 72de52078185f25fe206bfa5cc90b9ee2dc4d676 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 10 May 2022 14:06:24 -0400 Subject: [PATCH 004/168] Add primary keys to tables --- databases/_upgrade_private_8.sql | 15 +++++++++++++++ databases/_upgrade_sponsorTimes_32.sql | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 databases/_upgrade_private_8.sql create mode 100644 databases/_upgrade_sponsorTimes_32.sql diff --git a/databases/_upgrade_private_8.sql b/databases/_upgrade_private_8.sql new file mode 100644 index 0000000..e6e71ef --- /dev/null +++ b/databases/_upgrade_private_8.sql @@ -0,0 +1,15 @@ +BEGIN TRANSACTION; + +-- Add primary keys + +ALTER TABLE "userNameLogs" ADD PRIMARY KEY ("userID"); +ALTER TABLE "categoryVotes" ADD PRIMARY KEY ("UUID", "userID", "category"); +ALTER TABLE "sponsorTimes" ADD "id" SERIAL PRIMARY KEY; +ALTER TABLE "config" ADD PRIMARY KEY ("key"); +ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; +ALTER TABLE "tempVipLog" ADD PRIMARY KEY ("issuerUserID", "targetUserID"); +ALTER TABLE "votes" ADD PRIMARY KEY ("UUID", "userID"); + +UPDATE "config" SET value = 8 WHERE key = 'version'; + +COMMIT; \ No newline at end of file diff --git a/databases/_upgrade_sponsorTimes_32.sql b/databases/_upgrade_sponsorTimes_32.sql new file mode 100644 index 0000000..ece9dce --- /dev/null +++ b/databases/_upgrade_sponsorTimes_32.sql @@ -0,0 +1,19 @@ +BEGIN TRANSACTION; + +-- Add primary keys + +ALTER TABLE "sponsorTimes" ADD PRIMARY KEY ("UUID"); +ALTER TABLE "vipUsers" ADD PRIMARY KEY ("userID"); +ALTER TABLE "userNames" ADD PRIMARY KEY ("userID"); +ALTER TABLE "categoryVotes" ADD PRIMARY KEY ("UUID", "category"); +ALTER TABLE "lockCategories" ADD "lockID" SERIAL PRIMARY KEY; +ALTER TABLE "warnings" ADD PRIMARY KEY ("userID", "issueTime"); +ALTER TABLE "shadowBannedUsers" ADD PRIMARY KEY ("userID"); +ALTER TABLE "unlistedVideos" ADD PRIMARY KEY ("videoID"); +ALTER TABLE "config" ADD PRIMARY KEY ("key"); +ALTER TABLE "archivedSponsorTimes" ADD PRIMARY KEY ("UUID"); +ALTER TABLE "ratings" ADD PRIMARY KEY ("videoID", "service", "type"); + +UPDATE "config" SET value = 32 WHERE key = 'version'; + +COMMIT; \ No newline at end of file From 34771e96fea04890bff6aea13eb1a78992ce5f3d Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 10 May 2022 14:20:07 -0400 Subject: [PATCH 005/168] change primary key for category votes --- databases/_upgrade_private_8.sql | 2 +- databases/_upgrade_sponsorTimes_32.sql | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/databases/_upgrade_private_8.sql b/databases/_upgrade_private_8.sql index e6e71ef..86d961f 100644 --- a/databases/_upgrade_private_8.sql +++ b/databases/_upgrade_private_8.sql @@ -3,7 +3,7 @@ BEGIN TRANSACTION; -- Add primary keys ALTER TABLE "userNameLogs" ADD PRIMARY KEY ("userID"); -ALTER TABLE "categoryVotes" ADD PRIMARY KEY ("UUID", "userID", "category"); +ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "sponsorTimes" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "config" ADD PRIMARY KEY ("key"); ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; diff --git a/databases/_upgrade_sponsorTimes_32.sql b/databases/_upgrade_sponsorTimes_32.sql index ece9dce..76c07ae 100644 --- a/databases/_upgrade_sponsorTimes_32.sql +++ b/databases/_upgrade_sponsorTimes_32.sql @@ -5,8 +5,8 @@ BEGIN TRANSACTION; ALTER TABLE "sponsorTimes" ADD PRIMARY KEY ("UUID"); ALTER TABLE "vipUsers" ADD PRIMARY KEY ("userID"); ALTER TABLE "userNames" ADD PRIMARY KEY ("userID"); -ALTER TABLE "categoryVotes" ADD PRIMARY KEY ("UUID", "category"); -ALTER TABLE "lockCategories" ADD "lockID" SERIAL PRIMARY KEY; +ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; +ALTER TABLE "lockCategories" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "warnings" ADD PRIMARY KEY ("userID", "issueTime"); ALTER TABLE "shadowBannedUsers" ADD PRIMARY KEY ("userID"); ALTER TABLE "unlistedVideos" ADD PRIMARY KEY ("videoID"); From dbfc685bf9c7ac2d9c32643b4a0903444216dfdf Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 10 May 2022 14:21:44 -0400 Subject: [PATCH 006/168] fix another impossible key --- databases/_upgrade_sponsorTimes_32.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/_upgrade_sponsorTimes_32.sql b/databases/_upgrade_sponsorTimes_32.sql index 76c07ae..66bd479 100644 --- a/databases/_upgrade_sponsorTimes_32.sql +++ b/databases/_upgrade_sponsorTimes_32.sql @@ -9,7 +9,7 @@ ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "lockCategories" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "warnings" ADD PRIMARY KEY ("userID", "issueTime"); ALTER TABLE "shadowBannedUsers" ADD PRIMARY KEY ("userID"); -ALTER TABLE "unlistedVideos" ADD PRIMARY KEY ("videoID"); +ALTER TABLE "unlistedVideos" ADD PRIMARY KEY ("videoID", "timeSubmitted"); ALTER TABLE "config" ADD PRIMARY KEY ("key"); ALTER TABLE "archivedSponsorTimes" ADD PRIMARY KEY ("UUID"); ALTER TABLE "ratings" ADD PRIMARY KEY ("videoID", "service", "type"); From f56fbbd2c70ff7640e34ff28b2ac32636cf1dad5 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 10 May 2022 14:22:42 -0400 Subject: [PATCH 007/168] still impossible --- databases/_upgrade_sponsorTimes_32.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/_upgrade_sponsorTimes_32.sql b/databases/_upgrade_sponsorTimes_32.sql index 66bd479..c58931d 100644 --- a/databases/_upgrade_sponsorTimes_32.sql +++ b/databases/_upgrade_sponsorTimes_32.sql @@ -9,7 +9,7 @@ ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "lockCategories" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "warnings" ADD PRIMARY KEY ("userID", "issueTime"); ALTER TABLE "shadowBannedUsers" ADD PRIMARY KEY ("userID"); -ALTER TABLE "unlistedVideos" ADD PRIMARY KEY ("videoID", "timeSubmitted"); +ALTER TABLE "unlistedVideos" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "config" ADD PRIMARY KEY ("key"); ALTER TABLE "archivedSponsorTimes" ADD PRIMARY KEY ("UUID"); ALTER TABLE "ratings" ADD PRIMARY KEY ("videoID", "service", "type"); From 3931328b605d23695ff479ffb5660799452cba2a Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 10 May 2022 14:24:34 -0400 Subject: [PATCH 008/168] more duplicates --- databases/_upgrade_sponsorTimes_32.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/_upgrade_sponsorTimes_32.sql b/databases/_upgrade_sponsorTimes_32.sql index c58931d..50bfcea 100644 --- a/databases/_upgrade_sponsorTimes_32.sql +++ b/databases/_upgrade_sponsorTimes_32.sql @@ -12,7 +12,7 @@ ALTER TABLE "shadowBannedUsers" ADD PRIMARY KEY ("userID"); ALTER TABLE "unlistedVideos" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "config" ADD PRIMARY KEY ("key"); ALTER TABLE "archivedSponsorTimes" ADD PRIMARY KEY ("UUID"); -ALTER TABLE "ratings" ADD PRIMARY KEY ("videoID", "service", "type"); +ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; UPDATE "config" SET value = 32 WHERE key = 'version'; From 5889e9e557b02bc44ae7d2c7c8fee0569f502320 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 10 May 2022 14:25:47 -0400 Subject: [PATCH 009/168] another id --- databases/_upgrade_private_8.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/_upgrade_private_8.sql b/databases/_upgrade_private_8.sql index 86d961f..5e2b4fb 100644 --- a/databases/_upgrade_private_8.sql +++ b/databases/_upgrade_private_8.sql @@ -2,7 +2,7 @@ BEGIN TRANSACTION; -- Add primary keys -ALTER TABLE "userNameLogs" ADD PRIMARY KEY ("userID"); +ALTER TABLE "userNameLogs" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "sponsorTimes" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "config" ADD PRIMARY KEY ("key"); From 78acb4a76a66b34b266f60368aa94e734ce9fa2e Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 10 May 2022 14:27:43 -0400 Subject: [PATCH 010/168] am wrong again --- databases/_upgrade_private_8.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/_upgrade_private_8.sql b/databases/_upgrade_private_8.sql index 5e2b4fb..01881a6 100644 --- a/databases/_upgrade_private_8.sql +++ b/databases/_upgrade_private_8.sql @@ -7,7 +7,7 @@ ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "sponsorTimes" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "config" ADD PRIMARY KEY ("key"); ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; -ALTER TABLE "tempVipLog" ADD PRIMARY KEY ("issuerUserID", "targetUserID"); +ALTER TABLE "tempVipLog" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "votes" ADD PRIMARY KEY ("UUID", "userID"); UPDATE "config" SET value = 8 WHERE key = 'version'; From d67b9cdcc5183a3fdb2f2e354efb1f0cd0a3e809 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 10 May 2022 14:34:22 -0400 Subject: [PATCH 011/168] lots of anomolies --- databases/_upgrade_private_8.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/_upgrade_private_8.sql b/databases/_upgrade_private_8.sql index 01881a6..d16a81b 100644 --- a/databases/_upgrade_private_8.sql +++ b/databases/_upgrade_private_8.sql @@ -8,7 +8,7 @@ ALTER TABLE "sponsorTimes" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "config" ADD PRIMARY KEY ("key"); ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; ALTER TABLE "tempVipLog" ADD "id" SERIAL PRIMARY KEY; -ALTER TABLE "votes" ADD PRIMARY KEY ("UUID", "userID"); +ALTER TABLE "votes" ADD "id" SERIAL PRIMARY KEY; UPDATE "config" SET value = 8 WHERE key = 'version'; From 634d5d083a5be747edbc7773e7d0518d9e991b34 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 14 May 2022 15:43:38 -0400 Subject: [PATCH 012/168] Better indexes --- databases/_sponsorTimes_indexes.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/databases/_sponsorTimes_indexes.sql b/databases/_sponsorTimes_indexes.sql index e604574..cd22b93 100644 --- a/databases/_sponsorTimes_indexes.sql +++ b/databases/_sponsorTimes_indexes.sql @@ -15,14 +15,14 @@ CREATE INDEX IF NOT EXISTS "sponsorTimes_UUID" ("UUID" COLLATE pg_catalog."default" ASC NULLS LAST) TABLESPACE pg_default; -CREATE INDEX IF NOT EXISTS "sponsorTimes_hashedVideoID_gin" - ON public."sponsorTimes" USING gin - ("hashedVideoID" COLLATE pg_catalog."default" gin_trgm_ops, category COLLATE pg_catalog."default" gin_trgm_ops) +CREATE INDEX IF NOT EXISTS "sponsorTimes_hashedVideoID" + ON public."sponsorTimes" USING btree + (service COLLATE pg_catalog."default" ASC NULLS LAST, "hashedVideoID" text_pattern_ops ASC NULLS LAST, "startTime" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID" ON public."sponsorTimes" USING btree - ("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST, category COLLATE pg_catalog."default" ASC NULLS LAST, "timeSubmitted" ASC NULLS LAST) + (service COLLATE pg_catalog."default" ASC NULLS LAST, "videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "startTime" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX IF NOT EXISTS "sponsorTimes_description_gin" From 901a42d1b4ebe2243ea7abf73512f7c29b59b92b Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 14 May 2022 15:49:08 -0400 Subject: [PATCH 013/168] update docs with index --- DatabaseSchema.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatabaseSchema.md b/DatabaseSchema.md index 8952498..957edb1 100644 --- a/DatabaseSchema.md +++ b/DatabaseSchema.md @@ -50,7 +50,7 @@ | sponsorTime_timeSubmitted | timeSubmitted | | sponsorTime_userID | userID | | sponsorTimes_UUID | UUID | -| sponsorTimes_hashedVideoID_gin| hashedVideoID, category | +| sponsorTimes_hashedVideoID | hashedVideoID, category | | sponsorTimes_videoID | videoID, service, category, timeSubmitted | ### userNames From dfbc32617b199269b4f827da357ef01b57e676dc Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 17 May 2022 01:29:49 -0400 Subject: [PATCH 014/168] Create an image for db backups --- .github/workflows/sb-server.yml | 10 +++++++++- containers/backup-db/Dockerfile | 13 +++++++++++++ containers/backup-db/backup.sh | 6 ++++++ containers/backup-db/forget.sh | 1 + {rsync => containers/rsync}/Dockerfile | 0 5 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 containers/backup-db/Dockerfile create mode 100644 containers/backup-db/backup.sh create mode 100644 containers/backup-db/forget.sh rename {rsync => containers/rsync}/Dockerfile (100%) diff --git a/.github/workflows/sb-server.yml b/.github/workflows/sb-server.yml index 1e46555..89c60f8 100644 --- a/.github/workflows/sb-server.yml +++ b/.github/workflows/sb-server.yml @@ -20,6 +20,14 @@ jobs: with: name: "rsync-host" username: "ajayyy" - folder: "./rsync" + folder: "./containers/rsync" + secrets: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + backup-db: + uses: ./.github/workflows/docker-build.yml + with: + name: "db-backup" + username: "ajayyy" + folder: "./containers/backup-db" secrets: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/containers/backup-db/Dockerfile b/containers/backup-db/Dockerfile new file mode 100644 index 0000000..be2d7a3 --- /dev/null +++ b/containers/backup-db/Dockerfile @@ -0,0 +1,13 @@ +FROM alpine +RUN apk add postgresql-client +RUN apk add restic --repository http://dl-cdn.alpinelinux.org/alpine/latest-stable/community/ + +COPY ./backup.sh /usr/src/app/backup.sh +RUN chmod +x /usr/src/app/backup.sh +COPY ./backup.sh /usr/src/app/forget.sh +RUN chmod +x /usr/src/app/forget.sh + +RUN echo '30 * * * * /usr/src/app/backup.sh' >> /etc/crontabs/root +RUN echo '10 0 * * 1 /usr/src/app/forget.sh' >> /etc/crontabs/root + +CMD crond -l 2 -f \ No newline at end of file diff --git a/containers/backup-db/backup.sh b/containers/backup-db/backup.sh new file mode 100644 index 0000000..cc847e5 --- /dev/null +++ b/containers/backup-db/backup.sh @@ -0,0 +1,6 @@ +mkdir ./dump + +pg_dump -f ./dump/sponsorTimes.dump sponsorTimes +pg_dump -f ./dump/privateDB.dump privateDB + +restic backup ./dump \ No newline at end of file diff --git a/containers/backup-db/forget.sh b/containers/backup-db/forget.sh new file mode 100644 index 0000000..eaa6165 --- /dev/null +++ b/containers/backup-db/forget.sh @@ -0,0 +1 @@ +restic forget --prune --keep-last 48 --keep-daily 7 --keep-weekly 8 \ No newline at end of file diff --git a/rsync/Dockerfile b/containers/rsync/Dockerfile similarity index 100% rename from rsync/Dockerfile rename to containers/rsync/Dockerfile From e79a8417f43742a08aaa353a63931b8e2804c28e Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 17 May 2022 01:51:11 -0400 Subject: [PATCH 015/168] Don't always build backup db image --- .github/workflows/db-backup.yml | 18 ++++++++++++++++++ .github/workflows/sb-server.yml | 8 -------- 2 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/db-backup.yml diff --git a/.github/workflows/db-backup.yml b/.github/workflows/db-backup.yml new file mode 100644 index 0000000..49cf702 --- /dev/null +++ b/.github/workflows/db-backup.yml @@ -0,0 +1,18 @@ +name: Docker image builds +on: + push: + branches: + - master + paths: + - containers/backup-db/** + workflow_dispatch: + +jobs: + backup-db: + uses: ./.github/workflows/docker-build.yml + with: + name: "db-backup" + username: "ajayyy" + folder: "./containers/backup-db" + secrets: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/sb-server.yml b/.github/workflows/sb-server.yml index 89c60f8..51179dc 100644 --- a/.github/workflows/sb-server.yml +++ b/.github/workflows/sb-server.yml @@ -21,13 +21,5 @@ jobs: name: "rsync-host" username: "ajayyy" folder: "./containers/rsync" - secrets: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - backup-db: - uses: ./.github/workflows/docker-build.yml - with: - name: "db-backup" - username: "ajayyy" - folder: "./containers/backup-db" secrets: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From c9a0fb7bc3b099172ea3294ef6b738deac70fa88 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 17 May 2022 02:29:36 -0400 Subject: [PATCH 016/168] Add disk cache service Fixes #471 --- package-lock.json | 288 ++++---------------------------------- package.json | 1 - src/config.ts | 4 +- src/types/config.model.ts | 3 +- src/utils/diskCache.ts | 55 ++++---- src/utils/youtubeApi.ts | 4 +- 6 files changed, 62 insertions(+), 293 deletions(-) diff --git a/package-lock.json b/package-lock.json index ebf1447..2ac17bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "@ajayyy/lru-diskcache": "^2.0.0", "axios": "^0.24.0", "better-sqlite3": "^7.4.5", "cron": "^1.8.2", @@ -44,19 +43,6 @@ "node": ">=10" } }, - "node_modules/@ajayyy/lru-diskcache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ajayyy/lru-diskcache/-/lru-diskcache-2.0.0.tgz", - "integrity": "sha512-5QvGkQTgzpgqF/XHTxjqm1SXSkiUMT+wDZOthc7Xk35ejr8+ByVqwjtDfcm6vhQ6J++PONAxqTz4Hi6JpdBYMQ==", - "dependencies": { - "fs-extra": "^0.26.5", - "fswrite-stream": "^1.0.0", - "lodash": "^4.17.10", - "lru-cache": "^4.1.5", - "q": "^1.4.1", - "tmp": "^0.0.28" - } - }, "node_modules/@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -809,7 +795,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/barrage": { "version": "1.1.0", @@ -997,6 +984,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1291,7 +1279,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "node_modules/concat-stream": { "version": "1.6.2", @@ -2150,29 +2139,6 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, - "node_modules/fs-extra": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", - "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "node_modules/fs-extra/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -2187,7 +2153,8 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "node_modules/fsevents": { "version": "2.3.2", @@ -2203,14 +2170,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/fswrite-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fswrite-stream/-/fswrite-stream-1.0.0.tgz", - "integrity": "sha1-esPP5gkt3/mYQ1Z1UCOJ5lKjb64=", - "dependencies": { - "length-stream": "^0.1.1" - } - }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -2297,6 +2256,7 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2429,7 +2389,8 @@ "node_modules/graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true }, "node_modules/growl": { "version": "1.10.5", @@ -2576,6 +2537,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2789,14 +2751,6 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "node_modules/jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -2812,14 +2766,6 @@ "json-buffer": "3.0.0" } }, - "node_modules/klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "optionalDependencies": { - "graceful-fs": "^4.1.9" - } - }, "node_modules/latest-version": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", @@ -2832,17 +2778,6 @@ "node": ">=8" } }, - "node_modules/length-stream": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/length-stream/-/length-stream-0.1.1.tgz", - "integrity": "sha1-5ySxvi46lh1MQxNDfkllaRD65tY=", - "dependencies": { - "pass-stream": "~0.1.0" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2918,15 +2853,6 @@ "node": ">=0.10.0" } }, - "node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -3045,6 +2971,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3489,14 +3416,6 @@ "node": ">= 0.8.0" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -3585,17 +3504,6 @@ "node": ">= 0.8" } }, - "node_modules/pass-stream": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/pass-stream/-/pass-stream-0.1.5.tgz", - "integrity": "sha1-njr6TVglzdE3YHW9tW3jbfszZJ8=", - "dependencies": { - "readable-stream": "https://github.com/jeffbski/readable-stream/archive/v1.0.2-object-transform2-ret-self.tar.gz" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3609,6 +3517,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -3835,11 +3744,6 @@ "node": ">= 0.10" } }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -3876,15 +3780,6 @@ "node": ">=8" } }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, "node_modules/qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -3977,12 +3872,6 @@ "node": ">=0.10.0" } }, - "node_modules/readable-stream": { - "version": "1.0.2", - "resolved": "https://github.com/jeffbski/readable-stream/archive/v1.0.2-object-transform2-ret-self.tar.gz", - "integrity": "sha512-4DfqsAX+1OToUZ/uHLsC3NUHN7wCn2BNfkG/Eyr2ii4c4suSipjLFbVymS7pj49+aRcSutnK9dIuHS3X7fwApQ==", - "license": "BSD" - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4587,17 +4476,6 @@ "promise": "^7.1.1" } }, - "node_modules/tmp": { - "version": "0.0.28", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", - "integrity": "sha1-Fyc1t/YU6nrzlmT6hM8N5OUV0SA=", - "dependencies": { - "os-tmpdir": "~1.0.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -5061,11 +4939,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -5154,19 +5027,6 @@ } }, "dependencies": { - "@ajayyy/lru-diskcache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@ajayyy/lru-diskcache/-/lru-diskcache-2.0.0.tgz", - "integrity": "sha512-5QvGkQTgzpgqF/XHTxjqm1SXSkiUMT+wDZOthc7Xk35ejr8+ByVqwjtDfcm6vhQ6J++PONAxqTz4Hi6JpdBYMQ==", - "requires": { - "fs-extra": "^0.26.5", - "fswrite-stream": "^1.0.0", - "lodash": "^4.17.10", - "lru-cache": "^4.1.5", - "q": "^1.4.1", - "tmp": "^0.0.28" - } - }, "@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -5768,7 +5628,8 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "barrage": { "version": "1.1.0", @@ -5925,6 +5786,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6142,7 +6004,8 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "concat-stream": { "version": "1.6.2", @@ -6802,28 +6665,6 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, - "fs-extra": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", - "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - } - } - }, "fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -6835,7 +6676,8 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "2.3.2", @@ -6844,14 +6686,6 @@ "dev": true, "optional": true }, - "fswrite-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fswrite-stream/-/fswrite-stream-1.0.0.tgz", - "integrity": "sha1-esPP5gkt3/mYQ1Z1UCOJ5lKjb64=", - "requires": { - "length-stream": "^0.1.1" - } - }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -6922,6 +6756,7 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7019,7 +6854,8 @@ "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==" + "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", + "dev": true }, "growl": { "version": "1.10.5", @@ -7119,6 +6955,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -7281,14 +7118,6 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, "just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -7304,14 +7133,6 @@ "json-buffer": "3.0.0" } }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "requires": { - "graceful-fs": "^4.1.9" - } - }, "latest-version": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", @@ -7321,14 +7142,6 @@ "package-json": "^6.3.0" } }, - "length-stream": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/length-stream/-/length-stream-0.1.1.tgz", - "integrity": "sha1-5ySxvi46lh1MQxNDfkllaRD65tY=", - "requires": { - "pass-stream": "~0.1.0" - } - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7386,15 +7199,6 @@ "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", "dev": true }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -7476,6 +7280,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7829,11 +7634,6 @@ "word-wrap": "^1.2.3" } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, "p-cancelable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", @@ -7897,14 +7697,6 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, - "pass-stream": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/pass-stream/-/pass-stream-0.1.5.tgz", - "integrity": "sha1-njr6TVglzdE3YHW9tW3jbfszZJ8=", - "requires": { - "readable-stream": "https://github.com/jeffbski/readable-stream/archive/v1.0.2-object-transform2-ret-self.tar.gz" - } - }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7914,7 +7706,8 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true }, "path-key": { "version": "3.1.1", @@ -8077,11 +7870,6 @@ "ipaddr.js": "1.9.1" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, "pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -8112,11 +7900,6 @@ "escape-goat": "^2.0.0" } }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", @@ -8177,10 +7960,6 @@ } } }, - "readable-stream": { - "version": "https://github.com/jeffbski/readable-stream/archive/v1.0.2-object-transform2-ret-self.tar.gz", - "integrity": "sha512-4DfqsAX+1OToUZ/uHLsC3NUHN7wCn2BNfkG/Eyr2ii4c4suSipjLFbVymS7pj49+aRcSutnK9dIuHS3X7fwApQ==" - }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -8651,14 +8430,6 @@ "promise": "^7.1.1" } }, - "tmp": { - "version": "0.0.28", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.28.tgz", - "integrity": "sha1-Fyc1t/YU6nrzlmT6hM8N5OUV0SA=", - "requires": { - "os-tmpdir": "~1.0.1" - } - }, "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -8998,11 +8769,6 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/package.json b/package.json index 4ddd8dc..1541c3c 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "author": "Ajay Ramachandran", "license": "MIT", "dependencies": { - "@ajayyy/lru-diskcache": "^2.0.0", "axios": "^0.24.0", "better-sqlite3": "^7.4.5", "cron": "^1.8.2", diff --git a/src/config.ts b/src/config.ts index acd473e..8013e81 100644 --- a/src/config.ts +++ b/src/config.ts @@ -112,9 +112,7 @@ addDefaults(config, { name: "ratings" }] }, - diskCache: { - max: 10737418240 - }, + diskCacheURL: null, crons: null, redis: { enabled: false, diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 2fe08f6..8258005 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -1,6 +1,5 @@ import { PoolConfig } from "pg"; import * as redis from "redis"; -import { CacheOptions } from "@ajayyy/lru-diskcache"; interface RedisConfig extends redis.RedisClientOptions { enabled: boolean; @@ -53,7 +52,7 @@ export interface SBSConfig { maxRewardTimePerSegmentInSeconds?: number; postgres?: CustomPostgresConfig; dumpDatabase?: DumpDatabase; - diskCache: CacheOptions; + diskCacheURL: string; crons: CronJobOptions; } diff --git a/src/utils/diskCache.ts b/src/utils/diskCache.ts index 29ac5d5..78ecd63 100644 --- a/src/utils/diskCache.ts +++ b/src/utils/diskCache.ts @@ -1,34 +1,41 @@ -import LRU from "@ajayyy/lru-diskcache"; +import axios, { AxiosError } from "axios"; import { config } from "../config"; +import { Logger } from "./logger"; -let DiskCache: LRU; +class DiskCache { + async set(key: string, value: unknown): Promise { + if (!config.diskCacheURL) return false; -if (config.diskCache) { - DiskCache = new LRU("./databases/cache", config.diskCache); - DiskCache.init(); -} else { - DiskCache = { - /* eslint-disable @typescript-eslint/no-unused-vars */ - // constructor(rootPath, options): {}; + try { + const result = await axios.post(`${config.diskCacheURL}/api/v1/item`, { + key, + value + }); - init(): void { return; }, + return result.status === 200; + } catch (err) { + const response = (err as AxiosError).response; + if (!response || response.status !== 404) { + Logger.error(`DiskCache: Error setting key ${key}: ${err}`); + } - reset(): void { return; }, + return false; + } + } - has(key: string): boolean { return false; }, + async get(key: string): Promise { + if (!config.diskCacheURL) return null; - get(key: string, opts?: {encoding?: string}): string { return null; }, + try { + const result = await axios.get(`${config.diskCacheURL}/api/v1/item?key=${key}`, { timeout: 500 }); - // Returns size - set(key: string, dataOrSteam: string): Promise { return new Promise(() => 0); }, - - del(key: string): void { return; }, - - size(): number { return 0; }, - - prune(): void {return; }, - /* eslint-enable @typescript-eslint/no-unused-vars */ - }; + return result.status === 200 ? result.data : null; + } catch (err) { + Logger.error(`DiskCache: Error getting key ${key}: ${err}`); + return null; + } + } } -export default DiskCache; \ No newline at end of file +const diskCache = new DiskCache(); +export default diskCache; \ No newline at end of file diff --git a/src/utils/youtubeApi.ts b/src/utils/youtubeApi.ts index ed6d674..5f3dfe7 100644 --- a/src/utils/youtubeApi.ts +++ b/src/utils/youtubeApi.ts @@ -17,7 +17,7 @@ export class YouTubeAPI { if (data) { Logger.debug(`YouTube API: cache used for video information: ${videoID}`); - return { err: null, data: JSON.parse(data) }; + return { err: null, data: data as APIVideoData }; } } catch (err) { return { err: err as string | boolean, data: null }; @@ -38,7 +38,7 @@ export class YouTubeAPI { return { err: data.error, data: null }; } const apiResult = data as APIVideoData; - DiskCache.set(cacheKey, JSON.stringify(apiResult)) + DiskCache.set(cacheKey, apiResult) .catch((err: any) => Logger.warn(err)) .then(() => Logger.debug(`YouTube API: video information cache set for: ${videoID}`)); From 21f7d5d9384b57bf38189d05de5004b3684b43d0 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 17 May 2022 12:53:48 -0400 Subject: [PATCH 017/168] Don't add primary keys with sqlite --- databases/_upgrade_private_8.sql | 14 +++++++------- databases/_upgrade_sponsorTimes_32.sql | 22 +++++++++++----------- src/databases/Sqlite.ts | 4 +--- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/databases/_upgrade_private_8.sql b/databases/_upgrade_private_8.sql index d16a81b..768facb 100644 --- a/databases/_upgrade_private_8.sql +++ b/databases/_upgrade_private_8.sql @@ -2,13 +2,13 @@ BEGIN TRANSACTION; -- Add primary keys -ALTER TABLE "userNameLogs" ADD "id" SERIAL PRIMARY KEY; -ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; -ALTER TABLE "sponsorTimes" ADD "id" SERIAL PRIMARY KEY; -ALTER TABLE "config" ADD PRIMARY KEY ("key"); -ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; -ALTER TABLE "tempVipLog" ADD "id" SERIAL PRIMARY KEY; -ALTER TABLE "votes" ADD "id" SERIAL PRIMARY KEY; +ALTER TABLE "userNameLogs" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore +ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore +ALTER TABLE "sponsorTimes" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore +ALTER TABLE "config" ADD PRIMARY KEY ("key"); --!sqlite-ignore +ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore +ALTER TABLE "tempVipLog" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore +ALTER TABLE "votes" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore UPDATE "config" SET value = 8 WHERE key = 'version'; diff --git a/databases/_upgrade_sponsorTimes_32.sql b/databases/_upgrade_sponsorTimes_32.sql index 50bfcea..ebba3e6 100644 --- a/databases/_upgrade_sponsorTimes_32.sql +++ b/databases/_upgrade_sponsorTimes_32.sql @@ -2,17 +2,17 @@ BEGIN TRANSACTION; -- Add primary keys -ALTER TABLE "sponsorTimes" ADD PRIMARY KEY ("UUID"); -ALTER TABLE "vipUsers" ADD PRIMARY KEY ("userID"); -ALTER TABLE "userNames" ADD PRIMARY KEY ("userID"); -ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; -ALTER TABLE "lockCategories" ADD "id" SERIAL PRIMARY KEY; -ALTER TABLE "warnings" ADD PRIMARY KEY ("userID", "issueTime"); -ALTER TABLE "shadowBannedUsers" ADD PRIMARY KEY ("userID"); -ALTER TABLE "unlistedVideos" ADD "id" SERIAL PRIMARY KEY; -ALTER TABLE "config" ADD PRIMARY KEY ("key"); -ALTER TABLE "archivedSponsorTimes" ADD PRIMARY KEY ("UUID"); -ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; +ALTER TABLE "sponsorTimes" ADD PRIMARY KEY ("UUID"); --!sqlite-ignore +ALTER TABLE "vipUsers" ADD PRIMARY KEY ("userID"); --!sqlite-ignore +ALTER TABLE "userNames" ADD PRIMARY KEY ("userID"); --!sqlite-ignore +ALTER TABLE "categoryVotes" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore +ALTER TABLE "lockCategories" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore +ALTER TABLE "warnings" ADD PRIMARY KEY ("userID", "issueTime"); --!sqlite-ignore +ALTER TABLE "shadowBannedUsers" ADD PRIMARY KEY ("userID"); --!sqlite-ignore +ALTER TABLE "unlistedVideos" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore +ALTER TABLE "config" ADD PRIMARY KEY ("key"); --!sqlite-ignore +ALTER TABLE "archivedSponsorTimes" ADD PRIMARY KEY ("UUID"); --!sqlite-ignore +ALTER TABLE "ratings" ADD "id" SERIAL PRIMARY KEY; --!sqlite-ignore UPDATE "config" SET value = 32 WHERE key = 'version'; diff --git a/src/databases/Sqlite.ts b/src/databases/Sqlite.ts index f396791..f5dd371 100644 --- a/src/databases/Sqlite.ts +++ b/src/databases/Sqlite.ts @@ -93,9 +93,7 @@ export class Sqlite implements IDatabase { } private static processUpgradeQuery(query: string): string { - const result = query.replace(/^.*--!sqlite-ignore/gm, ""); - - return result; + return query.replace(/^.*--!sqlite-ignore/gm, ""); } } From f520e00ed42d2be81a58b71f1a0ed41c227ed408 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 20 May 2022 04:53:31 -0400 Subject: [PATCH 018/168] Fix null values messing with env import --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index 8013e81..aa8ac5f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -165,7 +165,7 @@ function loadFromEnv(config: SBSConfig, prefix = "") { const fullKey = (prefix ? `${prefix}_` : "") + key; const data = config[key]; - if (typeof data === "object" && !Array.isArray(data)) { + if (data && typeof data === "object" && !Array.isArray(data)) { loadFromEnv(data, fullKey); } else if (process.env[fullKey]) { const value = process.env[fullKey]; From 29660d998be808b8bdc6acb94ca1cae6c395d306 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 20 May 2022 16:59:21 -0400 Subject: [PATCH 019/168] Don't count users for options requests --- src/middleware/userCounter.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/middleware/userCounter.ts b/src/middleware/userCounter.ts index a03ad96..958f67e 100644 --- a/src/middleware/userCounter.ts +++ b/src/middleware/userCounter.ts @@ -6,8 +6,10 @@ import { getHash } from "../utils/getHash"; import { NextFunction, Request, Response } from "express"; export function userCounter(req: Request, res: Response, next: NextFunction): void { - axios.post(`${config.userCounterURL}/api/v1/addIP?hashedIP=${getHash(getIP(req), 1)}`) - .catch(() => Logger.debug(`Failing to connect to user counter at: ${config.userCounterURL}`)); + if (req.method !== "OPTIONS") { + axios.post(`${config.userCounterURL}/api/v1/addIP?hashedIP=${getHash(getIP(req), 1)}`) + .catch(() => Logger.debug(`Failing to connect to user counter at: ${config.userCounterURL}`)); + } next(); } From ed221c8599dee7117126ca68f66008de6c6931e0 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 21 May 2022 00:39:39 -0400 Subject: [PATCH 020/168] Don't log 404 errors for disk cache --- src/utils/diskCache.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/utils/diskCache.ts b/src/utils/diskCache.ts index 78ecd63..c03c750 100644 --- a/src/utils/diskCache.ts +++ b/src/utils/diskCache.ts @@ -31,7 +31,11 @@ class DiskCache { return result.status === 200 ? result.data : null; } catch (err) { - Logger.error(`DiskCache: Error getting key ${key}: ${err}`); + const response = (err as AxiosError).response; + if (!response || response.status !== 404) { + Logger.error(`DiskCache: Error getting key ${key}: ${err}`); + } + return null; } } From 55ff3230ed42f1bcec4684c34cb434c53fc3bd73 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 23 May 2022 18:32:40 -0400 Subject: [PATCH 021/168] Catch redis exceptions --- src/middleware/requestRateLimit.ts | 5 +++-- src/utils/getHashCache.ts | 2 +- src/utils/queryCacher.ts | 14 +++++++------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/middleware/requestRateLimit.ts b/src/middleware/requestRateLimit.ts index 06fde41..853738e 100644 --- a/src/middleware/requestRateLimit.ts +++ b/src/middleware/requestRateLimit.ts @@ -6,9 +6,10 @@ import { RateLimitConfig } from "../types/config.model"; import { Request } from "express"; import { isUserVIP } from "../utils/isUserVIP"; import { UserID } from "../types/user.model"; -import RedisStore from "rate-limit-redis"; +import RedisStore, { RedisReply } from "rate-limit-redis"; import redis from "../utils/redis"; import { config } from "../config"; +import { Logger } from "../utils/logger"; export function rateLimitMiddleware(limitConfig: RateLimitConfig, getUserID?: (req: Request) => UserID): RateLimitRequestHandler { return rateLimit({ @@ -29,7 +30,7 @@ export function rateLimitMiddleware(limitConfig: RateLimitConfig, getUserID?: (r } }, store: config.redis?.enabled ? new RedisStore({ - sendCommand: (...args: string[]) => redis.sendCommand(args), + sendCommand: (...args: string[]) => redis.sendCommand(args).catch((err) => Logger.error(err)) as Promise, }) : null, }); } diff --git a/src/utils/getHashCache.ts b/src/utils/getHashCache.ts index f801545..b3d0d7b 100644 --- a/src/utils/getHashCache.ts +++ b/src/utils/getHashCache.ts @@ -30,7 +30,7 @@ async function getFromRedis(key: HashedValue): Promise Logger.error(err)); return data as T & HashedValue; } \ No newline at end of file diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index 028c4e7..434d898 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -16,7 +16,7 @@ async function get(fetchFromDB: () => Promise, key: string): Promise { const data = await fetchFromDB(); - redis.set(key, JSON.stringify(data)); + redis.set(key, JSON.stringify(data)).catch((err) => Logger.error(err)); return data; } @@ -67,7 +67,7 @@ async function getAndSplit(fetchFromDB: (values: U[]) => Pr } for (const key in newResults) { - redis.set(key, JSON.stringify(newResults[key])); + redis.set(key, JSON.stringify(newResults[key])).catch((err) => Logger.error(err)); } }); } @@ -77,16 +77,16 @@ async function getAndSplit(fetchFromDB: (values: U[]) => Pr function clearSegmentCache(videoInfo: { videoID: VideoID; hashedVideoID: VideoIDHash; service: Service; userID?: UserID; }): void { if (videoInfo) { - redis.del(skipSegmentsKey(videoInfo.videoID, videoInfo.service)); - redis.del(skipSegmentGroupsKey(videoInfo.videoID, videoInfo.service)); - redis.del(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service)); - if (videoInfo.userID) redis.del(reputationKey(videoInfo.userID)); + redis.del(skipSegmentsKey(videoInfo.videoID, videoInfo.service)).catch((err) => Logger.error(err)); + redis.del(skipSegmentGroupsKey(videoInfo.videoID, videoInfo.service)).catch((err) => Logger.error(err)); + redis.del(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err)); + if (videoInfo.userID) redis.del(reputationKey(videoInfo.userID)).catch((err) => Logger.error(err)); } } function clearRatingCache(videoInfo: { hashedVideoID: VideoIDHash; service: Service;}): void { if (videoInfo) { - redis.del(ratingHashKey(videoInfo.hashedVideoID, videoInfo.service)); + redis.del(ratingHashKey(videoInfo.hashedVideoID, videoInfo.service)).catch((err) => Logger.error(err)); } } From 043c8b771e9fbcc18cf2dd1f4bfcb36ad1d30bad Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 23 May 2022 20:15:44 -0400 Subject: [PATCH 022/168] Lower redis timeout --- src/utils/redis.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/redis.ts b/src/utils/redis.ts index e1bc2e9..7627d0f 100644 --- a/src/utils/redis.ts +++ b/src/utils/redis.ts @@ -31,7 +31,7 @@ if (config.redis?.enabled) { client.connect(); exportClient = client; - const timeoutDuration = 200; + const timeoutDuration = 40; const get = client.get.bind(client); exportClient.get = (key) => new Promise((resolve, reject) => { const timeout = setTimeout(() => reject(), timeoutDuration); From cbdd85256633bceb5d2b4b52768e29ced18268dc Mon Sep 17 00:00:00 2001 From: Michael C Date: Thu, 26 May 2022 21:17:58 -0400 Subject: [PATCH 023/168] add additional poi_highlight tests --- test/cases/lockCategoriesRecords.ts | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test/cases/lockCategoriesRecords.ts b/test/cases/lockCategoriesRecords.ts index ab96c05..7d6bd4a 100644 --- a/test/cases/lockCategoriesRecords.ts +++ b/test/cases/lockCategoriesRecords.ts @@ -54,6 +54,7 @@ describe("lockCategoriesRecords", () => { await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record-1", "mute", "sponsor", "reason-5", "YouTube"]); await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record-1", "skip", "intro", "reason-5", "YouTube"]); await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record-1", "mute", "intro", "reason-5", "YouTube"]); + await db.prepare("run", insertLockCategoryQuery, [lockVIPUserHash, "delete-record-poi", "poi", "poi_highlight", "reason-6", "YouTube"]); }); it("Should update the database version when starting the application", async () => { @@ -519,4 +520,44 @@ describe("lockCategoriesRecords", () => { }) .catch(err => done(err)); }); + + it("should be able to delete poi type category by type poi", (done) => { + const videoID = "delete-record-poi"; + const json = { + videoID, + userID: lockVIPUser, + categories: [ + "poi_highlight", + ], + actionTypes: ["poi"] + }; + client.delete(endpoint, { data: json }) + .then(async res => { + assert.strictEqual(res.status, 200); + const result = await checkLockCategories(videoID); + assert.strictEqual(result.length, 0); + done(); + }) + .catch(err => done(err)); + }); + + it("should be able to delete poi type category by type poi", (done) => { + const videoID = "delete-record-poi"; + const json = { + videoID, + userID: lockVIPUser, + categories: [ + "poi_highlight", + ], + actionTypes: ["poi"] + }; + client.delete(endpoint, { data: json }) + .then(async res => { + assert.strictEqual(res.status, 200); + const result = await checkLockCategories(videoID); + assert.strictEqual(result.length, 0); + done(); + }) + .catch(err => done(err)); + }); }); From b7995832bc4dfc68fc70fdb922a4c4d2e3ad59e1 Mon Sep 17 00:00:00 2001 From: Michael C Date: Thu, 26 May 2022 21:19:27 -0400 Subject: [PATCH 024/168] add b2 syncing for sqlite base --- .github/workflows/generate-sqlite-base.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/generate-sqlite-base.yml b/.github/workflows/generate-sqlite-base.yml index 53f1703..97c5cb3 100644 --- a/.github/workflows/generate-sqlite-base.yml +++ b/.github/workflows/generate-sqlite-base.yml @@ -25,4 +25,13 @@ jobs: - uses: actions/upload-artifact@v2 with: name: SponsorTimesDB.db - path: databases/sponsorTimes.db \ No newline at end of file + path: databases/sponsorTimes.db + - uses: mchangrh/s3cmd-sync@v1 + with: + args: --acl-public + env: + S3_ENDPOINT: ${{ secrets.S3_ENDPOINT }} + S3_BUCKET: ${{ secrets.S3_BUCKET }} + S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }} + S3_ACCESS_KEY_SECRET: ${{ secrets.S3_ACCESS_KEY_SECRET }} + SOURCE_DIR: 'databases/sponsorTimes.db' \ No newline at end of file From d2730955257a9ae83880d2b3b7f134d8bfe24dbf Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 26 May 2022 22:47:07 -0400 Subject: [PATCH 025/168] Add improved hashed ip index --- databases/_private_indexes.sql | 5 ++--- databases/_upgrade_private_9.sql | 9 +++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 databases/_upgrade_private_9.sql diff --git a/databases/_private_indexes.sql b/databases/_private_indexes.sql index 7187514..35d3bcc 100644 --- a/databases/_private_indexes.sql +++ b/databases/_private_indexes.sql @@ -1,9 +1,8 @@ -- sponsorTimes -CREATE INDEX IF NOT EXISTS "privateDB_sponsorTimes_v3" +CREATE INDEX IF NOT EXISTS "privateDB_sponsorTimes_v4" ON public."sponsorTimes" USING btree - ("hashedIP" COLLATE pg_catalog."default" ASC NULLS LAST, "videoID" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST) -; + ("videoID" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST, "timeSubmitted" ASC NULLS LAST); -- votes diff --git a/databases/_upgrade_private_9.sql b/databases/_upgrade_private_9.sql new file mode 100644 index 0000000..b4baaa9 --- /dev/null +++ b/databases/_upgrade_private_9.sql @@ -0,0 +1,9 @@ +BEGIN TRANSACTION; + +-- Add primary keys + +DROP INDEX "privateDB_sponsorTimes_v3"; --!sqlite-ignore + +UPDATE "config" SET value = 9 WHERE key = 'version'; + +COMMIT; \ No newline at end of file From 6621ae373019750533fd091e0b3a4d3b939a9a8d Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 26 May 2022 22:49:21 -0400 Subject: [PATCH 026/168] Fix upgrade crash --- databases/_upgrade_private_9.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/_upgrade_private_9.sql b/databases/_upgrade_private_9.sql index b4baaa9..88e3634 100644 --- a/databases/_upgrade_private_9.sql +++ b/databases/_upgrade_private_9.sql @@ -2,7 +2,7 @@ BEGIN TRANSACTION; -- Add primary keys -DROP INDEX "privateDB_sponsorTimes_v3"; --!sqlite-ignore +DROP INDEX IF EXISTS "privateDB_sponsorTimes_v3"; --!sqlite-ignore UPDATE "config" SET value = 9 WHERE key = 'version'; From 0260b4889dcb99b3e812dda91cb241a9b8425dce Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 27 May 2022 19:07:44 -0400 Subject: [PATCH 027/168] safe navigate in user vip --- src/utils/isUserVIP.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/isUserVIP.ts b/src/utils/isUserVIP.ts index 4e2ed46..b4ac6c1 100644 --- a/src/utils/isUserVIP.ts +++ b/src/utils/isUserVIP.ts @@ -2,5 +2,5 @@ import { db } from "../databases/databases"; import { HashedUserID } from "../types/user.model"; export async function isUserVIP(userID: HashedUserID): Promise { - return (await db.prepare("get", `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ? LIMIT 1`, [userID])).userCount > 0; + return (await db.prepare("get", `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ? LIMIT 1`, [userID]))?.userCount > 0; } From 046a535ebc2bbd6316dfc71995fa2e644c447b08 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 1 Jun 2022 13:53:10 -0400 Subject: [PATCH 028/168] less server --- nginx/nginx.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 38447d6..180c82f 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -81,8 +81,8 @@ http { server 10.0.0.16:4441 max_fails=25 fail_timeout=20s; server 10.0.0.16:4442 max_fails=25 fail_timeout=20s; - server 10.0.0.17:4441 max_fails=25 fail_timeout=20s; - server 10.0.0.17:4442 max_fails=25 fail_timeout=20s; + #server 10.0.0.17:4441 max_fails=25 fail_timeout=20s; + #server 10.0.0.17:4442 max_fails=25 fail_timeout=20s; #server 134.209.69.251:80 backup; From 8ec44aff1a53ae3c4eed99fec5c19a600b024d21 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Wed, 1 Jun 2022 14:24:58 -0400 Subject: [PATCH 029/168] more cache time --- nginx/nginx.conf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 180c82f..a113bd8 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -136,8 +136,8 @@ http { include /etc/nginx/cors.conf; #return 200 "[]"; proxy_pass http://backend_$request_method; - #proxy_cache CACHEZONE; - #proxy_cache_valid 10s; + proxy_cache CACHEZONE; + proxy_cache_valid 20s; #limit_req zone=mylimit; #access_log /etc/nginx/logs/download.log no_ip; From 5c437508356c7c65f57f6633f5ac46a30d84c318 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 10 Jun 2022 16:40:48 -0400 Subject: [PATCH 030/168] Allow ssl --- src/config.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index aa8ac5f..e2d56d2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -76,7 +76,10 @@ addDefaults(config, { user: "", host: "", password: "", - port: 5432 + port: 5432, + ssl: { + rejectUnauthorized: false + } }, dumpDatabase: { enabled: false, From 749fa4bb95aea4c48c5d219fe9674872cded9107 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 14 Jun 2022 16:22:59 -0400 Subject: [PATCH 031/168] Fix not all db config vars being used --- src/databases/databases.ts | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/databases/databases.ts b/src/databases/databases.ts index 52f357c..4bea8c7 100644 --- a/src/databases/databases.ts +++ b/src/databases/databases.ts @@ -17,11 +17,8 @@ if (config.mysql) { readOnly: config.readOnly, createDbIfNotExists: config.createDatabaseIfNotExist, postgres: { - user: config.postgres?.user, - host: config.postgres?.host, + ...config.postgres, database: "sponsorTimes", - password: config.postgres?.password, - port: config.postgres?.port, } }); @@ -32,11 +29,8 @@ if (config.mysql) { readOnly: config.readOnly, createDbIfNotExists: config.createDatabaseIfNotExist, postgres: { - user: config.postgres?.user, - host: config.postgres?.host, - database: "privateDB", - password: config.postgres?.password, - port: config.postgres?.port, + ...config.postgres, + database: "privateDB" } }); } else { From a1871803881f2649d4de6180039becd0d8abfba4 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 24 Jun 2022 01:24:55 -0400 Subject: [PATCH 032/168] Add new index --- databases/_sponsorTimes_indexes.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/databases/_sponsorTimes_indexes.sql b/databases/_sponsorTimes_indexes.sql index cd22b93..139970f 100644 --- a/databases/_sponsorTimes_indexes.sql +++ b/databases/_sponsorTimes_indexes.sql @@ -25,6 +25,11 @@ CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID" (service COLLATE pg_catalog."default" ASC NULLS LAST, "videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "startTime" ASC NULLS LAST) TABLESPACE pg_default; +CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID_category" + ON public."sponsorTimes" USING btree + (service COLLATE pg_catalog."default" ASC NULLS LAST, "videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "category" ASC NULLS LAST) + TABLESPACE pg_default; + CREATE INDEX IF NOT EXISTS "sponsorTimes_description_gin" ON public."sponsorTimes" USING gin ("description" COLLATE pg_catalog."default" gin_trgm_ops, category COLLATE pg_catalog."default" gin_trgm_ops) From f8ef145bb8f861c734906a2878440de6c9f4f71d Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 24 Jun 2022 01:29:07 -0400 Subject: [PATCH 033/168] fix collation index --- databases/_sponsorTimes_indexes.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/_sponsorTimes_indexes.sql b/databases/_sponsorTimes_indexes.sql index 139970f..00ad8c8 100644 --- a/databases/_sponsorTimes_indexes.sql +++ b/databases/_sponsorTimes_indexes.sql @@ -27,7 +27,7 @@ CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID" CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID_category" ON public."sponsorTimes" USING btree - (service COLLATE pg_catalog."default" ASC NULLS LAST, "videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "category" ASC NULLS LAST) + (service COLLATE pg_catalog."default" ASC NULLS LAST, "videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "category" COLLATE pg_catalog."default" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX IF NOT EXISTS "sponsorTimes_description_gin" From e1d6fdfefbb80e91f8c8e23ff8b57f2a695a97f4 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 24 Jun 2022 02:21:37 -0400 Subject: [PATCH 034/168] Remove service from index --- databases/_sponsorTimes_indexes.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/databases/_sponsorTimes_indexes.sql b/databases/_sponsorTimes_indexes.sql index 00ad8c8..e028547 100644 --- a/databases/_sponsorTimes_indexes.sql +++ b/databases/_sponsorTimes_indexes.sql @@ -27,7 +27,7 @@ CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID" CREATE INDEX IF NOT EXISTS "sponsorTimes_videoID_category" ON public."sponsorTimes" USING btree - (service COLLATE pg_catalog."default" ASC NULLS LAST, "videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "category" COLLATE pg_catalog."default" ASC NULLS LAST) + ("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, "category" COLLATE pg_catalog."default" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX IF NOT EXISTS "sponsorTimes_description_gin" From a8d0336cae775844fa3ee94bad7a735a6360bac9 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 24 Jun 2022 17:20:48 -0400 Subject: [PATCH 035/168] Don't crash on postgres errors --- src/databases/Postgres.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index a6e855e..d34da78 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -21,6 +21,9 @@ export class Postgres implements IDatabase { async init(): Promise { this.pool = new Pool(this.config.postgres); + this.pool.on("error", (err) => { + Logger.error(err.stack); + }); if (!this.config.readOnly) { if (this.config.createDbIfNotExists) { From 1a232600a128d23d6be642bbe5ad69582da83896 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 25 Jun 2022 01:41:45 -0400 Subject: [PATCH 036/168] Add option to cycle between multiple postgres instances --- src/config.ts | 13 ++++++++++++- src/databases/Postgres.ts | 35 +++++++++++++++++++++++++++++++---- src/databases/databases.ts | 12 ++++++++++-- src/types/config.model.ts | 7 ++++++- 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/config.ts b/src/config.ts index e2d56d2..54b7d74 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,7 @@ import fs from "fs"; import { SBSConfig } from "./types/config.model"; import packageJson from "../package.json"; -import { isBoolean, isNumber } from "lodash"; +import { isNumber } from "lodash"; const isTestMode = process.env.npm_lifecycle_script === packageJson.scripts.test; const configFile = process.env.TEST_POSTGRES ? "ci.json" @@ -81,6 +81,17 @@ addDefaults(config, { rejectUnauthorized: false } }, + postgresReadOnly: { + enabled: false, + weight: 1, + user: "", + host: "", + password: "", + port: 5432, + ssl: { + rejectUnauthorized: false + } + }, dumpDatabase: { enabled: false, minTimeBetweenMs: 180000, diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index d34da78..7fca688 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -3,6 +3,7 @@ import { IDatabase, QueryType } from "./IDatabase"; import { Client, Pool, PoolClient, types } from "pg"; import fs from "fs"; +import { CustomPostgresConfig, CustomPostgresReadOnlyConfig } from "../types/config.model"; // return numeric (pg_type oid=1700) as float types.setTypeParser(1700, function(val) { @@ -14,16 +15,33 @@ types.setTypeParser(20, function(val) { return parseInt(val, 10); }); +export interface DatabaseConfig { + dbSchemaFileName: string, + dbSchemaFolder: string, + fileNamePrefix: string, + readOnly: boolean, + createDbIfNotExists: boolean, + postgres: CustomPostgresConfig, + postgresReadOnly: CustomPostgresReadOnlyConfig +} + export class Postgres implements IDatabase { private pool: Pool; + private poolRead: Pool; - constructor(private config: Record) {} + constructor(private config: DatabaseConfig) {} async init(): Promise { this.pool = new Pool(this.config.postgres); - this.pool.on("error", (err) => { + const errorHandler = (err: Error) => { Logger.error(err.stack); - }); + }; + this.pool.on("error", errorHandler); + + if (this.config.postgresReadOnly) { + this.poolRead = new Pool(this.config.postgresReadOnly); + this.poolRead.on("error", errorHandler); + } if (!this.config.readOnly) { if (this.config.createDbIfNotExists) { @@ -60,7 +78,7 @@ export class Postgres implements IDatabase { let client: PoolClient; try { - client = await this.pool.connect(); + client = await this.getClient(type); const queryResult = await client.query({ text: query, values: params }); switch (type) { @@ -85,6 +103,15 @@ export class Postgres implements IDatabase { } } + private getClient(type: string): Promise { + if (this.poolRead && (type === "get" || type === "all") + && Math.random() > 1 / (this.config.postgresReadOnly.weight + 1)) { + return this.poolRead.connect(); + } else { + return this.pool.connect(); + } + } + private async createDB() { const client = new Client({ ...this.config.postgres, diff --git a/src/databases/databases.ts b/src/databases/databases.ts index 4bea8c7..e2c225b 100644 --- a/src/databases/databases.ts +++ b/src/databases/databases.ts @@ -19,7 +19,11 @@ if (config.mysql) { postgres: { ...config.postgres, database: "sponsorTimes", - } + }, + postgresReadOnly: config.postgresReadOnly ? { + ...config.postgresReadOnly, + database: "sponsorTimes" + } : null }); privateDB = new Postgres({ @@ -31,7 +35,11 @@ if (config.mysql) { postgres: { ...config.postgres, database: "privateDB" - } + }, + postgresReadOnly: config.postgresReadOnly ? { + ...config.postgresReadOnly, + database: "privateDB" + } : null }); } else { db = new Sqlite({ diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 8258005..774bd01 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -5,10 +5,14 @@ interface RedisConfig extends redis.RedisClientOptions { enabled: boolean; } -interface CustomPostgresConfig extends PoolConfig { +export interface CustomPostgresConfig extends PoolConfig { enabled: boolean; } +export interface CustomPostgresReadOnlyConfig extends CustomPostgresConfig { + weight: number; +} + export interface SBSConfig { [index: string]: any port: number; @@ -51,6 +55,7 @@ export interface SBSConfig { redis?: RedisConfig; maxRewardTimePerSegmentInSeconds?: number; postgres?: CustomPostgresConfig; + postgresReadOnly?: CustomPostgresReadOnlyConfig; dumpDatabase?: DumpDatabase; diskCacheURL: string; crons: CronJobOptions; From 08003bc2f2a9f55b41ec995aa9b0dee3244a8a20 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 25 Jun 2022 12:02:01 -0400 Subject: [PATCH 037/168] Switch postgres instances if there is a failure --- src/databases/Postgres.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 7fca688..a10b9e7 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -27,20 +27,26 @@ export interface DatabaseConfig { export class Postgres implements IDatabase { private pool: Pool; + private lastPoolFail: number = 0; + private poolRead: Pool; + private lastPoolReadFail: number = 0; constructor(private config: DatabaseConfig) {} async init(): Promise { this.pool = new Pool(this.config.postgres); - const errorHandler = (err: Error) => { + this.pool.on("error", (err) => { Logger.error(err.stack); - }; - this.pool.on("error", errorHandler); + this.lastPoolFail = Date.now(); + }); if (this.config.postgresReadOnly) { this.poolRead = new Pool(this.config.postgresReadOnly); - this.poolRead.on("error", errorHandler); + this.poolRead.on("error", (err) => { + Logger.error(err.stack); + this.lastPoolReadFail = Date.now(); + }); } if (!this.config.readOnly) { @@ -104,8 +110,11 @@ export class Postgres implements IDatabase { } private getClient(type: string): Promise { - if (this.poolRead && (type === "get" || type === "all") - && Math.random() > 1 / (this.config.postgresReadOnly.weight + 1)) { + const readAvailable = this.poolRead && (type === "get" || type === "all"); + const ignroreReadDueToFailure = this.lastPoolReadFail < Date.now() - 1000 * 30; + const readDueToFailure = this.lastPoolFail < Date.now() - 1000 * 30; + if (readAvailable && !ignroreReadDueToFailure && (readDueToFailure || + Math.random() > 1 / (this.config.postgresReadOnly.weight + 1))) { return this.poolRead.connect(); } else { return this.pool.connect(); From e615d7c032fec65457a1d4960f720e8a972307ed Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 25 Jun 2022 12:03:30 -0400 Subject: [PATCH 038/168] Fix warnings --- src/databases/Postgres.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index a10b9e7..c0711bb 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -27,10 +27,10 @@ export interface DatabaseConfig { export class Postgres implements IDatabase { private pool: Pool; - private lastPoolFail: number = 0; + private lastPoolFail = 0; private poolRead: Pool; - private lastPoolReadFail: number = 0; + private lastPoolReadFail = 0; constructor(private config: DatabaseConfig) {} From 86e61c778ca4f7650c97efa898e7d39aecd85add Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 25 Jun 2022 12:30:16 -0400 Subject: [PATCH 039/168] Fix fail calculation --- src/databases/Postgres.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index c0711bb..51b9fdb 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -111,8 +111,8 @@ export class Postgres implements IDatabase { private getClient(type: string): Promise { const readAvailable = this.poolRead && (type === "get" || type === "all"); - const ignroreReadDueToFailure = this.lastPoolReadFail < Date.now() - 1000 * 30; - const readDueToFailure = this.lastPoolFail < Date.now() - 1000 * 30; + const ignroreReadDueToFailure = this.lastPoolReadFail > Date.now() - 1000 * 30; + const readDueToFailure = this.lastPoolFail > Date.now() - 1000 * 30; if (readAvailable && !ignroreReadDueToFailure && (readDueToFailure || Math.random() > 1 / (this.config.postgresReadOnly.weight + 1))) { return this.poolRead.connect(); From c6868fa839d1eae00c5732ce698e7e3e372a97ab Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 27 Jun 2022 19:35:50 -0400 Subject: [PATCH 040/168] Disable ssl --- src/config.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/config.ts b/src/config.ts index 54b7d74..2efd0c9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -76,10 +76,7 @@ addDefaults(config, { user: "", host: "", password: "", - port: 5432, - ssl: { - rejectUnauthorized: false - } + port: 5432 }, postgresReadOnly: { enabled: false, @@ -87,10 +84,7 @@ addDefaults(config, { user: "", host: "", password: "", - port: 5432, - ssl: { - rejectUnauthorized: false - } + port: 5432 }, dumpDatabase: { enabled: false, From 54db2c8c10b2c06314c9379ccde8a199679a78bd Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 27 Jun 2022 19:47:29 -0400 Subject: [PATCH 041/168] Release client on error --- src/databases/Postgres.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 51b9fdb..1d5cb5e 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -36,16 +36,20 @@ export class Postgres implements IDatabase { async init(): Promise { this.pool = new Pool(this.config.postgres); - this.pool.on("error", (err) => { + this.pool.on("error", (err, client) => { Logger.error(err.stack); this.lastPoolFail = Date.now(); + + client.release(true); }); if (this.config.postgresReadOnly) { this.poolRead = new Pool(this.config.postgresReadOnly); - this.poolRead.on("error", (err) => { + this.poolRead.on("error", (err, client) => { Logger.error(err.stack); this.lastPoolReadFail = Date.now(); + + client.release(true); }); } From 8560c3f6735bf7bb4e5ec0b25f00427220ba3f14 Mon Sep 17 00:00:00 2001 From: Michael C Date: Tue, 28 Jun 2022 15:02:09 -0400 Subject: [PATCH 042/168] add tests for #454 --- test/cases/voteOnSponsorTime.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index 9fad1db..9b33862 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -628,4 +628,17 @@ describe("voteOnSponsorTime", () => { done(); }); }); + + it("Should not be able to revive full video segment as non-vip", (done) => { + const UUID = "full-video-uuid-1"; + postVote("VIPUser", UUID, 0); // downvote as VIP + postVote("randomID3", UUID, 1) + .then(async res => { + assert.strictEqual(res.status, 200); + const row = await getSegmentVotes(UUID); + assert.strictEqual(row.votes, -2); + done(); + }) + .catch(err => done(err)); + }); }); From 931e3b8b111500b4fda601a4d8084cb94e49df1c Mon Sep 17 00:00:00 2001 From: Michael C Date: Tue, 28 Jun 2022 15:19:42 -0400 Subject: [PATCH 043/168] add instanbul/nyc test coverage --- .gitignore | 3 + .nycrc.json | 5 + package-lock.json | 2529 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 4 files changed, 2539 insertions(+) create mode 100644 .nycrc.json diff --git a/.gitignore b/.gitignore index 227dadb..25260b5 100644 --- a/.gitignore +++ b/.gitignore @@ -45,3 +45,6 @@ working .DS_Store /.idea/ /dist/ + +# nyc coverage output +.nyc_output/ \ No newline at end of file diff --git a/.nycrc.json b/.nycrc.json new file mode 100644 index 0000000..76027a5 --- /dev/null +++ b/.nycrc.json @@ -0,0 +1,5 @@ +{ + "exclude": [ + "src/routes/addUnlitedVideo.ts" + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2ac17bd..3aeb3d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "eslint": "^8.3.0", "mocha": "^9.1.3", "nodemon": "^2.0.15", + "nyc": "^15.1.0", "sinon": "^12.0.1", "ts-mock-imports": "^1.3.8", "ts-node": "^10.4.0", @@ -43,6 +44,409 @@ "node": ">=10" } }, + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.6.tgz", + "integrity": "sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.6.tgz", + "integrity": "sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.6", + "@babel/helper-compilation-targets": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helpers": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.6.tgz", + "integrity": "sha512-AIwwoOS8axIC5MZbhNHRLKi3D+DMpvDf9XUcu3pIVAfOHFT45f4AoDAltRbHIQomCipkCZxrNkfpOEHhJz/VKw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz", + "integrity": "sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz", + "integrity": "sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz", + "integrity": "sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz", + "integrity": "sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.6.tgz", + "integrity": "sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.6.tgz", + "integrity": "sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz", + "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.6.tgz", + "integrity": "sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.6", + "@babel/helper-function-name": "^7.18.6", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/types": "^7.18.6", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.6.tgz", + "integrity": "sha512-NdBNzPDwed30fZdDQtVR7ZgaO4UKjuaQFH9VArS+HMnurlOY0JWN+4ROlu/iapMFwjRQU4pOG4StZfDmulEwGA==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -113,6 +517,170 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz", + "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@node-redis/bloom": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@node-redis/bloom/-/bloom-1.0.1.tgz", @@ -622,6 +1190,19 @@ "node": ">=0.4.0" } }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -716,11 +1297,29 @@ "node": ">= 8" } }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, "node_modules/are-we-there-yet": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", @@ -1008,6 +1607,34 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/browserslist": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.1.tgz", + "integrity": "sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001359", + "electron-to-chromium": "^1.4.172", + "node-releases": "^2.0.5", + "update-browserslist-db": "^1.0.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1094,6 +1721,21 @@ "node": ">=8" } }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1115,6 +1757,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001359", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001359.tgz", + "integrity": "sha512-Xln/BAsPzEuiVLgJ2/45IaqD9jShtk3Y33anKb4+yLwQzws3+v6odKfpgES/cDEaZMLzSChpIGdbOYtH9MyuHw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1178,6 +1836,15 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -1276,6 +1943,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1351,6 +2024,15 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, "node_modules/cookie": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", @@ -1470,6 +2152,18 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -1561,6 +2255,12 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "node_modules/electron-to-chromium": { + "version": "1.4.172", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.172.tgz", + "integrity": "sha512-yDoFfTJnqBAB6hSiPvzmsBJSrjOXJtHSJoqJdI/zSIh7DYupYnIOHt/bbPw/WE31BJjNTybDdNAs21gCMnTh0Q==", + "dev": true + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -1595,6 +2295,12 @@ "node": ">=8.6" } }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -1779,6 +2485,19 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", @@ -2055,6 +2774,23 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2118,6 +2854,19 @@ } } }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2134,6 +2883,26 @@ "node": ">= 0.6" } }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -2218,6 +2987,15 @@ "node": ">= 4" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2227,6 +3005,15 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-port": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", @@ -2424,6 +3211,31 @@ "node": ">=8" } }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -2433,6 +3245,12 @@ "he": "bin/he" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "node_modules/http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -2533,6 +3351,15 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2686,6 +3513,18 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -2704,6 +3543,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-yarn-global": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", @@ -2721,6 +3569,115 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2733,6 +3690,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", @@ -2751,6 +3720,18 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "node_modules/json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -3252,6 +4233,24 @@ "node": ">=10" } }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "dev": true + }, "node_modules/nodemon": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", @@ -3372,6 +4371,215 @@ "node": ">=0.10.0" } }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3455,6 +4663,42 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", @@ -3619,6 +4863,12 @@ "split2": "^3.1.1" } }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "node_modules/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", @@ -3631,6 +4881,70 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -3715,6 +5029,18 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -3957,6 +5283,18 @@ "node": ">=8" } }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -3966,6 +5304,12 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4263,6 +5607,32 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/split2": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", @@ -4284,6 +5654,12 @@ "node": ">= 6" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", @@ -4352,6 +5728,15 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4460,6 +5845,20 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4476,6 +5875,15 @@ "promise": "^7.1.1" } }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -4707,6 +6115,32 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz", + "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/update-notifier": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", @@ -4769,6 +6203,15 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -4798,6 +6241,12 @@ "node": ">= 8" } }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -5027,6 +6476,319 @@ } }, "dependencies": { + "@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.6.tgz", + "integrity": "sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ==", + "dev": true + }, + "@babel/core": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.6.tgz", + "integrity": "sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.1.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.6", + "@babel/helper-compilation-targets": "^7.18.6", + "@babel/helper-module-transforms": "^7.18.6", + "@babel/helpers": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.6.tgz", + "integrity": "sha512-AIwwoOS8axIC5MZbhNHRLKi3D+DMpvDf9XUcu3pIVAfOHFT45f4AoDAltRbHIQomCipkCZxrNkfpOEHhJz/VKw==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "dependencies": { + "@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz", + "integrity": "sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.20.2", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz", + "integrity": "sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz", + "integrity": "sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw==", + "dev": true, + "requires": { + "@babel/template": "^7.18.6", + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-module-transforms": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz", + "integrity": "sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.18.6", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.18.6", + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-simple-access": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", + "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", + "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", + "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.6.tgz", + "integrity": "sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ==", + "dev": true, + "requires": { + "@babel/template": "^7.18.6", + "@babel/traverse": "^7.18.6", + "@babel/types": "^7.18.6" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.6.tgz", + "integrity": "sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw==", + "dev": true + }, + "@babel/template": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.6.tgz", + "integrity": "sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/types": "^7.18.6" + } + }, + "@babel/traverse": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.6.tgz", + "integrity": "sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.6", + "@babel/helper-function-name": "^7.18.6", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.18.6", + "@babel/types": "^7.18.6", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.6.tgz", + "integrity": "sha512-NdBNzPDwed30fZdDQtVR7ZgaO4UKjuaQFH9VArS+HMnurlOY0JWN+4ROlu/iapMFwjRQU4pOG4StZfDmulEwGA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + } + }, "@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -5084,6 +6846,133 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz", + "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.14", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", + "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "@node-redis/bloom": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@node-redis/bloom/-/bloom-1.0.1.tgz", @@ -5479,6 +7368,16 @@ "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5550,11 +7449,26 @@ "picomatch": "^2.0.4" } }, + "append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "requires": { + "default-require-extensions": "^3.0.0" + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, "are-we-there-yet": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", @@ -5807,6 +7721,18 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "browserslist": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.1.tgz", + "integrity": "sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001359", + "electron-to-chromium": "^1.4.172", + "node-releases": "^2.0.5", + "update-browserslist-db": "^1.0.4" + } + }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -5863,6 +7789,18 @@ } } }, + "caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "requires": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -5875,6 +7813,12 @@ "integrity": "sha512-tVI4q5jjFV5CavAU8DXfza/TJcZutVKo/5Foskmsqcm0MsL91moHvwiGNnqaa2o6PF/7yT5ikDRcVcl8Rj6LCA==", "dev": true }, + "caniuse-lite": { + "version": "1.0.30001359", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001359.tgz", + "integrity": "sha512-Xln/BAsPzEuiVLgJ2/45IaqD9jShtk3Y33anKb4+yLwQzws3+v6odKfpgES/cDEaZMLzSChpIGdbOYtH9MyuHw==", + "dev": true + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5923,6 +7867,12 @@ "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "dev": true }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, "cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -6001,6 +7951,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6066,6 +8022,15 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, + "convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, "cookie": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", @@ -6151,6 +8116,15 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "default-require-extensions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", + "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", + "dev": true, + "requires": { + "strip-bom": "^4.0.0" + } + }, "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -6221,6 +8195,12 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "electron-to-chromium": { + "version": "1.4.172", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.172.tgz", + "integrity": "sha512-yDoFfTJnqBAB6hSiPvzmsBJSrjOXJtHSJoqJdI/zSIh7DYupYnIOHt/bbPw/WE31BJjNTybDdNAs21gCMnTh0Q==", + "dev": true + }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -6249,6 +8229,12 @@ "ansi-colors": "^4.1.1" } }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -6386,6 +8372,12 @@ "eslint-visitor-keys": "^3.1.0" } }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, "esquery": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", @@ -6613,6 +8605,17 @@ } } }, + "find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6650,6 +8653,16 @@ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" }, + "foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6660,6 +8673,12 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -6727,12 +8746,24 @@ "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==" }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, "get-port": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", @@ -6880,12 +8911,36 @@ "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", "dev": true }, + "hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "requires": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "dependencies": { + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, "he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "http-cache-semantics": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", @@ -6951,6 +9006,12 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7062,6 +9123,12 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -7074,6 +9141,12 @@ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, "is-yarn-global": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", @@ -7091,6 +9164,93 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "requires": { + "append-transform": "^2.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz", + "integrity": "sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -7100,6 +9260,12 @@ "argparse": "^2.0.1" } }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", @@ -7118,6 +9284,12 @@ "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", "dev": true }, + "json5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true + }, "just-extend": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", @@ -7512,6 +9684,21 @@ "semver": "^7.3.5" } }, + "node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "requires": { + "process-on-spawn": "^1.0.0" + } + }, + "node-releases": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.5.tgz", + "integrity": "sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q==", + "dev": true + }, "nodemon": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", @@ -7599,6 +9786,172 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, + "nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "requires": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "dependencies": { + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7658,6 +10011,33 @@ "p-limit": "^3.0.2" } }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, "package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", @@ -7781,12 +10161,66 @@ "split2": "^3.1.1" } }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, "picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "dev": true }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, "postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -7847,6 +10281,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "requires": { + "fromentries": "^1.2.0" + } + }, "progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -8024,12 +10467,27 @@ "rc": "^1.2.8" } }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -8246,6 +10704,26 @@ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "requires": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + } + }, "split2": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", @@ -8266,6 +10744,12 @@ } } }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", @@ -8318,6 +10802,12 @@ "ansi-regex": "^5.0.1" } }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -8414,6 +10904,17 @@ } } }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -8430,6 +10931,12 @@ "promise": "^7.1.1" } }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, "to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -8587,6 +11094,16 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "update-browserslist-db": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz", + "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, "update-notifier": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", @@ -8637,6 +11154,12 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -8657,6 +11180,12 @@ "isexe": "^2.0.0" } }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true + }, "wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", diff --git a/package.json b/package.json index 1541c3c..3f4aa49 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "src/index.ts", "scripts": { "test": "npm run tsc && ts-node test/test.ts", + "test:coverate": "nyc npm run test", "dev": "nodemon", "dev:bash": "nodemon -x 'npm test ; npm start'", "postgres:docker": "docker run --rm -p 5432:5432 -e POSTGRES_USER=ci_db_user -e POSTGRES_PASSWORD=ci_db_pass postgres:alpine", @@ -42,6 +43,7 @@ "eslint": "^8.3.0", "mocha": "^9.1.3", "nodemon": "^2.0.15", + "nyc": "^15.1.0", "sinon": "^12.0.1", "ts-mock-imports": "^1.3.8", "ts-node": "^10.4.0", From de60415f5584e198df7c69764e1ce201a3d41705 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 28 Jun 2022 18:14:12 -0400 Subject: [PATCH 044/168] more generous reputation --- src/utils/reputation.ts | 8 +++---- test/cases/getUserInfo.ts | 2 +- test/cases/reputation.ts | 44 ++++++++++++++++++++++++++++----------- 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/utils/reputation.ts b/src/utils/reputation.ts index 80d3eef..5bea3f9 100644 --- a/src/utils/reputation.ts +++ b/src/utils/reputation.ts @@ -61,13 +61,13 @@ export function calculateReputationFromMetrics(metrics: ReputationDBResult): num } const downvoteRatio = metrics.downvotedSubmissions / metrics.totalSubmissions; - if (downvoteRatio > 0.3) { - return convertRange(Math.min(downvoteRatio, 0.7), 0.3, 0.7, -0.5, -2.5); + if (downvoteRatio > 0.5) { + return convertRange(Math.min(downvoteRatio, 0.7), 0.5, 0.7, -0.5, -2.5); } const nonSelfDownvoteRatio = metrics.nonSelfDownvotedSubmissions / metrics.totalSubmissions; - if (nonSelfDownvoteRatio > 0.05) { - return convertRange(Math.min(nonSelfDownvoteRatio, 0.4), 0.05, 0.4, -0.5, -2.5); + if (nonSelfDownvoteRatio > 0.3) { + return convertRange(Math.min(nonSelfDownvoteRatio, 0.4), 0.3, 0.4, -0.5, -2.5); } if (metrics.votedSum < 5) { diff --git a/test/cases/getUserInfo.ts b/test/cases/getUserInfo.ts index 098d092..4f8d3ae 100644 --- a/test/cases/getUserInfo.ts +++ b/test/cases/getUserInfo.ts @@ -64,7 +64,7 @@ describe("getUserInfo", () => { ignoredViewCount: 20, segmentCount: 3, ignoredSegmentCount: 2, - reputation: -2, + reputation: -1.5, lastSegmentID: "uuid000005", vip: false, warnings: 0, diff --git a/test/cases/reputation.ts b/test/cases/reputation.ts index b06c0a9..23b2bdc 100644 --- a/test/cases/reputation.ts +++ b/test/cases/reputation.ts @@ -10,6 +10,8 @@ describe("reputation", () => { const userHashLowSubmissions = getHash(userIDLowSubmissions); const userIDHighDownvotes = "reputation-highdownvotes" as UserID; const userHashHighDownvotes = getHash(userIDHighDownvotes); + const userIDLowNonSelfDownvotes = "reputation-lownonselfdownvotes" as UserID; + const userHashLowNonSelfDownvotes = getHash(userIDLowNonSelfDownvotes); const userIDHighNonSelfDownvotes = "reputation-highnonselfdownvotes" as UserID; const userHashHighNonSelfDownvotes = getHash(userIDHighNonSelfDownvotes); const userIDNewSubmissions = "reputation-newsubmissions" as UserID; @@ -45,15 +47,28 @@ describe("reputation", () => { await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-uuid-7", userHashHighDownvotes, 1606240000000, 50, "sponsor", 0, 0]); // First video is considered a normal downvote, second is considered a self-downvote (ie. they didn't resubmit to fix their downvote) - await db.prepare("run", sponsorTimesInsertQuery, [`${videoID}A`, 1, 11, 2, 0, "reputation-1-1-uuid-0", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [`${videoID}A`, 1, 11, 2, 0, "reputation-1-1-uuid-0", userHashLowNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); // Different category, same video - await db.prepare("run", sponsorTimesInsertQuery, [`${videoID}A`, 1, 11, -2, 0, "reputation-1-1-uuid-1", userHashHighNonSelfDownvotes, 1606240000000, 50, "intro", 0, 0]); - await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-uuid-2", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); - await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-uuid-3", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); - await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-uuid-4", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); - await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, -1, 0, "reputation-1-1-uuid-5", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); - await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-uuid-6", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); - await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-uuid-7", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [`${videoID}A`, 1, 11, -2, 0, "reputation-1-1-uuid-1", userHashLowNonSelfDownvotes, 1606240000000, 50, "intro", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-uuid-2", userHashLowNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-uuid-3", userHashLowNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-uuid-4", userHashLowNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, -1, 0, "reputation-1-1-uuid-5", userHashLowNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-uuid-6", userHashLowNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-uuid-7", userHashLowNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + + // First videos is considered a normal downvote, last is considered a self-downvote (ie. they didn't resubmit to fix their downvote) + await db.prepare("run", sponsorTimesInsertQuery, [`${videoID}A`, 1, 11, 2, 0, "reputation-1-1-1-uuid-0", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + // Different category, same video + await db.prepare("run", sponsorTimesInsertQuery, [`${videoID}A`, 1, 11, -2, 0, "reputation-1-1-1-uuid-1", userHashHighNonSelfDownvotes, 1606240000000, 50, "intro", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [`${videoID}B`, 1, 11, -2, 0, "reputation-1-1-1-uuid-1-b", userHashHighNonSelfDownvotes, 1606240000000, 50, "intro", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [`${videoID}C`, 1, 11, -2, 0, "reputation-1-1-1-uuid-1-c", userHashHighNonSelfDownvotes, 1606240000000, 50, "intro", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-1-uuid-2", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-1-uuid-3", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-1-uuid-4", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, -1, 0, "reputation-1-1-1-uuid-5", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-1-uuid-6", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); + await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 0, 0, "reputation-1-1-1-uuid-7", userHashHighNonSelfDownvotes, 1606240000000, 50, "sponsor", 0, 0]); await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 2, 0, "reputation-2-uuid-0", userHashNewSubmissions, Date.now(), 50, "sponsor", 0, 0]); await db.prepare("run", sponsorTimesInsertQuery, [videoID, 1, 11, 2, 0, "reputation-2-uuid-1", userHashNewSubmissions, Date.now(), 50, "sponsor", 0, 0]); @@ -141,10 +156,10 @@ describe("reputation", () => { }; const data = await getReputation(getHash(userIDHighDownvotes)); assert.strictEqual(data, calculateReputationFromMetrics(metrics)); - assert.strictEqual(data, -2.125); + assert.strictEqual(data, -1.7500000000000002); }); - it("user with high non self downvote ratio", async () => { + it("user with low non self downvote ratio", async () => { const metrics = { totalSubmissions: 8, downvotedSubmissions: 2, @@ -155,9 +170,14 @@ describe("reputation", () => { oldUpvotedSubmissions: 1, mostUpvotedInLockedVideoSum: 0 }; - const data = await getReputation(userHashHighNonSelfDownvotes); + const data = await getReputation(userHashLowNonSelfDownvotes); assert.strictEqual(data, calculateReputationFromMetrics(metrics)); - assert.strictEqual(data, -1.6428571428571428); + assert.strictEqual(data, 0); + }); + + it("user with high non self downvote ratio", async () => { + const data = await getReputation(userHashHighNonSelfDownvotes); + assert.strictEqual(data, -2.5); }); it("user with mostly new submissions", async () => { From b1b40d410f53ed133d1f35fc2d6577ec673c5b3d Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Jun 2022 01:37:50 -0400 Subject: [PATCH 045/168] Prevent errors from double calling release --- src/databases/Postgres.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 1d5cb5e..c52d7a5 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -40,7 +40,11 @@ export class Postgres implements IDatabase { Logger.error(err.stack); this.lastPoolFail = Date.now(); - client.release(true); + try { + client.release(true); + } catch (err) { + Logger.error(`prepare (postgres): ${err}`); + } }); if (this.config.postgresReadOnly) { @@ -49,7 +53,11 @@ export class Postgres implements IDatabase { Logger.error(err.stack); this.lastPoolReadFail = Date.now(); - client.release(true); + try { + client.release(true); + } catch (err) { + Logger.error(`prepare (postgres): ${err}`); + } }); } @@ -109,7 +117,11 @@ export class Postgres implements IDatabase { } catch (err) { Logger.error(`prepare (postgres): ${err}`); } finally { - client?.release(); + try { + client?.release(); + } catch (err) { + Logger.error(`prepare (postgres): ${err}`); + } } } From 5057c867075d511a4f8f14276aad965e9b1d8068 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Jun 2022 01:38:58 -0400 Subject: [PATCH 046/168] Fix read only db used by default --- src/databases/Postgres.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index c52d7a5..b8ca3a8 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -47,7 +47,7 @@ export class Postgres implements IDatabase { } }); - if (this.config.postgresReadOnly) { + if (this.config.postgresReadOnly && this.config.postgresReadOnly.enabled) { this.poolRead = new Pool(this.config.postgresReadOnly); this.poolRead.on("error", (err, client) => { Logger.error(err.stack); From 3844404637724c39efa2825af5484c18fa96f0bc Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Jun 2022 02:00:59 -0400 Subject: [PATCH 047/168] ctch client connection errors --- src/databases/Postgres.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index b8ca3a8..dc84dd1 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -43,7 +43,7 @@ export class Postgres implements IDatabase { try { client.release(true); } catch (err) { - Logger.error(`prepare (postgres): ${err}`); + Logger.error(`pool (postgres): ${err}`); } }); @@ -56,7 +56,7 @@ export class Postgres implements IDatabase { try { client.release(true); } catch (err) { - Logger.error(`prepare (postgres): ${err}`); + Logger.error(`poolRead (postgres): ${err}`); } }); } @@ -97,6 +97,16 @@ export class Postgres implements IDatabase { let client: PoolClient; try { client = await this.getClient(type); + client.on("error", (err) => { + Logger.error(err.stack); + + try { + client.release(true); + } catch (err) { + Logger.error(`client (postgres): ${err}`); + } + }); + const queryResult = await client.query({ text: query, values: params }); switch (type) { From edff48d2588a15172f9e85f7847e3e4a928a8ecc Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Jun 2022 17:50:15 -0400 Subject: [PATCH 048/168] Don't release client on client error --- src/databases/Postgres.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index dc84dd1..0b70728 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -99,12 +99,6 @@ export class Postgres implements IDatabase { client = await this.getClient(type); client.on("error", (err) => { Logger.error(err.stack); - - try { - client.release(true); - } catch (err) { - Logger.error(`client (postgres): ${err}`); - } }); const queryResult = await client.query({ text: query, values: params }); From a99da61039a7f21fa77a603daff2b3b8c56d30aa Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Jun 2022 17:56:06 -0400 Subject: [PATCH 049/168] Fetch user count right away --- src/routes/getTotalStats.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/getTotalStats.ts b/src/routes/getTotalStats.ts index 6c6a01d..df310ab 100644 --- a/src/routes/getTotalStats.ts +++ b/src/routes/getTotalStats.ts @@ -10,9 +10,10 @@ let firefoxUsersCache = 0; // By the privacy friendly user counter let apiUsersCache = 0; - let lastUserCountCheck = 0; +updateExtensionUsers(); + export async function getTotalStats(req: Request, res: Response): Promise { const userCountQuery = `(SELECT COUNT(*) FROM (SELECT DISTINCT "userID" from "sponsorTimes") t) "userCount",`; From ebee00322adf0dadb1e0bb99c562ac1d3a71664a Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 30 Jun 2022 18:11:02 -0400 Subject: [PATCH 050/168] remove client error listener completely --- src/databases/Postgres.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 0b70728..2d2e925 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -97,10 +97,6 @@ export class Postgres implements IDatabase { let client: PoolClient; try { client = await this.getClient(type); - client.on("error", (err) => { - Logger.error(err.stack); - }); - const queryResult = await client.query({ text: query, values: params }); switch (type) { From cc953884d9f60fc402067d87c1a8e6da792ccabd Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 4 Jul 2022 01:10:52 -0400 Subject: [PATCH 051/168] Don't count chapter for time saved --- src/routes/getTopUsers.ts | 2 +- src/routes/getTotalStats.ts | 2 +- src/routes/getUserStats.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/getTopUsers.ts b/src/routes/getTopUsers.ts index 282fea3..34d6bc5 100644 --- a/src/routes/getTopUsers.ts +++ b/src/routes/getTopUsers.ts @@ -34,7 +34,7 @@ async function generateTopUsersStats(sortBy: string, categoryStatsEnabled = fals SUM(((CASE WHEN "sponsorTimes"."endTime" - "sponsorTimes"."startTime" > ? THEN ? ELSE "sponsorTimes"."endTime" - "sponsorTimes"."startTime" END) / 60) * "sponsorTimes"."views") as "minutesSaved", SUM("votes") as "userVotes", ${additionalFields} COALESCE("userNames"."userName", "sponsorTimes"."userID") as "userName" FROM "sponsorTimes" LEFT JOIN "userNames" ON "sponsorTimes"."userID"="userNames"."userID" LEFT JOIN "shadowBannedUsers" ON "sponsorTimes"."userID"="shadowBannedUsers"."userID" - WHERE "sponsorTimes"."votes" > -1 AND "sponsorTimes"."shadowHidden" != 1 AND "shadowBannedUsers"."userID" IS NULL + WHERE "sponsorTimes"."votes" > -1 AND "sponsorTimes"."shadowHidden" != 1 AND "sponsorTimes"."actionType" != 'chapter' AND "shadowBannedUsers"."userID" IS NULL GROUP BY COALESCE("userName", "sponsorTimes"."userID") HAVING SUM("votes") > 20 ORDER BY "${sortBy}" DESC LIMIT 100`, [maxRewardTimePerSegmentInSeconds, maxRewardTimePerSegmentInSeconds]); diff --git a/src/routes/getTotalStats.ts b/src/routes/getTotalStats.ts index df310ab..e001d5d 100644 --- a/src/routes/getTotalStats.ts +++ b/src/routes/getTotalStats.ts @@ -18,7 +18,7 @@ export async function getTotalStats(req: Request, res: Response): Promise const userCountQuery = `(SELECT COUNT(*) FROM (SELECT DISTINCT "userID" from "sponsorTimes") t) "userCount",`; const row = await db.prepare("get", `SELECT ${req.query.countContributingUsers ? userCountQuery : ""} COUNT(*) as "totalSubmissions", - SUM("views") as "viewCount", SUM(("endTime" - "startTime") / 60 * "views") as "minutesSaved" FROM "sponsorTimes" WHERE "shadowHidden" != 1 AND "votes" >= 0`, []); + SUM("views") as "viewCount", SUM(("endTime" - "startTime") / 60 * "views") as "minutesSaved" FROM "sponsorTimes" WHERE "shadowHidden" != 1 AND "votes" >= 0 AND "actionType" != 'chapter'`, []); if (row !== undefined) { const extensionUsers = chromeUsersCache + firefoxUsersCache; diff --git a/src/routes/getUserStats.ts b/src/routes/getUserStats.ts index 41040f0..8fca455 100644 --- a/src/routes/getUserStats.ts +++ b/src/routes/getUserStats.ts @@ -37,7 +37,7 @@ async function dbGetUserSummary(userID: HashedUserID, fetchCategoryStats: boolea ${additionalQuery} count(*) as "segmentCount" FROM "sponsorTimes" - WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" !=1`, + WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1 AND "actionType" != 'chapter'`, [maxRewardTimePerSegmentInSeconds, maxRewardTimePerSegmentInSeconds, userID]); const source = (row.minutesSaved != null) ? row : {}; const handler = { get: (target: Record, name: string) => target?.[name] || 0 }; From f9de547b95ee9f35ee6400ca698726702e5708c9 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 4 Jul 2022 16:18:58 -0400 Subject: [PATCH 052/168] Add malicious vote type for chapters --- DatabaseSchema.md | 1 + databases/_upgrade_private_10.sql | 9 ++++++++ src/config.ts | 1 + src/routes/voteOnSponsorTime.ts | 38 +++++++++++++++++++------------ src/types/config.model.ts | 1 + src/types/segments.model.ts | 9 ++++++++ test/cases/voteOnSponsorTime.ts | 26 +++++++++++++++++++++ 7 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 databases/_upgrade_private_10.sql diff --git a/DatabaseSchema.md b/DatabaseSchema.md index 957edb1..ab11b53 100644 --- a/DatabaseSchema.md +++ b/DatabaseSchema.md @@ -209,6 +209,7 @@ | userID | TEXT | not null | | hashedIP | TEXT | not null | | type | INTEGER | not null | +| originalVoteType | INTEGER | not null | # Since type was reused to also specify the number of votes removed when less than 0, this is being used for the actual type | index | field | | -- | :--: | diff --git a/databases/_upgrade_private_10.sql b/databases/_upgrade_private_10.sql new file mode 100644 index 0000000..bdac3b6 --- /dev/null +++ b/databases/_upgrade_private_10.sql @@ -0,0 +1,9 @@ +BEGIN TRANSACTION; + +-- Add primary keys + +ALTER TABLE "votes" ADD "originalType" INTEGER NOT NULL default -1; + +UPDATE "config" SET value = 10 WHERE key = 'version'; + +COMMIT; \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 2efd0c9..1a62374 100644 --- a/src/config.ts +++ b/src/config.ts @@ -42,6 +42,7 @@ addDefaults(config, { discordNeuralBlockRejectWebhookURL: null, discordFailedReportChannelWebhookURL: null, discordReportChannelWebhookURL: null, + discordMaliciousReportWebhookURL: null, getTopUsersCacheTimeMinutes: 240, globalSalt: null, mode: "", diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 1412a24..002b63b 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -11,7 +11,7 @@ import { getIP } from "../utils/getIP"; import { getHashCache } from "../utils/getHashCache"; import { config } from "../config"; import { UserID } from "../types/user.model"; -import { DBSegment, Category, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash, VideoDuration, ActionType } from "../types/segments.model"; +import { DBSegment, Category, HashedIP, IPAddress, SegmentUUID, Service, VideoID, VideoIDHash, VideoDuration, ActionType, VoteType } from "../types/segments.model"; import { QueryCacher } from "../utils/queryCacher"; import axios from "axios"; @@ -36,6 +36,7 @@ interface FinalResponse { interface VoteData { UUID: string; nonAnonUserID: string; + originalType: VoteType; voteTypeEnum: number; isTempVIP: boolean; isVIP: boolean; @@ -112,7 +113,9 @@ async function sendWebhooks(voteData: VoteData) { if (submissionInfoRow !== undefined && userSubmissionCountRow != undefined) { let webhookURL: string = null; - if (voteData.voteTypeEnum === voteTypes.normal) { + if (voteData.originalType === VoteType.Malicious) { + webhookURL = config.discordMaliciousReportWebhookURL; + } else if (voteData.voteTypeEnum === voteTypes.normal) { switch (voteData.finalResponse.webhookType) { case VoteWebhookType.Normal: webhookURL = config.discordReportChannelWebhookURL; @@ -329,6 +332,8 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID return { status: 200 }; } + const originalType = type; + //hash the userID const nonAnonUserID = await getHashCache(paramUserID); const userID = await getHashCache(paramUserID + UUID); @@ -421,13 +426,13 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID let incrementAmount = 0; let oldIncrementAmount = 0; - if (type == 1) { + if (type == VoteType.Upvote) { //upvote incrementAmount = 1; - } else if (type == 0) { + } else if (type === VoteType.Downvote || type === VoteType.Malicious) { //downvote incrementAmount = -1; - } else if (type == 20) { + } else if (type == VoteType.Undo) { //undo/cancel vote incrementAmount = 0; } else { @@ -435,17 +440,13 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID return { status: 400 }; } if (votesRow) { - if (votesRow.type === 1) { - //upvote + if (votesRow.type === VoteType.Upvote) { oldIncrementAmount = 1; - } else if (votesRow.type === 0) { - //downvote + } else if (votesRow.type === VoteType.Downvote) { oldIncrementAmount = -1; - } else if (votesRow.type === 2) { - //extra downvote + } else if (votesRow.type === VoteType.ExtraDownvote) { oldIncrementAmount = -4; - } else if (votesRow.type === 20) { - //undo/cancel vote + } else if (votesRow.type === VoteType.Undo) { oldIncrementAmount = 0; } else if (votesRow.type < 0) { //vip downvote @@ -466,8 +467,14 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID type = incrementAmount; } + if (type === VoteType.Malicious) { + incrementAmount = -Math.min(segmentInfo.votes + 2 - oldIncrementAmount, 5); + type = incrementAmount; + } + // Only change the database if they have made a submission before and haven't voted recently const userAbleToVote = (!(isOwnSubmission && incrementAmount > 0 && oldIncrementAmount >= 0) + && !(originalType === VoteType.Malicious && segmentInfo.actionType !== ActionType.Chapter) && !finalResponse.blockVote && finalResponse.finalStatus === 200 && (await db.prepare("get", `SELECT "userID" FROM "sponsorTimes" WHERE "userID" = ?`, [nonAnonUserID])) !== undefined @@ -480,9 +487,9 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID if (ableToVote) { //update the votes table if (votesRow) { - await privateDB.prepare("run", `UPDATE "votes" SET "type" = ? WHERE "userID" = ? AND "UUID" = ?`, [type, userID, UUID]); + await privateDB.prepare("run", `UPDATE "votes" SET "type" = ?, "originalType" = ? WHERE "userID" = ? AND "UUID" = ?`, [type, originalType, userID, UUID]); } else { - await privateDB.prepare("run", `INSERT INTO "votes" VALUES(?, ?, ?, ?, ?)`, [UUID, userID, hashedIP, type, nonAnonUserID]); + await privateDB.prepare("run", `INSERT INTO "votes" VALUES(?, ?, ?, ?, ?, ?)`, [UUID, userID, hashedIP, type, nonAnonUserID, originalType]); } // update the vote count on this sponsorTime @@ -510,6 +517,7 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID sendWebhooks({ UUID, nonAnonUserID, + originalType, voteTypeEnum, isTempVIP, isVIP, diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 774bd01..a9f2532 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -24,6 +24,7 @@ export interface SBSConfig { discordFailedReportChannelWebhookURL?: string; discordFirstTimeSubmissionsWebhookURL?: string; discordCompletelyIncorrectReportWebhookURL?: string; + discordMaliciousReportWebhookURL?: string; neuralBlockURL?: string; discordNeuralBlockRejectWebhookURL?: string; userCounterURL?: string; diff --git a/src/types/segments.model.ts b/src/types/segments.model.ts index 5edb3e0..f657d6c 100644 --- a/src/types/segments.model.ts +++ b/src/types/segments.model.ts @@ -120,3 +120,12 @@ export enum SortableFields { votes = "votes", views = "views", } + + +export enum VoteType { + Downvote = 0, + Upvote = 1, + ExtraDownvote = 2, + Undo = 20, + Malicious = 30 +} \ No newline at end of file diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index 9fad1db..ebaf2eb 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -67,6 +67,8 @@ describe("voteOnSponsorTime", () => { await db.prepare("run", insertSponsorTimeQuery, ["duration-changed", 1, 12, 0, 0, "duration-changed-uuid-3", "testman", 20, 0, "sponsor", "skip", 0, 0]); // add videoDuration to duration-changed-uuid-2 await db.prepare("run", `UPDATE "sponsorTimes" SET "videoDuration" = 150 WHERE "UUID" = 'duration-changed-uuid-2'`); + await db.prepare("run", insertSponsorTimeQuery, ["chapter-video", 1, 10, 0, 0, "chapter-uuid-1", "testman", 0, 0, "chapter", "chapter", 0, 0]); + await db.prepare("run", insertSponsorTimeQuery, ["chapter-video", 1, 10, 0, 0, "non-chapter-uuid-2", "testman", 0, 0, "sponsor", "skip", 0, 0]); const insertWarningQuery = 'INSERT INTO "warnings" ("userID", "issueTime", "issuerUserID", "enabled") VALUES(?, ?, ?, ?)'; await db.prepare("run", insertWarningQuery, [warnUser01Hash, now, warnVip01Hash, 1]); @@ -223,6 +225,30 @@ describe("voteOnSponsorTime", () => { .catch(err => done(err)); }); + it("should be able to completely downvote chapter using malicious", (done) => { + const UUID = "chapter-uuid-1"; + postVote(randomID2, UUID, 30) + .then(async res => { + assert.strictEqual(res.status, 200); + const row = await getSegmentVotes(UUID); + assert.strictEqual(row.votes, -2); + done(); + }) + .catch(err => done(err)); + }); + + it("should not be able to completely downvote non-chapter using malicious", (done) => { + const UUID = "non-chapter-uuid-2"; + postVote(randomID2, UUID, 30) + .then(async res => { + assert.strictEqual(res.status, 200); + const row = await getSegmentVotes(UUID); + assert.strictEqual(row.votes, 0); + done(); + }) + .catch(err => done(err)); + }); + it("Should be able to vote for a category and it should add your vote to the database", (done) => { const UUID = "vote-uuid-4"; postVoteCategory(randomID2, UUID, "intro") From 0f3df8db1b7e68a18b64cc9a7fb6c4a1ffc6fab8 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 4 Jul 2022 16:21:02 -0400 Subject: [PATCH 053/168] make temp vip as powerful as vip for submitting --- src/routes/postSkipSegments.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 8ccee51..1d2fecd 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -492,8 +492,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise Date: Mon, 4 Jul 2022 23:44:50 -0400 Subject: [PATCH 054/168] Add canSubmitChapter and fix all userInfo functions running --- src/config.ts | 1 + src/routes/getUserInfo.ts | 25 +++++++++++++++---------- src/types/config.model.ts | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/config.ts b/src/config.ts index 1a62374..ee9e26f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -43,6 +43,7 @@ addDefaults(config, { discordFailedReportChannelWebhookURL: null, discordReportChannelWebhookURL: null, discordMaliciousReportWebhookURL: null, + minReputationToSubmitChapter: 0, getTopUsersCacheTimeMinutes: 240, globalSalt: null, mode: "", diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index 37aa678..dbede73 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -105,6 +105,10 @@ async function dbGetBanned(userID: HashedUserID): Promise { } } +async function dbCanSubmitChapter(userID: HashedUserID): Promise { + return (await isUserVIP(userID)) || (await getReputation(userID)) > config.minReputationToSubmitChapter; +} + type cases = Record const executeIfFunction = (f: any) => @@ -119,16 +123,17 @@ const functionSwitch = (cases: cases) => (defaultCase: string) => (key: string) const dbGetValue = (userID: HashedUserID, property: string): Promise => { return functionSwitch({ userID, - userName: dbGetUsername(userID), - ignoredSegmentCount: dbGetIgnoredSegmentCount(userID), - viewCount: dbGetViewsForUser(userID), - ignoredViewCount: dbGetIgnoredViewsForUser(userID), - warnings: dbGetWarningsForUser(userID), - warningReason: dbGetActiveWarningReasonForUser(userID), - banned: dbGetBanned(userID), - reputation: getReputation(userID), - vip: isUserVIP(userID), - lastSegmentID: dbGetLastSegmentForUser(userID), + userName: () => dbGetUsername(userID), + ignoredSegmentCount: () => dbGetIgnoredSegmentCount(userID), + viewCount: () => dbGetViewsForUser(userID), + ignoredViewCount: () => dbGetIgnoredViewsForUser(userID), + warnings: () => dbGetWarningsForUser(userID), + warningReason: () => dbGetActiveWarningReasonForUser(userID), + banned: () => dbGetBanned(userID), + reputation: () => getReputation(userID), + vip: () => isUserVIP(userID), + lastSegmentID: () => dbGetLastSegmentForUser(userID), + canSubmitChapter: () => dbCanSubmitChapter(userID) })("")(property); }; diff --git a/src/types/config.model.ts b/src/types/config.model.ts index a9f2532..9030e69 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -27,6 +27,7 @@ export interface SBSConfig { discordMaliciousReportWebhookURL?: string; neuralBlockURL?: string; discordNeuralBlockRejectWebhookURL?: string; + minReputationToSubmitChapter: number; userCounterURL?: string; proxySubmission?: string; behindProxy: string | boolean; From 8ed695bcdcacc87e23d4089b6872c13b364a8f1d Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 4 Jul 2022 23:45:52 -0400 Subject: [PATCH 055/168] Fix canSubmitChapter being filtered out --- src/routes/getUserInfo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index dbede73..15d7c8d 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -143,7 +143,7 @@ async function getUserInfo(req: Request, res: Response): Promise { const defaultProperties: string[] = ["userID", "userName", "minutesSaved", "segmentCount", "ignoredSegmentCount", "viewCount", "ignoredViewCount", "warnings", "warningReason", "reputation", "vip", "lastSegmentID"]; - const allProperties: string[] = [...defaultProperties, "banned"]; + const allProperties: string[] = [...defaultProperties, "banned", "canSubmitChapter"]; let paramValues: string[] = req.query.values ? JSON.parse(req.query.values as string) : req.query.value From 7d396f3782e2f13e69cf37d7012de4f4b383a4f4 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 5 Jul 2022 19:23:02 -0400 Subject: [PATCH 056/168] Do more things in parallel in getSkipSegments --- src/routes/getSkipSegments.ts | 17 ++++++++++------- src/types/segments.model.ts | 1 + 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index a9500f8..0cac607 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -27,6 +27,10 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, service: if (cache.shadowHiddenSegmentIPs[videoID] === undefined) cache.shadowHiddenSegmentIPs[videoID] = {}; if (cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted] === undefined) { + if (cache.userHashedIP === undefined && cache.userHashedIPPromise === undefined) { + cache.userHashedIPPromise = getHashCache((getIP(req) + config.globalSalt) as IPAddress); + } + const service = getService(req?.query?.service as string); const fetchData = () => privateDB.prepare("all", 'SELECT "hashedIP" FROM "sponsorTimes" WHERE "videoID" = ? AND "timeSubmitted" = ? AND "service" = ?', [videoID, segment.timeSubmitted, service]) as Promise<{ hashedIP: HashedIP }[]>; @@ -35,9 +39,8 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, service: const ipList = cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted]; - if (ipList?.length > 0 && cache.userHashedIP === undefined) { - //hash the IP only if it's strictly necessary - cache.userHashedIP = await getHashCache((getIP(req) + config.globalSalt) as IPAddress); + if (ipList?.length > 0 && cache.userHashedIP === undefined && cache.userHashedIPPromise) { + cache.userHashedIP = await cache.userHashedIPPromise; } //if this isn't their ip, don't send it to them const shouldShadowHide = cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted]?.some( @@ -133,7 +136,7 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, return acc; }, {}); - for (const [videoID, videoData] of Object.entries(segmentPerVideoID)) { + await Promise.all(Object.entries(segmentPerVideoID).map(async ([videoID, videoData]) => { const data: VideoData = { hash: videoData.hash, segments: [], @@ -153,7 +156,7 @@ async function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, if (data.segments.length > 0) { segments[videoID] = data; } - } + })); return segments; } catch (err) { @@ -283,7 +286,7 @@ async function buildSegmentGroups(segments: DBSegment[]): Promise { if (segment.startTime >= cursor) { currentGroup = { segments: [], votes: 0, reputation: 0, locked: false, required: false }; overlappingSegmentsGroups.push(currentGroup); @@ -309,7 +312,7 @@ async function buildSegmentGroups(segments: DBSegment[]): Promise { diff --git a/src/types/segments.model.ts b/src/types/segments.model.ts index f657d6c..324d196 100644 --- a/src/types/segments.model.ts +++ b/src/types/segments.model.ts @@ -101,6 +101,7 @@ export interface VideoData { export interface SegmentCache { shadowHiddenSegmentIPs: SBRecord>, userHashedIP?: HashedIP + userHashedIPPromise?: Promise; } export interface DBLock { From adca0256a093b480a1ec9dafd791e11ff78ddab4 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 5 Jul 2022 19:30:45 -0400 Subject: [PATCH 057/168] Fix voting insert with postgres --- src/routes/voteOnSponsorTime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 002b63b..941189f 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -489,7 +489,7 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID if (votesRow) { await privateDB.prepare("run", `UPDATE "votes" SET "type" = ?, "originalType" = ? WHERE "userID" = ? AND "UUID" = ?`, [type, originalType, userID, UUID]); } else { - await privateDB.prepare("run", `INSERT INTO "votes" VALUES(?, ?, ?, ?, ?, ?)`, [UUID, userID, hashedIP, type, nonAnonUserID, originalType]); + await privateDB.prepare("run", `INSERT INTO "votes" ("UUID", "userID", "hashedIP", "type", "normalUserID", "originalType") VALUES(?, ?, ?, ?, ?, ?)`, [UUID, userID, hashedIP, type, nonAnonUserID, originalType]); } // update the vote count on this sponsorTime From 47f460bb2c977633bb4056fe6a5f5ade5e8789db Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 5 Jul 2022 20:11:30 -0400 Subject: [PATCH 058/168] Fix non-sequencial reputation in getSkipSegments --- src/routes/getSkipSegments.ts | 12 ++++++--- test/cases/getSkipSegmentsByHash.ts | 40 ++++++++++++++--------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 0cac607..368d250 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -39,7 +39,7 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, service: const ipList = cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted]; - if (ipList?.length > 0 && cache.userHashedIP === undefined && cache.userHashedIPPromise) { + if (ipList?.length > 0 && cache.userHashedIP === undefined) { cache.userHashedIP = await cache.userHashedIPPromise; } //if this isn't their ip, don't send it to them @@ -278,6 +278,9 @@ async function chooseSegments(videoID: VideoID, service: Service, segments: DBSe //This allows new less voted items to still sometimes appear to give them a chance at getting votes. //Segments with less than -1 votes are already ignored before this function is called async function buildSegmentGroups(segments: DBSegment[]): Promise { + const reputationPromises = segments.map(segment => + segment.userID ? getReputation(segment.userID) : null); + //Create groups of segments that are similar to eachother //Segments must be sorted by their startTime so that we can build groups chronologically: //1. As long as the segments' startTime fall inside the currentGroup, we keep adding them to that group @@ -286,7 +289,8 @@ async function buildSegmentGroups(segments: DBSegment[]): Promise { + for (let i = 0; i < segments.length; i++) { + const segment = segments[i]; if (segment.startTime >= cursor) { currentGroup = { segments: [], votes: 0, reputation: 0, locked: false, required: false }; overlappingSegmentsGroups.push(currentGroup); @@ -298,7 +302,7 @@ async function buildSegmentGroups(segments: DBSegment[]): Promise 0) { currentGroup.reputation += segment.reputation; } @@ -312,7 +316,7 @@ async function buildSegmentGroups(segments: DBSegment[]): Promise { diff --git a/test/cases/getSkipSegmentsByHash.ts b/test/cases/getSkipSegmentsByHash.ts index 4d18a0a..098f9c0 100644 --- a/test/cases/getSkipSegmentsByHash.ts +++ b/test/cases/getSkipSegmentsByHash.ts @@ -150,7 +150,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/fdaf`, { params: { categories: `["sponsor","intro"]` } }) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 2); assert.strictEqual(data[0].segments.length, 2); assert.strictEqual(data[1].segments.length, 1); @@ -163,15 +163,15 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/fdaf`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); const expected = [{ segments: [{ category: "sponsor", - UUID: "getSegmentsByHash-01", + UUID: "getSegmentsByHash-01" }] }, { segments: [{ - category: "sponsor", + category: "sponsor" }] }]; assert.strictEqual(data.length, 2); @@ -187,7 +187,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/fdaf`, { params: { actionType: "skip" } }) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 2); assert.strictEqual(data[0].segments.length, 1); assert.strictEqual(data[1].segments.length, 1); @@ -211,7 +211,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/fdaf?actionType=skip&actionType=mute`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 2); assert.strictEqual(data[0].segments.length, 2); assert.strictEqual(data[1].segments.length, 1); @@ -237,7 +237,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/fdaf?actionTypes=["skip","mute"]`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 2); const expected = [{ segments: [{ @@ -261,7 +261,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/fdaf`, { params: { service: "PeerTube" } }) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); const expected = [{ segments: [{ @@ -279,7 +279,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/c962`, { params: { category: "poi_highlight", actionType: "poi" } }) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); assert.strictEqual(data[0].segments.length, 1); assert.strictEqual(data[0].segments[0].category, "poi_highlight"); @@ -293,7 +293,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/c962`, { params: { category: "poi_highlight" } }) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); assert.strictEqual(data[0].segments.length, 1); assert.strictEqual(data[0].segments[0].category, "poi_highlight"); @@ -317,7 +317,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/${getHash(testID, 1).substring(0, 3)}`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); const expected = [{ segments: [{ @@ -337,7 +337,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/fdaff4?&category=sponsor&category=intro`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); const expected = [{ segments: [{ @@ -360,7 +360,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/d518?requiredSegments=["requiredSegmentVid-2","requiredSegmentVid-3"]`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); const expected = [{ segments: [{ @@ -380,7 +380,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/d518?requiredSegment=requiredSegmentVid-2&requiredSegment=requiredSegmentVid-3`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); assert.strictEqual(data[0].segments.length, 2); const expected = [{ @@ -400,7 +400,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/7258?category=chapter&actionType=chapter`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); const expected = [{ segments: [{ @@ -432,7 +432,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/6613?actionType=skip&actionType=mute`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); const expected = [{ segments: [{ @@ -490,7 +490,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/3061?categories=["sponsor","music_offtopic"]`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); const expected = [{ segments: [{ @@ -510,7 +510,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/ab0c?actionType=skip&actionType=mute`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); const expected = [{ segments: [{ @@ -551,7 +551,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/278f`, { params: { category: ["sponsor", "selfpromo"], actionType: "full" } }) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); assert.strictEqual(data[0].segments.length, 1); assert.strictEqual(data[0].segments[0].category, "selfpromo"); @@ -566,7 +566,7 @@ describe("getSkipSegmentsByHash", () => { client.get(`${endpoint}/17bf?requiredSegments=["${requiredSegment1.slice(0,8)}","${requiredSegment2.slice(0,8)}"]`) .then(res => { assert.strictEqual(res.status, 200); - const data = res.data; + const data = (res.data as Array).sort((a, b) => a.videoID.localeCompare(b.videoID)); assert.strictEqual(data.length, 1); const expected = [{ segments: [{ From c2b0ecd6f6066f58536acb8d03d19e55b5613e32 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 6 Jul 2022 00:11:45 -0400 Subject: [PATCH 059/168] Add ability to add manually choose who can submit chapters --- databases/_sponsorTimes_indexes.sql | 7 +++ databases/_upgrade_sponsorTimes_33.sql | 13 +++++ src/app.ts | 3 ++ src/routes/addFeature.ts | 72 ++++++++++++++++++++++++++ src/routes/getUserInfo.ts | 7 +-- src/routes/postSkipSegments.ts | 16 ++++-- src/types/user.model.ts | 6 ++- src/utils/features.ts | 11 ++++ src/utils/permissions.ts | 11 ++++ src/utils/queryCacher.ts | 11 ++-- src/utils/redisKeys.ts | 8 ++- test/cases/addFeatures.ts | 68 ++++++++++++++++++++++++ test/cases/oldSubmitSponsorTimes.ts | 13 ++++- test/cases/postSkipSegments.ts | 68 ++++++++++++++++++++++-- 14 files changed, 292 insertions(+), 22 deletions(-) create mode 100644 databases/_upgrade_sponsorTimes_33.sql create mode 100644 src/routes/addFeature.ts create mode 100644 src/utils/features.ts create mode 100644 src/utils/permissions.ts create mode 100644 test/cases/addFeatures.ts diff --git a/databases/_sponsorTimes_indexes.sql b/databases/_sponsorTimes_indexes.sql index e028547..4f5c52c 100644 --- a/databases/_sponsorTimes_indexes.sql +++ b/databases/_sponsorTimes_indexes.sql @@ -108,4 +108,11 @@ CREATE INDEX IF NOT EXISTS "ratings_hashedVideoID" CREATE INDEX IF NOT EXISTS "ratings_videoID" ON public."ratings" USING btree ("videoID" COLLATE pg_catalog."default" ASC NULLS LAST, service COLLATE pg_catalog."default" ASC NULLS LAST) + TABLESPACE pg_default; + +--- userFeatures + +CREATE INDEX IF NOT EXISTS "userFeatures_userID" + ON public."userFeatures" USING btree + ("userID" COLLATE pg_catalog."default" ASC NULLS LAST, "feature" ASC NULLS LAST) TABLESPACE pg_default; \ No newline at end of file diff --git a/databases/_upgrade_sponsorTimes_33.sql b/databases/_upgrade_sponsorTimes_33.sql new file mode 100644 index 0000000..7d2c9ff --- /dev/null +++ b/databases/_upgrade_sponsorTimes_33.sql @@ -0,0 +1,13 @@ +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS "userFeatures" ( + "userID" TEXT NOT NULL, + "feature" INTEGER NOT NULL, + "issuerUserID" TEXT NOT NULL, + "timeSubmitted" INTEGER NOT NULL, + PRIMARY KEY ("userID", "feature") +); + +UPDATE "config" SET value = 33 WHERE key = 'version'; + +COMMIT; \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 77f1c2b..1aa7739 100644 --- a/src/app.ts +++ b/src/app.ts @@ -48,6 +48,7 @@ import { getRating } from "./routes/ratings/getRating"; import { postClearCache as ratingPostClearCache } from "./routes/ratings/postClearCache"; import { getTopCategoryUsers } from "./routes/getTopCategoryUsers"; import { addUserAsTempVIP } from "./routes/addUserAsTempVIP"; +import { addFeature } from "./routes/addFeature"; export function createServer(callback: () => void): Server { // Create a service (the app object is just a callback). @@ -196,6 +197,8 @@ function setupRoutes(router: Router) { router.get("/api/lockReason", getLockReason); + router.post("/api/feature", addFeature) + // ratings router.get("/api/ratings/rate/:prefix", getRating); router.get("/api/ratings/rate", getRating); diff --git a/src/routes/addFeature.ts b/src/routes/addFeature.ts new file mode 100644 index 0000000..8319015 --- /dev/null +++ b/src/routes/addFeature.ts @@ -0,0 +1,72 @@ +import { getHashCache } from "../utils/getHashCache"; +import { db } from "../databases/databases"; +import { config } from "../config"; +import { Request, Response } from "express"; +import { isUserVIP } from "../utils/isUserVIP"; +import { Feature, HashedUserID } from "../types/user.model"; +import { Logger } from "../utils/logger"; +import { QueryCacher } from "../utils/queryCacher"; + +interface AddFeatureRequest extends Request { + body: { + userID: HashedUserID; + adminUserID: string; + feature: string; + enabled: string; + } +} + +const allowedFeatures = { + vip: [ + Feature.ChapterSubmitter + ], + admin: [ + Feature.ChapterSubmitter + ] +} + +export async function addFeature(req: AddFeatureRequest, res: Response): Promise { + const { body: { userID, adminUserID } } = req; + const feature = parseInt(req.body.feature) as Feature; + const enabled = req.body?.enabled !== "false"; + + if (!userID || !adminUserID) { + // invalid request + return res.sendStatus(400); + } + + // hash the userID + const adminUserIDInput = await getHashCache(adminUserID); + const isAdmin = adminUserIDInput !== config.adminUserID; + const isVIP = (await isUserVIP(userID)) || isAdmin; + + if (!isAdmin && !isVIP) { + // not authorized + return res.sendStatus(403); + } + + try { + const currentAllowedFeatures = isAdmin ? allowedFeatures.admin : allowedFeatures.vip; + if (currentAllowedFeatures.includes(feature)) { + if (enabled) { + const featureAdded = await db.prepare("get", 'SELECT "feature" from "userFeatures" WHERE "userID" = ? AND "feature" = ?', [userID, feature]); + if (!featureAdded) { + await db.prepare("run", 'INSERT INTO "userFeatures" ("userID", "feature", "issuerUserID", "timeSubmitted") VALUES(?, ?, ?, ?)' + , [userID, feature, adminUserID, Date.now()]); + } + } else { + await db.prepare("run", 'DELETE FROM "userFeatures" WHERE "userID" = ? AND "feature" = ?', [userID, feature]); + } + + QueryCacher.clearFeatureCache(userID, feature); + } else { + return res.status(400).send("Invalid feature"); + } + + return res.sendStatus(200); + } catch (e) { + Logger.error(e as string); + + return res.sendStatus(500); + } +} diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index 15d7c8d..0c913c3 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -7,6 +7,7 @@ import { HashedUserID, UserID } from "../types/user.model"; import { getReputation } from "../utils/reputation"; import { SegmentUUID } from "../types/segments.model"; import { config } from "../config"; +import { canSubmitChapter } from "../utils/permissions"; const maxRewardTime = config.maxRewardTimePerSegmentInSeconds; async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ minutesSaved: number, segmentCount: number }> { @@ -105,10 +106,6 @@ async function dbGetBanned(userID: HashedUserID): Promise { } } -async function dbCanSubmitChapter(userID: HashedUserID): Promise { - return (await isUserVIP(userID)) || (await getReputation(userID)) > config.minReputationToSubmitChapter; -} - type cases = Record const executeIfFunction = (f: any) => @@ -133,7 +130,7 @@ const dbGetValue = (userID: HashedUserID, property: string): Promise getReputation(userID), vip: () => isUserVIP(userID), lastSegmentID: () => dbGetLastSegmentForUser(userID), - canSubmitChapter: () => dbCanSubmitChapter(userID) + canSubmitChapter: () => canSubmitChapter(userID) })("")(property); }; diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 1d2fecd..cdded50 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -21,6 +21,7 @@ import { parseUserAgent } from "../utils/userAgent"; import { getService } from "../utils/getService"; import axios from "axios"; import { vote } from "./voteOnSponsorTime"; +import { canSubmitChapter } from "../utils/permissions"; type CheckResult = { pass: boolean, @@ -200,7 +201,8 @@ async function checkUserActiveWarning(userID: string): Promise { return CHECK_PASS; } -function checkInvalidFields(videoID: VideoID, userID: UserID, segments: IncomingSegment[]): CheckResult { +async function checkInvalidFields(videoID: VideoID, userID: UserID, hashedUserID: HashedUserID + , segments: IncomingSegment[]): Promise { const invalidFields = []; const errors = []; if (typeof videoID !== "string" || videoID?.length == 0) { @@ -227,6 +229,10 @@ function checkInvalidFields(videoID: VideoID, userID: UserID, segments: Incoming || (segmentPair.description.length !== 0 && segmentPair.actionType !== ActionType.Chapter)) { invalidFields.push("segment description"); } + + if (segmentPair.actionType === ActionType.Chapter && !(await canSubmitChapter(hashedUserID))) { + invalidFields.push("permission to submit chapters"); + } } if (invalidFields.length !== 0) { @@ -478,14 +484,14 @@ export async function postSkipSegments(req: Request, res: Response): Promise((prev, val) => `${prev} ${val.category}`, "")}', times: ${segments.reduce((prev, val) => `${prev} ${val.segment}`, "")}`); diff --git a/src/types/user.model.ts b/src/types/user.model.ts index 102126a..355e8bb 100644 --- a/src/types/user.model.ts +++ b/src/types/user.model.ts @@ -1,4 +1,8 @@ import { HashedValue } from "./hash.model"; export type UserID = string & { __userIDBrand: unknown }; -export type HashedUserID = UserID & HashedValue; \ No newline at end of file +export type HashedUserID = UserID & HashedValue; + +export enum Feature { + ChapterSubmitter = 0 +} \ No newline at end of file diff --git a/src/utils/features.ts b/src/utils/features.ts new file mode 100644 index 0000000..4f69166 --- /dev/null +++ b/src/utils/features.ts @@ -0,0 +1,11 @@ +import { db } from "../databases/databases"; +import { Feature, HashedUserID } from "../types/user.model"; +import { QueryCacher } from "./queryCacher"; +import { userFeatureKey } from "./redisKeys"; + +export async function hasFeature(userID: HashedUserID, feature: Feature): Promise { + return await QueryCacher.get(async () => { + const result = await db.prepare("get", 'SELECT "feature" from "userFeatures" WHERE "userID" = ? AND "feature" = ?', [userID, feature]); + return !!result; + }, userFeatureKey(userID, feature)); +} \ No newline at end of file diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts new file mode 100644 index 0000000..4269ed0 --- /dev/null +++ b/src/utils/permissions.ts @@ -0,0 +1,11 @@ +import { config } from "../config"; +import { Feature, HashedUserID } from "../types/user.model"; +import { hasFeature } from "./features"; +import { isUserVIP } from "./isUserVIP"; +import { getReputation } from "./reputation"; + +export async function canSubmitChapter(userID: HashedUserID): Promise { + return (await isUserVIP(userID)) + || (await getReputation(userID)) > config.minReputationToSubmitChapter + || (await hasFeature(userID, Feature.ChapterSubmitter)); +} \ No newline at end of file diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index 434d898..d4f8647 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -1,8 +1,8 @@ import redis from "../utils/redis"; import { Logger } from "../utils/logger"; -import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey } from "./redisKeys"; +import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey, userFeatureKey } from "./redisKeys"; import { Service, VideoID, VideoIDHash } from "../types/segments.model"; -import { UserID } from "../types/user.model"; +import { Feature, HashedUserID, UserID } from "../types/user.model"; async function get(fetchFromDB: () => Promise, key: string): Promise { try { @@ -90,9 +90,14 @@ function clearRatingCache(videoInfo: { hashedVideoID: VideoIDHash; service: Serv } } +function clearFeatureCache(userID: HashedUserID, feature: Feature): void { + redis.del(userFeatureKey(userID, feature)).catch((err) => Logger.error(err)); +} + export const QueryCacher = { get, getAndSplit, clearSegmentCache, - clearRatingCache + clearRatingCache, + clearFeatureCache }; \ No newline at end of file diff --git a/src/utils/redisKeys.ts b/src/utils/redisKeys.ts index 2da45a8..86cfcef 100644 --- a/src/utils/redisKeys.ts +++ b/src/utils/redisKeys.ts @@ -1,5 +1,5 @@ import { Service, VideoID, VideoIDHash } from "../types/segments.model"; -import { HashedUserID, UserID } from "../types/user.model"; +import { Feature, HashedUserID, UserID } from "../types/user.model"; import { HashedValue } from "../types/hash.model"; import { Logger } from "./logger"; @@ -36,4 +36,8 @@ export function shaHashKey(singleIter: HashedValue): string { } export const tempVIPKey = (userID: HashedUserID): string => - `vip.temp.${userID}`; \ No newline at end of file + `vip.temp.${userID}`; + +export function userFeatureKey (userID: HashedUserID, feature: Feature): string { + return `user.${userID}.feature.${feature}`; +} \ No newline at end of file diff --git a/test/cases/addFeatures.ts b/test/cases/addFeatures.ts new file mode 100644 index 0000000..9b2c574 --- /dev/null +++ b/test/cases/addFeatures.ts @@ -0,0 +1,68 @@ +import assert from "assert"; +import { db } from "../../src/databases/databases"; +import { Feature, HashedUserID } from "../../src/types/user.model"; +import { hasFeature } from "../../src/utils/features"; +import { getHash } from "../../src/utils/getHash"; +import { client } from "../utils/httpClient"; + +const endpoint = "/api/feature"; + +const postAddFeatures = (userID: string, adminUserID: string, feature: Feature, enabled: string) => client({ + method: "POST", + url: endpoint, + data: { + userID, + feature, + enabled, + adminUserID + } +}); + +const privateVipUserID = "VIPUser-addFeatures"; +const vipUserID = getHash(privateVipUserID); + +const hashedUserID1 = "user1-addFeatures" as HashedUserID; +const hashedUserID2 = "user2-addFeatures" as HashedUserID; +const hashedUserID3 = "user3-addFeatures" as HashedUserID; + +const validFeatures = [Feature.ChapterSubmitter]; + +describe("addFeatures", () => { + before(() => { + const userFeatureQuery = `INSERT INTO "userFeatures" ("userID", "feature", "issuerUserID", "timeSubmitted") VALUES(?, ?, ?, ?)`; + + return Promise.all([ + db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES (?)`, [vipUserID]), + + db.prepare("run", userFeatureQuery, [hashedUserID2, Feature.ChapterSubmitter, "some-user", 0]), + db.prepare("run", userFeatureQuery, [hashedUserID3, Feature.ChapterSubmitter, "some-user", 0]) + ]); + }); + + it("can add features", async () => { + for (const feature of validFeatures) { + const result = await postAddFeatures(hashedUserID1, vipUserID, feature, "true"); + assert.strictEqual(result.status, 200); + + assert.strictEqual(await hasFeature(hashedUserID1, feature), true); + } + }); + + it("can remove features", async () => { + const feature = Feature.ChapterSubmitter; + + const result = await postAddFeatures(hashedUserID2, vipUserID, feature, "false"); + assert.strictEqual(result.status, 200); + + assert.strictEqual(await hasFeature(hashedUserID2, feature), false); + }); + + it("can update features", async () => { + const feature = Feature.ChapterSubmitter; + + const result = await postAddFeatures(hashedUserID3, vipUserID, feature, "true"); + assert.strictEqual(result.status, 200); + + assert.strictEqual(await hasFeature(hashedUserID3, feature), true); + }); +}); \ No newline at end of file diff --git a/test/cases/oldSubmitSponsorTimes.ts b/test/cases/oldSubmitSponsorTimes.ts index ca5edf6..b1f6d20 100644 --- a/test/cases/oldSubmitSponsorTimes.ts +++ b/test/cases/oldSubmitSponsorTimes.ts @@ -44,8 +44,17 @@ describe("postVideoSponsorTime (Old submission method)", () => { .catch(err => done(err)); }); - it("Should return 400 for missing params", (done) => { - client.post(endpoint, { params: { startTime: 1, endTime: 10, userID } }) + it("Should return 400 for missing video", (done) => { + client.get(endpoint, { params: { startTime: 1, endTime: 10, userID } }) + .then(res => { + assert.strictEqual(res.status, 400); + done(); + }) + .catch(err => done(err)); + }); + + it("Should return 400 for missing userID", (done) => { + client.get(endpoint, { params: { videoID: videoID1, startTime: 1, endTime: 10 } }) .then(res => { assert.strictEqual(res.status, 400); done(); diff --git a/test/cases/postSkipSegments.ts b/test/cases/postSkipSegments.ts index 4e5ff5d..47ccff2 100644 --- a/test/cases/postSkipSegments.ts +++ b/test/cases/postSkipSegments.ts @@ -7,6 +7,7 @@ import * as YouTubeAPIModule from "../../src/utils/youtubeApi"; import { YouTubeApiMock } from "../youtubeMock"; import assert from "assert"; import { client } from "../utils/httpClient"; +import { Feature } from "../../src/types/user.model"; const mockManager = ImportMock.mockStaticClass(YouTubeAPIModule, "YouTubeAPI"); const sinonStub = mockManager.mock("listVideos"); @@ -15,6 +16,7 @@ sinonStub.callsFake(YouTubeApiMock.listVideos); describe("postSkipSegments", () => { // Constant and helpers const submitUserOne = `PostSkipUser1${".".repeat(18)}`; + const submitUserOneHash = getHash(submitUserOne); const submitUserTwo = `PostSkipUser2${".".repeat(18)}`; const submitUserTwoHash = getHash(submitUserTwo); const submitUserThree = `PostSkipUser3${".".repeat(18)}`; @@ -30,7 +32,6 @@ describe("postSkipSegments", () => { const banUser01 = "ban-user01-loremipsumdolorsitametconsectetur"; const banUser01Hash = getHash(banUser01); - const submitUserOneHash = getHash(submitUserOne); const submitVIPuser = `VIPPostSkipUser${".".repeat(16)}`; const warnVideoID = "postSkip2"; const badInputVideoID = "dQw4w9WgXcQ"; @@ -66,6 +67,15 @@ describe("postSkipSegments", () => { db.prepare("run", insertSponsorTimeQuery, ["full_video_duration_segment", 0, 0, 0, "full-video-duration-uuid-0", submitUserTwoHash, 0, 0, "sponsor", "full", 123, 0, "full_video_duration_segment"]); db.prepare("run", insertSponsorTimeQuery, ["full_video_duration_segment", 25, 30, 0, "full-video-duration-uuid-1", submitUserTwoHash, 0, 0, "sponsor", "skip", 123, 0, "full_video_duration_segment"]); + const reputationVideoID = "post_reputation_video"; + db.prepare("run", insertSponsorTimeQuery, [reputationVideoID, 1, 11, 2,"post_reputation-5-uuid-0", submitUserOneHash, 1606240000000, 50, "sponsor", "skip", 0, 0, reputationVideoID]); + db.prepare("run", insertSponsorTimeQuery, [reputationVideoID, 1, 11, 2,"post_reputation-5-uuid-1", submitUserOneHash, 1606240000000, 50, "sponsor", "skip", 0, 0, reputationVideoID]); + db.prepare("run", insertSponsorTimeQuery, [reputationVideoID, 1, 11, 2,"post_reputation-5-uuid-2", submitUserOneHash, 1606240000000, 50, "sponsor", "skip", 0, 0, reputationVideoID]); + db.prepare("run", insertSponsorTimeQuery, [reputationVideoID, 1, 11, 2,"post_reputation-5-uuid-3", submitUserOneHash, 1606240000000, 50, "sponsor", "skip", 0, 0, reputationVideoID]); + db.prepare("run", insertSponsorTimeQuery, [reputationVideoID, 1, 11, 2,"post_reputation-5-uuid-4", submitUserOneHash, 1606240000000, 50, "sponsor", "skip", 0, 0, reputationVideoID]); + db.prepare("run", insertSponsorTimeQuery, [reputationVideoID, 1, 11, 0,"post_reputation-5-uuid-6", submitUserOneHash, 1606240000000, 50, "sponsor", "skip", 0, 0, reputationVideoID]); + db.prepare("run", insertSponsorTimeQuery, [reputationVideoID, 1, 11, 0,"post_reputation-5-uuid-7", submitUserOneHash, 1606240000000, 50, "sponsor", "skip", 0, 0, reputationVideoID]); + const now = Date.now(); const warnVip01Hash = getHash("warn-vip01-qwertyuiopasdfghjklzxcvbnm"); const reason01 = "Reason01"; @@ -102,6 +112,9 @@ describe("postSkipSegments", () => { // ban user db.prepare("run", `INSERT INTO "shadowBannedUsers" ("userID") VALUES(?)`, [banUser01Hash]); + + // user feature + db.prepare("run", `INSERT INTO "userFeatures" ("userID", "feature", "issuerUserID", "timeSubmitted") VALUES(?, ?, ?, ?)`, [submitUserTwoHash, Feature.ChapterSubmitter, "some-user", 0]); }); it("Should be able to submit a single time (Params method)", (done) => { @@ -189,7 +202,7 @@ describe("postSkipSegments", () => { .catch(err => done(err)); }); - it("Should be able to submit a single chapter (JSON method)", (done) => { + it("Should be able to submit a single chapter due to reputation (JSON method)", (done) => { const videoID = "postSkipChapter1"; postSkipSegmentJSON({ userID: submitUserOne, @@ -217,6 +230,34 @@ describe("postSkipSegments", () => { .catch(err => done(err)); }); + it("Should be able to submit a single chapter due to user feature (JSON method)", (done) => { + const videoID = "postSkipChapter2"; + postSkipSegmentJSON({ + userID: submitUserTwo, + videoID, + segments: [{ + segment: [0, 10], + category: "chapter", + actionType: "chapter", + description: "This is a chapter" + }], + }) + .then(async res => { + assert.strictEqual(res.status, 200); + const row = await queryDatabaseChapter(videoID); + const expected = { + startTime: 0, + endTime: 10, + category: "chapter", + actionType: "chapter", + description: "This is a chapter" + }; + assert.ok(partialDeepEquals(row, expected)); + done(); + }) + .catch(err => done(err)); + }); + it("Should not be able to submit an music_offtopic with mute action type (JSON method)", (done) => { const videoID = "postSkip4"; postSkipSegmentJSON({ @@ -237,8 +278,27 @@ describe("postSkipSegments", () => { .catch(err => done(err)); }); + it("Should not be able to submit a chapter without permission (JSON method)", (done) => { + const videoID = "postSkipChapter3"; + postSkipSegmentJSON({ + userID: submitUserThree, + videoID, + segments: [{ + segment: [0, 10], + category: "chapter", + actionType: "chapter", + description: "This is a chapter" + }], + }) + .then(res => { + assert.strictEqual(res.status, 400); + done(); + }) + .catch(err => done(err)); + }); + it("Should not be able to submit a chapter with skip action type (JSON method)", (done) => { - const videoID = "postSkipChapter2"; + const videoID = "postSkipChapter4"; postSkipSegmentJSON({ userID: submitUserOne, videoID, @@ -258,7 +318,7 @@ describe("postSkipSegments", () => { }); it("Should not be able to submit a sponsor with a description (JSON method)", (done) => { - const videoID = "postSkipChapter3"; + const videoID = "postSkipChapter5"; postSkipSegmentJSON({ userID: submitUserOne, videoID, From 5b60243b22507f33b79acfdadfd5b846b4c7cc98 Mon Sep 17 00:00:00 2001 From: Michael C Date: Wed, 13 Jul 2022 00:29:40 -0400 Subject: [PATCH 060/168] update packages, use native redis typings --- package-lock.json | 4173 +++++++++++++++----------------------------- package.json | 44 +- src/utils/redis.ts | 8 +- 3 files changed, 1388 insertions(+), 2837 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3aeb3d3..7b9530b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,39 +9,39 @@ "version": "0.1.0", "license": "MIT", "dependencies": { - "axios": "^0.24.0", - "better-sqlite3": "^7.4.5", - "cron": "^1.8.2", - "express": "^4.17.1", + "axios": "^0.27.2", + "better-sqlite3": "^7.6.0", + "cron": "^2.0.0", + "express": "^4.18.1", "express-promise-router": "^4.1.1", - "express-rate-limit": "^6.3.0", + "express-rate-limit": "^6.4.0", "lodash": "^4.17.21", - "pg": "^8.7.1", + "pg": "^8.7.3", "rate-limit-redis": "^3.0.1", - "redis": "^4.0.6", + "redis": "^4.2.0", "sync-mysql": "^3.0.1" }, "devDependencies": { - "@types/better-sqlite3": "^7.4.1", - "@types/cron": "^1.7.3", + "@types/better-sqlite3": "^7.5.0", + "@types/cron": "^2.0.0", "@types/express": "^4.17.13", - "@types/lodash": "^4.14.178", - "@types/mocha": "^9.0.0", - "@types/node": "^16.11.11", - "@types/pg": "^8.6.1", - "@typescript-eslint/eslint-plugin": "^5.5.0", - "@typescript-eslint/parser": "^5.5.0", - "eslint": "^8.3.0", - "mocha": "^9.1.3", - "nodemon": "^2.0.15", + "@types/lodash": "^4.14.182", + "@types/mocha": "^9.1.1", + "@types/node": "^18.0.3", + "@types/pg": "^8.6.5", + "@typescript-eslint/eslint-plugin": "^5.30.6", + "@typescript-eslint/parser": "^5.30.6", + "eslint": "^8.19.0", + "mocha": "^10.0.0", + "nodemon": "^2.0.19", "nyc": "^15.1.0", - "sinon": "^12.0.1", + "sinon": "^14.0.0", "ts-mock-imports": "^1.3.8", - "ts-node": "^10.4.0", - "typescript": "^4.5.2" + "ts-node": "^10.8.2", + "typescript": "^4.7.4" }, "engines": { - "node": ">=10" + "node": ">=16" } }, "node_modules/@ampproject/remapping": { @@ -447,63 +447,67 @@ "node": ">=6.9.0" } }, - "node_modules/@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "dependencies": { - "@cspotcode/source-map-consumer": "0.8.0" + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { "node": ">=12" } }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@eslint/eslintrc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz", - "integrity": "sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.0.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">= 4" + "node": "*" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", - "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.4" }, @@ -681,65 +685,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@node-redis/bloom": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@node-redis/bloom/-/bloom-1.0.1.tgz", - "integrity": "sha512-mXEBvEIgF4tUzdIN89LiYsbi6//EdpFA7L8M+DHCvePXg+bfHWi+ct5VI6nHUFQE5+ohm/9wmgihCH3HSkeKsw==", - "peerDependencies": { - "@node-redis/client": "^1.0.0" - } - }, - "node_modules/@node-redis/client": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@node-redis/client/-/client-1.0.5.tgz", - "integrity": "sha512-ESZ3bd1f+od62h4MaBLKum+klVJfA4wAeLHcVQBkoXa1l0viFesOWnakLQqKg+UyrlJhZmXJWtu0Y9v7iTMrig==", - "dependencies": { - "cluster-key-slot": "1.1.0", - "generic-pool": "3.8.2", - "redis-parser": "3.0.0", - "yallist": "4.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@node-redis/client/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/@node-redis/graph": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@node-redis/graph/-/graph-1.0.0.tgz", - "integrity": "sha512-mRSo8jEGC0cf+Rm7q8mWMKKKqkn6EAnA9IA2S3JvUv/gaWW/73vil7GLNwion2ihTptAm05I9LkepzfIXUKX5g==", - "peerDependencies": { - "@node-redis/client": "^1.0.0" - } - }, - "node_modules/@node-redis/json": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@node-redis/json/-/json-1.0.2.tgz", - "integrity": "sha512-qVRgn8WfG46QQ08CghSbY4VhHFgaTY71WjpwRBGEuqGPfWwfRcIf3OqSpR7Q/45X+v3xd8mvYjywqh0wqJ8T+g==", - "peerDependencies": { - "@node-redis/client": "^1.0.0" - } - }, - "node_modules/@node-redis/search": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@node-redis/search/-/search-1.0.5.tgz", - "integrity": "sha512-MCOL8iCKq4v+3HgEQv8zGlSkZyXSXtERgrAJ4TSryIG/eLFy84b57KmNNa/V7M1Q2Wd2hgn2nPCGNcQtk1R1OQ==", - "peerDependencies": { - "@node-redis/client": "^1.0.0" - } - }, - "node_modules/@node-redis/time-series": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@node-redis/time-series/-/time-series-1.0.2.tgz", - "integrity": "sha512-HGQ8YooJ8Mx7l28tD7XjtB3ImLEjlUxG1wC1PAjxu6hPJqjPshUZxAICzDqDjtIbhDTf48WXXUcx8TQJB1XTKA==", - "peerDependencies": { - "@node-redis/client": "^1.0.0" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -775,13 +720,57 @@ "node": ">= 8" } }, - "node_modules/@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true, + "node_modules/@redis/bloom": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", + "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz", + "integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==", + "dependencies": { + "cluster-key-slot": "1.1.0", + "generic-pool": "3.8.2", + "yallist": "4.0.0" + }, "engines": { - "node": ">=6" + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", + "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", + "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz", + "integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", + "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", + "peerDependencies": { + "@redis/client": "^1.0.0" } }, "node_modules/@sinonjs/commons": { @@ -794,18 +783,18 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, "dependencies": { "@sinonjs/commons": "^1.7.0" } }, "node_modules/@sinonjs/samsam": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", - "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz", + "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==", "dev": true, "dependencies": { "@sinonjs/commons": "^1.6.0", @@ -819,18 +808,6 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "node_modules/@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "dependencies": { - "defer-to-connect": "^1.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@tsconfig/node10": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", @@ -856,9 +833,9 @@ "dev": true }, "node_modules/@types/better-sqlite3": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.4.1.tgz", - "integrity": "sha512-ReUz3npCelyRMEVF1inVnP1rlnCSD4LRwaPvfJViR6ZiSXRVTBtLHU52sdwtbyQJQjo6RRPghlKn9e1PpQFptw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.5.0.tgz", + "integrity": "sha512-G9ZbMjydW2yj1AgiPlUtdgF3a1qNpLJLudc9ynJCeJByS3XFWpmT9LT+VSHrKHFbxb31CvtYwetLTOvG9zdxdg==", "dev": true, "dependencies": { "@types/node": "*" @@ -884,13 +861,13 @@ } }, "node_modules/@types/cron": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@types/cron/-/cron-1.7.3.tgz", - "integrity": "sha512-iPmUXyIJG1Js+ldPYhOQcYU3kCAQ2FWrSkm1FJPoii2eYSn6wEW6onPukNTT0bfiflexNSRPl6KWmAIqS+36YA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/cron/-/cron-2.0.0.tgz", + "integrity": "sha512-xZM08fqvwIXgghtPVkSPKNgC+JoMQ2OHazEvyTKnNf7aWu1aB6/4lBbQFrb03Td2cUGG7ITzMv3mFYnMu6xRaQ==", "dev": true, "dependencies": { - "@types/node": "*", - "moment": ">=2.14.0" + "@types/luxon": "*", + "@types/node": "*" } }, "node_modules/@types/express": { @@ -917,15 +894,21 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, "node_modules/@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "version": "4.14.182", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", + "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", + "dev": true + }, + "node_modules/@types/luxon": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-2.3.2.tgz", + "integrity": "sha512-WOehptuhKIXukSUUkRgGbj2c997Uv/iUgYgII8U7XLJqq9W2oF0kQ6frEznRQbdurioz+L/cdaIm4GutTQfgmA==", "dev": true }, "node_modules/@types/mime": { @@ -935,21 +918,21 @@ "devOptional": true }, "node_modules/@types/mocha": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", - "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", "dev": true }, "node_modules/@types/node": { - "version": "16.11.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.11.tgz", - "integrity": "sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw==", + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", + "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==", "devOptional": true }, "node_modules/@types/pg": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", - "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.5.tgz", + "integrity": "sha512-tOkGtAqRVkHa/PVZicq67zuujI4Oorfglsr2IbKofDwBSysnaqSx7W1mDqFqdkGE6Fbgh+PZAl0r/BWON/mozw==", "dev": true, "dependencies": { "@types/node": "*", @@ -980,18 +963,19 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz", - "integrity": "sha512-4bV6fulqbuaO9UMXU0Ia0o6z6if+kmMRW8rMRyfqXj/eGrZZRGedS4n0adeGNnjr8LKAM495hrQ7Tea52UWmQA==", + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz", + "integrity": "sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg==", "dev": true, "dependencies": { - "@typescript-eslint/experimental-utils": "5.5.0", - "@typescript-eslint/scope-manager": "5.5.0", - "debug": "^4.3.2", + "@typescript-eslint/scope-manager": "5.30.6", + "@typescript-eslint/type-utils": "5.30.6", + "@typescript-eslint/utils": "5.30.6", + "debug": "^4.3.4", "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", + "ignore": "^5.2.0", "regexpp": "^3.2.0", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { @@ -1011,40 +995,16 @@ } } }, - "node_modules/@typescript-eslint/experimental-utils": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.5.0.tgz", - "integrity": "sha512-kjWeeVU+4lQ1SLYErRKV5yDXbWDPkpbzTUUlfAUifPYvpX0qZlrcCZ96/6oWxt3QxtK5WVhXz+KsnwW9cIW+3A==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - } - }, "node_modules/@typescript-eslint/parser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.5.0.tgz", - "integrity": "sha512-JsXBU+kgQOAgzUn2jPrLA+Rd0Y1dswOlX3hp8MuRO1hQDs6xgHtbCXEiAu7bz5hyVURxbXcA2draasMbNqrhmg==", + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.30.6.tgz", + "integrity": "sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "debug": "^4.3.2" + "@typescript-eslint/scope-manager": "5.30.6", + "@typescript-eslint/types": "5.30.6", + "@typescript-eslint/typescript-estree": "5.30.6", + "debug": "^4.3.4" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1063,13 +1023,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.5.0.tgz", - "integrity": "sha512-0/r656RmRLo7CbN4Mdd+xZyPJ/fPCKhYdU6mnZx+8msAD8nJSP8EyCFkzbd6vNVZzZvWlMYrSNekqGrCBqFQhg==", + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz", + "integrity": "sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0" + "@typescript-eslint/types": "5.30.6", + "@typescript-eslint/visitor-keys": "5.30.6" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1079,10 +1039,36 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.6.tgz", + "integrity": "sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.30.6", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/@typescript-eslint/types": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.5.0.tgz", - "integrity": "sha512-OaYTqkW3GnuHxqsxxJ6KypIKd5Uw7bFiQJZRyNi1jbMJnK3Hc/DR4KwB6KJj6PBRkJJoaNwzMNv9vtTk87JhOg==", + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.6.tgz", + "integrity": "sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1093,17 +1079,17 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.5.0.tgz", - "integrity": "sha512-pVn8btYUiYrjonhMAO0yG8lm7RApzy2L4RC7Td/mC/qFkyf6vRbGyZozoA94+w6D2Y2GRqpMoCWcwx/EUOzyoQ==", + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz", + "integrity": "sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0", - "debug": "^4.3.2", - "globby": "^11.0.4", + "@typescript-eslint/types": "5.30.6", + "@typescript-eslint/visitor-keys": "5.30.6", + "debug": "^4.3.4", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" }, "engines": { @@ -1119,14 +1105,38 @@ } } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.5.0.tgz", - "integrity": "sha512-4GzJ1kRtsWzHhdM40tv0ZKHNSbkDhF0Woi/TDwVJX6UICwJItvP7ZTXbjTkCdrors7ww0sYe0t+cIKDAJwZ7Kw==", + "node_modules/@typescript-eslint/utils": { + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.30.6.tgz", + "integrity": "sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.5.0", - "eslint-visitor-keys": "^3.0.0" + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.30.6", + "@typescript-eslint/types": "5.30.6", + "@typescript-eslint/typescript-estree": "5.30.6", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz", + "integrity": "sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.30.6", + "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1149,21 +1159,21 @@ "dev": true }, "node_modules/accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dependencies": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" } }, "node_modules/acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1219,38 +1229,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -1309,40 +1287,12 @@ "node": ">=8" } }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, - "node_modules/are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "node_modules/are-we-there-yet/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1374,12 +1324,18 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "dependencies": { - "follow-redirects": "^1.14.4" + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" } }, "node_modules/babel-runtime": { @@ -1438,14 +1394,13 @@ ] }, "node_modules/better-sqlite3": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.4.5.tgz", - "integrity": "sha512-mybC3dgrtJeHkIRGP36tST7wjBlIMgTRAXhhO4bMpPZ17EG23FZxZeFcwKWy6o8mV1SKQFnQNyeAZlQpGrgheQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.6.0.tgz", + "integrity": "sha512-wYckL8S8RHP+KKNsZuJGZ7z/6FFmVgwd0U8jSv6t997C+EFR1yvi8p2WIpTb10jiV5rRA5VtMdgtAZFcAnK3Iw==", "hasInstallScript": true, "dependencies": { "bindings": "^1.5.0", - "prebuild-install": "^7.0.0", - "tar": "^6.1.11" + "prebuild-install": "^7.1.0" } }, "node_modules/bignumber.js": { @@ -1483,42 +1438,27 @@ "readable-stream": "^3.4.0" } }, - "node_modules/bl/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dependencies": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/debug": { @@ -1532,52 +1472,7 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/boxen/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -1672,55 +1567,13 @@ } }, "node_modules/bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "engines": { "node": ">= 0.8" } }, - "node_modules/cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cacheable-request/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/caching-transform": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", @@ -1736,6 +1589,18 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1790,10 +1655,16 @@ } }, "node_modules/chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1823,18 +1694,9 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "node_modules/clean-stack": { "version": "2.2.0", @@ -1845,18 +1707,6 @@ "node": ">=6" } }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -1891,24 +1741,6 @@ "node": ">=8" } }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - } - }, - "node_modules/clone-response/node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/cluster-key-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", @@ -1917,14 +1749,6 @@ "node": ">=0.10.0" } }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1943,6 +1767,17 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -1983,39 +1818,36 @@ "util-deprecate": "~1.0.1" } }, - "node_modules/configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "dependencies": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, "node_modules/content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dependencies": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", @@ -2034,9 +1866,9 @@ } }, "node_modules/cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "engines": { "node": ">= 0.6" } @@ -2065,11 +1897,11 @@ "dev": true }, "node_modules/cron": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", - "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.0.0.tgz", + "integrity": "sha512-RPeRunBCFr/WEo7WLp8Jnm45F/ziGJiHVvVQEBSDTSGu6uHW49b2FOP2O14DcXlGJRLhwE7TIoDzHHK4KmlL6g==", "dependencies": { - "moment-timezone": "^0.5.x" + "luxon": "^1.23.x" } }, "node_modules/cross-spawn": { @@ -2086,19 +1918,10 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2164,39 +1987,37 @@ "node": ">=8" } }, - "node_modules/defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "node_modules/delegates": { + "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } }, "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/diff": { @@ -2232,28 +2053,10 @@ "node": ">=6.0.0" } }, - "node_modules/dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { "version": "1.4.172", @@ -2270,7 +2073,7 @@ "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "engines": { "node": ">= 0.8" } @@ -2283,18 +2086,6 @@ "once": "^1.4.0" } }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -2310,19 +2101,10 @@ "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/escape-string-regexp": { "version": "4.0.0", @@ -2337,32 +2119,31 @@ } }, "node_modules/eslint": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.3.0.tgz", - "integrity": "sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", + "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.0.4", - "@humanwhocodes/config-array": "^0.6.0", + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", + "eslint-scope": "^7.1.1", "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.1.0", - "espree": "^9.1.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", + "globals": "^13.15.0", + "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", @@ -2370,12 +2151,10 @@ "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", "regexpp": "^3.2.0", - "semver": "^7.2.1", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0", @@ -2432,18 +2211,18 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", - "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -2462,24 +2241,27 @@ "node": ">=4.0" } }, - "node_modules/eslint/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">= 4" + "node": "*" } }, "node_modules/espree": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz", - "integrity": "sha512-ZgYLvCS1wxOczBYGcQT9DDWgicXwJ4dbocr9uYN+/eresBAUuBu+O4WzB21ufQ/JqQT8gyp7hJ3z8SHii32mTQ==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", "dev": true, "dependencies": { - "acorn": "^8.6.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2561,7 +2343,7 @@ "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "engines": { "node": ">= 0.6" } @@ -2575,37 +2357,38 @@ } }, "node_modules/express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", "dependencies": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -2637,14 +2420,14 @@ } }, "node_modules/express-rate-limit": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.3.0.tgz", - "integrity": "sha512-932Io1VGKjM3ppi7xW9sb1J5nVkEJSUiOtHw2oE+JyHks1e+AXuOBSXbJKM0mcXwEnW1TibJibQ455Ow1YFjfg==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.4.0.tgz", + "integrity": "sha512-lxQRZI4gi3qAWTf0/Uqsyugsz57h8bd7QyllXBgJvd6DJKokzW7C5DTaNvwzvAQzwHGFaItybfYGhC8gpu0V2A==", "engines": { "node": ">= 12.9.0" }, "peerDependencies": { - "express": "^4" + "express": "^4 || ^5" } }, "node_modules/express/node_modules/debug": { @@ -2660,6 +2443,25 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2667,9 +2469,9 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -2679,7 +2481,7 @@ "micromatch": "^4.0.4" }, "engines": { - "node": ">=8" + "node": ">=8.6.0" } }, "node_modules/fast-glob/node_modules/glob-parent": { @@ -2745,16 +2547,16 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -2772,7 +2574,7 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/find-cache-dir": { "version": "3.3.2", @@ -2836,9 +2638,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", "funding": [ { "type": "individual", @@ -2867,6 +2669,19 @@ "node": ">=8.0.0" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2878,7 +2693,7 @@ "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "engines": { "node": ">= 0.6" } @@ -2908,17 +2723,6 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2939,46 +2743,17 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "node_modules/gauge/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/gauge/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/generic-pool": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", @@ -3005,6 +2780,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -3022,27 +2810,15 @@ "node": ">=4" } }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -3071,34 +2847,10 @@ "node": ">=10.13.0" } }, - "node_modules/global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/global-dirs/node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -3111,16 +2863,16 @@ } }, "node_modules/globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" }, "engines": { @@ -3130,62 +2882,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/got/node_modules/decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/got/node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", "dev": true }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, "engines": { - "node": ">=4.x" + "node": ">= 0.4.0" } }, "node_modules/has-flag": { @@ -3197,18 +2908,15 @@ "node": ">=8" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "node_modules/has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/hasha": { @@ -3251,25 +2959,19 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "node_modules/http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/iconv-lite": { @@ -3303,9 +3005,9 @@ ] }, "node_modules/ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true, "engines": { "node": ">= 4" @@ -3333,15 +3035,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3371,9 +3064,9 @@ } }, "node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "1.3.8", @@ -3400,18 +3093,6 @@ "node": ">=8" } }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3421,17 +3102,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dependencies": { - "number-is-nan": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3444,34 +3114,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -3481,24 +3123,6 @@ "node": ">=0.12.0" } }, - "node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -3552,12 +3176,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3702,12 +3320,6 @@ "node": ">=4" } }, - "node_modules/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3738,27 +3350,6 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, - "node_modules/keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "dependencies": { - "json-buffer": "3.0.0" - } - }, - "node_modules/latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "dependencies": { - "package-json": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -3800,7 +3391,7 @@ "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, "node_modules/lodash.merge": { @@ -3825,13 +3416,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true, + "node_modules/luxon": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", + "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==", "engines": { - "node": ">=0.10.0" + "node": "*" } }, "node_modules/make-dir": { @@ -3867,7 +3457,7 @@ "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "engines": { "node": ">= 0.6" } @@ -3895,13 +3485,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" @@ -3919,19 +3509,19 @@ } }, "node_modules/mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" @@ -3965,120 +3555,72 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, - "node_modules/minipass": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", - "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "node_modules/mocha": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", - "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", "dev": true, "dependencies": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.2", + "chokidar": "3.5.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", - "growl": "1.10.5", + "glob": "7.2.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "3.0.4", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.1.25", + "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.1.5", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "bin": { "_mocha": "bin/_mocha", - "mocha": "bin/mocha" + "mocha": "bin/mocha.js" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 14.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mochajs" } }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "node_modules/mocha/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==", "dev": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "balanced-match": "^1.0.0" } }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", @@ -4101,25 +3643,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/moment": { - "version": "2.29.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", - "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==", - "engines": { - "node": "*" - } - }, - "node_modules/moment-timezone": { - "version": "0.5.34", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", - "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==", - "dependencies": { - "moment": ">= 2.9.0" - }, - "engines": { - "node": "*" - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4155,9 +3678,9 @@ } }, "node_modules/nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -4178,39 +3701,30 @@ "dev": true }, "node_modules/negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "engines": { "node": ">= 0.6" } }, "node_modules/nise": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", - "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz", + "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": ">=5", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", "path-to-regexp": "^1.7.0" } }, - "node_modules/nise/node_modules/@sinonjs/fake-timers": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", - "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, "node_modules/nise/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true }, "node_modules/nise/node_modules/path-to-regexp": { @@ -4223,9 +3737,9 @@ } }, "node_modules/node-abi": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.5.0.tgz", - "integrity": "sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.22.0.tgz", + "integrity": "sha512-u4uAs/4Zzmp/jjsD9cyFYDXeISfUWaAVWshPmDZOFOv4Xl4SbzTXm53I04C2uRueYJ+0t5PEtLH/owbn2Npf/w==", "dependencies": { "semver": "^7.3.5" }, @@ -4252,9 +3766,9 @@ "dev": true }, "node_modules/nodemon": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", - "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz", + "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -4264,10 +3778,10 @@ "minimatch": "^3.0.4", "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" + "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" @@ -4343,34 +3857,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -4580,18 +4066,18 @@ "node": ">=6" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "engines": { - "node": ">=0.10.0" + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -4624,15 +4110,6 @@ "node": ">= 0.8.0" } }, - "node_modules/p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4699,30 +4176,6 @@ "node": ">=8" } }, - "node_modules/package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "dependencies": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", @@ -4790,14 +4243,14 @@ } }, "node_modules/pg": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", - "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz", + "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==", "dependencies": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "^2.5.0", - "pg-pool": "^3.4.1", + "pg-pool": "^3.5.1", "pg-protocol": "^1.5.0", "pg-types": "^2.1.0", "pgpass": "1.x" @@ -4828,9 +4281,9 @@ } }, "node_modules/pg-pool": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", - "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz", + "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==", "peerDependencies": { "pg": ">=8.0" } @@ -4870,9 +4323,9 @@ "dev": true }, "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "engines": { "node": ">=8.6" @@ -4981,18 +4434,17 @@ } }, "node_modules/prebuild-install": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.0.0.tgz", - "integrity": "sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", "node-abi": "^3.3.0", - "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", @@ -5015,15 +4467,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -5041,15 +4484,6 @@ "node": ">=8" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -5094,24 +4528,18 @@ "node": ">=6" } }, - "node_modules/pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, + "node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", "dependencies": { - "escape-goat": "^2.0.0" + "side-channel": "^1.0.4" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", "engines": { "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/queue-microtask": { @@ -5163,12 +4591,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -5198,6 +4626,19 @@ "node": ">=0.10.0" } }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5211,35 +4652,16 @@ } }, "node_modules/redis": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.0.6.tgz", - "integrity": "sha512-IaPAxgF5dV0jx+A9l6yd6R9/PAChZIoAskDVRzUODeLDNhsMlq7OLLTmu0AwAr0xjrJ1bibW5xdpRwqIQ8Q0Xg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz", + "integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==", "dependencies": { - "@node-redis/bloom": "1.0.1", - "@node-redis/client": "1.0.5", - "@node-redis/graph": "1.0.0", - "@node-redis/json": "1.0.2", - "@node-redis/search": "1.0.5", - "@node-redis/time-series": "1.0.2" - } - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" + "@redis/bloom": "1.0.2", + "@redis/client": "1.2.0", + "@redis/graph": "1.0.1", + "@redis/json": "1.0.3", + "@redis/search": "1.0.6", + "@redis/time-series": "1.0.3" } }, "node_modules/regenerator-runtime": { @@ -5259,30 +4681,6 @@ "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "dependencies": { - "rc": "^1.2.8" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -5319,15 +4717,6 @@ "node": ">=4" } }, - "node_modules/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "dependencies": { - "lowercase-keys": "^1.0.0" - } - }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -5387,9 +4776,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -5400,27 +4789,6 @@ "node": ">=10" } }, - "node_modules/semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/semver/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5432,29 +4800,24 @@ "node": ">=10" } }, - "node_modules/semver/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -5471,12 +4834,12 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/send/node_modules/ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { "version": "6.0.0", @@ -5488,14 +4851,14 @@ } }, "node_modules/serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" @@ -5504,12 +4867,13 @@ "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "node_modules/setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -5532,10 +4896,24 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", + "dev": true }, "node_modules/simple-concat": { "version": "1.0.1", @@ -5580,17 +4958,38 @@ "simple-concat": "^1.0.0" } }, + "node_modules/simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "dependencies": { + "semver": "~7.0.0" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/sinon": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", - "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.0.tgz", + "integrity": "sha512-ugA6BFmE+WrJdh0owRZHToLd32Uw3Lxq6E6LtNRU+xTVBefx632h03Q7apXWRsRdZAJ41LB8aUfn2+O4jsDNMw==", "dev": true, "dependencies": { "@sinonjs/commons": "^1.8.3", - "@sinonjs/fake-timers": "^8.1.0", - "@sinonjs/samsam": "^6.0.2", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^6.1.1", "diff": "^5.0.0", - "nise": "^5.1.0", + "nise": "^5.1.1", "supports-color": "^7.2.0" }, "funding": { @@ -5641,19 +5040,6 @@ "readable-stream": "^3.0.0" } }, - "node_modules/split2/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -5669,11 +5055,11 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/string_decoder": { @@ -5684,38 +5070,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5780,22 +5134,6 @@ "get-port": "^3.1.0" } }, - "node_modules/tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -5807,11 +5145,6 @@ "tar-stream": "^2.1.4" } }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -5827,24 +5160,6 @@ "node": ">=6" } }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -5884,15 +5199,6 @@ "node": ">=4" } }, - "node_modules/to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5906,9 +5212,9 @@ } }, "node_modules/toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "engines": { "node": ">=0.6" } @@ -5936,12 +5242,12 @@ } }, "node_modules/ts-node": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", - "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.2.tgz", + "integrity": "sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==", "dev": true, "dependencies": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -5952,11 +5258,13 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "bin": { "ts-node": "dist/bin.js", "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js", "ts-script": "dist/bin-script-deprecated.js" @@ -6009,7 +5317,7 @@ "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dependencies": { "safe-buffer": "^5.0.1" }, @@ -6077,9 +5385,9 @@ } }, "node_modules/typescript": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", - "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6095,22 +5403,10 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, - "node_modules/unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "dependencies": { - "crypto-random-string": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "engines": { "node": ">= 0.8" } @@ -6141,34 +5437,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "dependencies": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6178,18 +5446,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "dependencies": { - "prepend-http": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6218,6 +5474,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -6247,49 +5509,6 @@ "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", "dev": true }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/widest-line/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/widest-line/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -6300,9 +5519,9 @@ } }, "node_modules/workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, "node_modules/wrap-ansi": { @@ -6362,15 +5581,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "node_modules/xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -6388,6 +5598,11 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -6789,53 +6004,62 @@ "to-fast-properties": "^2.0.0" } }, - "@cspotcode/source-map-consumer": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", - "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", - "dev": true - }, "@cspotcode/source-map-support": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", - "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, "requires": { - "@cspotcode/source-map-consumer": "0.8.0" + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } } }, "@eslint/eslintrc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.4.tgz", - "integrity": "sha512-h8Vx6MdxwWI2WM8/zREHMoqdgLNXEL4QX3MWSVMdyNJGvXVOs+6lp+m2hc3FnuMHDc4poxFNI20vCk0OmI4G0Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", + "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.0.0", - "globals": "^13.9.0", - "ignore": "^4.0.6", + "espree": "^9.3.2", + "globals": "^13.15.0", + "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "dependencies": { - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, "@humanwhocodes/config-array": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.6.0.tgz", - "integrity": "sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==", + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", + "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.0", + "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", "minimatch": "^3.0.4" } @@ -6973,54 +6197,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "@node-redis/bloom": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@node-redis/bloom/-/bloom-1.0.1.tgz", - "integrity": "sha512-mXEBvEIgF4tUzdIN89LiYsbi6//EdpFA7L8M+DHCvePXg+bfHWi+ct5VI6nHUFQE5+ohm/9wmgihCH3HSkeKsw==", - "requires": {} - }, - "@node-redis/client": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@node-redis/client/-/client-1.0.5.tgz", - "integrity": "sha512-ESZ3bd1f+od62h4MaBLKum+klVJfA4wAeLHcVQBkoXa1l0viFesOWnakLQqKg+UyrlJhZmXJWtu0Y9v7iTMrig==", - "requires": { - "cluster-key-slot": "1.1.0", - "generic-pool": "3.8.2", - "redis-parser": "3.0.0", - "yallist": "4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "@node-redis/graph": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@node-redis/graph/-/graph-1.0.0.tgz", - "integrity": "sha512-mRSo8jEGC0cf+Rm7q8mWMKKKqkn6EAnA9IA2S3JvUv/gaWW/73vil7GLNwion2ihTptAm05I9LkepzfIXUKX5g==", - "requires": {} - }, - "@node-redis/json": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@node-redis/json/-/json-1.0.2.tgz", - "integrity": "sha512-qVRgn8WfG46QQ08CghSbY4VhHFgaTY71WjpwRBGEuqGPfWwfRcIf3OqSpR7Q/45X+v3xd8mvYjywqh0wqJ8T+g==", - "requires": {} - }, - "@node-redis/search": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@node-redis/search/-/search-1.0.5.tgz", - "integrity": "sha512-MCOL8iCKq4v+3HgEQv8zGlSkZyXSXtERgrAJ4TSryIG/eLFy84b57KmNNa/V7M1Q2Wd2hgn2nPCGNcQtk1R1OQ==", - "requires": {} - }, - "@node-redis/time-series": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@node-redis/time-series/-/time-series-1.0.2.tgz", - "integrity": "sha512-HGQ8YooJ8Mx7l28tD7XjtB3ImLEjlUxG1wC1PAjxu6hPJqjPshUZxAICzDqDjtIbhDTf48WXXUcx8TQJB1XTKA==", - "requires": {} - }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -7047,11 +6223,45 @@ "fastq": "^1.6.0" } }, - "@sindresorhus/is": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", - "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", - "dev": true + "@redis/bloom": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.0.2.tgz", + "integrity": "sha512-EBw7Ag1hPgFzdznK2PBblc1kdlj5B5Cw3XwI9/oG7tSn85/HKy3X9xHy/8tm/eNXJYHLXHJL/pkwBpFMVVefkw==", + "requires": {} + }, + "@redis/client": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.2.0.tgz", + "integrity": "sha512-a8Nlw5fv2EIAFJxTDSSDVUT7yfBGpZO96ybZXzQpgkyLg/dxtQ1uiwTc0EGfzg1mrPjZokeBSEGTbGXekqTNOg==", + "requires": { + "cluster-key-slot": "1.1.0", + "generic-pool": "3.8.2", + "yallist": "4.0.0" + } + }, + "@redis/graph": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.0.1.tgz", + "integrity": "sha512-oDE4myMCJOCVKYMygEMWuriBgqlS5FqdWerikMoJxzmmTUErnTRRgmIDa2VcgytACZMFqpAOWDzops4DOlnkfQ==", + "requires": {} + }, + "@redis/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.3.tgz", + "integrity": "sha512-4X0Qv0BzD9Zlb0edkUoau5c1bInWSICqXAGrpwEltkncUwcxJIGEcVryZhLgb0p/3PkKaLIWkjhHRtLe9yiA7Q==", + "requires": {} + }, + "@redis/search": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.0.6.tgz", + "integrity": "sha512-pP+ZQRis5P21SD6fjyCeLcQdps+LuTzp2wdUbzxEmNhleighDDTD5ck8+cYof+WLec4csZX7ks+BuoMw0RaZrA==", + "requires": {} + }, + "@redis/time-series": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.3.tgz", + "integrity": "sha512-OFp0q4SGrTH0Mruf6oFsHGea58u8vS/iI5+NpYdicaM+7BgqBZH8FFvNZ8rYYLrUO/QRqMq72NpXmxLVNcdmjA==", + "requires": {} }, "@sinonjs/commons": { "version": "1.8.3", @@ -7063,18 +6273,18 @@ } }, "@sinonjs/fake-timers": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz", - "integrity": "sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" } }, "@sinonjs/samsam": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", - "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz", + "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==", "dev": true, "requires": { "@sinonjs/commons": "^1.6.0", @@ -7088,15 +6298,6 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, - "@szmarczak/http-timer": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", - "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", - "dev": true, - "requires": { - "defer-to-connect": "^1.0.1" - } - }, "@tsconfig/node10": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", @@ -7122,9 +6323,9 @@ "dev": true }, "@types/better-sqlite3": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.4.1.tgz", - "integrity": "sha512-ReUz3npCelyRMEVF1inVnP1rlnCSD4LRwaPvfJViR6ZiSXRVTBtLHU52sdwtbyQJQjo6RRPghlKn9e1PpQFptw==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.5.0.tgz", + "integrity": "sha512-G9ZbMjydW2yj1AgiPlUtdgF3a1qNpLJLudc9ynJCeJByS3XFWpmT9LT+VSHrKHFbxb31CvtYwetLTOvG9zdxdg==", "dev": true, "requires": { "@types/node": "*" @@ -7150,13 +6351,13 @@ } }, "@types/cron": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@types/cron/-/cron-1.7.3.tgz", - "integrity": "sha512-iPmUXyIJG1Js+ldPYhOQcYU3kCAQ2FWrSkm1FJPoii2eYSn6wEW6onPukNTT0bfiflexNSRPl6KWmAIqS+36YA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/cron/-/cron-2.0.0.tgz", + "integrity": "sha512-xZM08fqvwIXgghtPVkSPKNgC+JoMQ2OHazEvyTKnNf7aWu1aB6/4lBbQFrb03Td2cUGG7ITzMv3mFYnMu6xRaQ==", "dev": true, "requires": { - "@types/node": "*", - "moment": ">=2.14.0" + "@types/luxon": "*", + "@types/node": "*" } }, "@types/express": { @@ -7183,15 +6384,21 @@ } }, "@types/json-schema": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", - "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, "@types/lodash": { - "version": "4.14.178", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", - "integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==", + "version": "4.14.182", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", + "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==", + "dev": true + }, + "@types/luxon": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-2.3.2.tgz", + "integrity": "sha512-WOehptuhKIXukSUUkRgGbj2c997Uv/iUgYgII8U7XLJqq9W2oF0kQ6frEznRQbdurioz+L/cdaIm4GutTQfgmA==", "dev": true }, "@types/mime": { @@ -7201,21 +6408,21 @@ "devOptional": true }, "@types/mocha": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.0.0.tgz", - "integrity": "sha512-scN0hAWyLVAvLR9AyW7HoFF5sJZglyBsbPuHO4fv7JRvfmPBMfp1ozWqOf/e4wwPNxezBZXRfWzMb6iFLgEVRA==", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", "dev": true }, "@types/node": { - "version": "16.11.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.11.tgz", - "integrity": "sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw==", + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.3.tgz", + "integrity": "sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==", "devOptional": true }, "@types/pg": { - "version": "8.6.1", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", - "integrity": "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w==", + "version": "8.6.5", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.5.tgz", + "integrity": "sha512-tOkGtAqRVkHa/PVZicq67zuujI4Oorfglsr2IbKofDwBSysnaqSx7W1mDqFqdkGE6Fbgh+PZAl0r/BWON/mozw==", "dev": true, "requires": { "@types/node": "*", @@ -7246,86 +6453,98 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.5.0.tgz", - "integrity": "sha512-4bV6fulqbuaO9UMXU0Ia0o6z6if+kmMRW8rMRyfqXj/eGrZZRGedS4n0adeGNnjr8LKAM495hrQ7Tea52UWmQA==", + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.6.tgz", + "integrity": "sha512-J4zYMIhgrx4MgnZrSDD7sEnQp7FmhKNOaqaOpaoQ/SfdMfRB/0yvK74hTnvH+VQxndZynqs5/Hn4t+2/j9bADg==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "5.5.0", - "@typescript-eslint/scope-manager": "5.5.0", - "debug": "^4.3.2", + "@typescript-eslint/scope-manager": "5.30.6", + "@typescript-eslint/type-utils": "5.30.6", + "@typescript-eslint/utils": "5.30.6", + "debug": "^4.3.4", "functional-red-black-tree": "^1.0.1", - "ignore": "^5.1.8", + "ignore": "^5.2.0", "regexpp": "^3.2.0", - "semver": "^7.3.5", + "semver": "^7.3.7", "tsutils": "^3.21.0" } }, - "@typescript-eslint/experimental-utils": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.5.0.tgz", - "integrity": "sha512-kjWeeVU+4lQ1SLYErRKV5yDXbWDPkpbzTUUlfAUifPYvpX0qZlrcCZ96/6oWxt3QxtK5WVhXz+KsnwW9cIW+3A==", + "@typescript-eslint/parser": { + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.30.6.tgz", + "integrity": "sha512-gfF9lZjT0p2ZSdxO70Xbw8w9sPPJGfAdjK7WikEjB3fcUI/yr9maUVEdqigBjKincUYNKOmf7QBMiTf719kbrA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.30.6", + "@typescript-eslint/types": "5.30.6", + "@typescript-eslint/typescript-estree": "5.30.6", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.30.6.tgz", + "integrity": "sha512-Hkq5PhLgtVoW1obkqYH0i4iELctEKixkhWLPTYs55doGUKCASvkjOXOd/pisVeLdO24ZX9D6yymJ/twqpJiG3g==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.30.6", + "@typescript-eslint/visitor-keys": "5.30.6" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.30.6.tgz", + "integrity": "sha512-GFVVzs2j0QPpM+NTDMXtNmJKlF842lkZKDSanIxf+ArJsGeZUIaeT4jGg+gAgHt7AcQSFwW7htzF/rbAh2jaVA==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "5.30.6", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/types": { + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.30.6.tgz", + "integrity": "sha512-HdnP8HioL1F7CwVmT4RaaMX57RrfqsOMclZc08wGMiDYJBsLGBM7JwXM4cZJmbWLzIR/pXg1kkrBBVpxTOwfUg==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.6.tgz", + "integrity": "sha512-Z7TgPoeYUm06smfEfYF0RBkpF8csMyVnqQbLYiGgmUSTaSXTP57bt8f0UFXstbGxKIreTwQCujtaH0LY9w9B+A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.30.6", + "@typescript-eslint/visitor-keys": "5.30.6", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/utils": { + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.30.6.tgz", + "integrity": "sha512-xFBLc/esUbLOJLk9jKv0E9gD/OH966M40aY9jJ8GiqpSkP2xOV908cokJqqhVd85WoIvHVHYXxSFE4cCSDzVvA==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", + "@typescript-eslint/scope-manager": "5.30.6", + "@typescript-eslint/types": "5.30.6", + "@typescript-eslint/typescript-estree": "5.30.6", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" } }, - "@typescript-eslint/parser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.5.0.tgz", - "integrity": "sha512-JsXBU+kgQOAgzUn2jPrLA+Rd0Y1dswOlX3hp8MuRO1hQDs6xgHtbCXEiAu7bz5hyVURxbXcA2draasMbNqrhmg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.5.0", - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/typescript-estree": "5.5.0", - "debug": "^4.3.2" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.5.0.tgz", - "integrity": "sha512-0/r656RmRLo7CbN4Mdd+xZyPJ/fPCKhYdU6mnZx+8msAD8nJSP8EyCFkzbd6vNVZzZvWlMYrSNekqGrCBqFQhg==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0" - } - }, - "@typescript-eslint/types": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.5.0.tgz", - "integrity": "sha512-OaYTqkW3GnuHxqsxxJ6KypIKd5Uw7bFiQJZRyNi1jbMJnK3Hc/DR4KwB6KJj6PBRkJJoaNwzMNv9vtTk87JhOg==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.5.0.tgz", - "integrity": "sha512-pVn8btYUiYrjonhMAO0yG8lm7RApzy2L4RC7Td/mC/qFkyf6vRbGyZozoA94+w6D2Y2GRqpMoCWcwx/EUOzyoQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.5.0", - "@typescript-eslint/visitor-keys": "5.5.0", - "debug": "^4.3.2", - "globby": "^11.0.4", - "is-glob": "^4.0.3", - "semver": "^7.3.5", - "tsutils": "^3.21.0" - } - }, "@typescript-eslint/visitor-keys": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.5.0.tgz", - "integrity": "sha512-4GzJ1kRtsWzHhdM40tv0ZKHNSbkDhF0Woi/TDwVJX6UICwJItvP7ZTXbjTkCdrors7ww0sYe0t+cIKDAJwZ7Kw==", + "version": "5.30.6", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.6.tgz", + "integrity": "sha512-41OiCjdL2mCaSDi2SvYbzFLlqqlm5v1ZW9Ym55wXKL/Rx6OOB1IbuFGo71Fj6Xy90gJDFTlgOS+vbmtGHPTQQA==", "dev": true, "requires": { - "@typescript-eslint/types": "5.5.0", - "eslint-visitor-keys": "^3.0.0" + "@typescript-eslint/types": "5.30.6", + "eslint-visitor-keys": "^3.3.0" } }, "@ungap/promise-all-settled": { @@ -7341,18 +6560,18 @@ "dev": true }, "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" } }, "acorn": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz", - "integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==", + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", "dev": true }, "acorn-jsx": { @@ -7390,34 +6609,6 @@ "uri-js": "^4.2.2" } }, - "ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "requires": { - "string-width": "^4.1.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } - } - }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -7458,42 +6649,12 @@ "default-require-extensions": "^3.0.0" } }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, - "are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - } - } - }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -7522,12 +6683,18 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", "requires": { - "follow-redirects": "^1.14.4" + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" } }, "babel-runtime": { @@ -7574,13 +6741,12 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "better-sqlite3": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.4.5.tgz", - "integrity": "sha512-mybC3dgrtJeHkIRGP36tST7wjBlIMgTRAXhhO4bMpPZ17EG23FZxZeFcwKWy6o8mV1SKQFnQNyeAZlQpGrgheQ==", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-7.6.0.tgz", + "integrity": "sha512-wYckL8S8RHP+KKNsZuJGZ7z/6FFmVgwd0U8jSv6t997C+EFR1yvi8p2WIpTb10jiV5rRA5VtMdgtAZFcAnK3Iw==", "requires": { "bindings": "^1.5.0", - "prebuild-install": "^7.0.0", - "tar": "^6.1.11" + "prebuild-install": "^7.1.0" } }, "bignumber.js": { @@ -7610,40 +6776,25 @@ "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "requires": { - "bytes": "3.1.0", + "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "dependencies": { "debug": { @@ -7657,42 +6808,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "requires": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -7753,41 +6869,9 @@ "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" }, "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" - }, - "cacheable-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", - "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^3.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^4.1.0", - "responselike": "^1.0.2" - }, - "dependencies": { - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - } - } + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "caching-transform": { "version": "4.0.0", @@ -7801,6 +6885,15 @@ "write-file-atomic": "^3.0.0" } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -7830,9 +6923,9 @@ } }, "chokidar": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz", - "integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==", + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "requires": { "anymatch": "~3.1.2", @@ -7857,15 +6950,9 @@ } }, "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" }, "clean-stack": { "version": "2.2.0", @@ -7873,12 +6960,6 @@ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true }, - "cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true - }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -7909,33 +6990,11 @@ } } }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - }, - "dependencies": { - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - } - } - }, "cluster-key-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz", "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==" }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7951,6 +7010,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -7990,31 +7057,19 @@ } } }, - "configstore": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", - "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", - "dev": true, - "requires": { - "dot-prop": "^5.2.0", - "graceful-fs": "^4.1.2", - "make-dir": "^3.0.0", - "unique-string": "^2.0.0", - "write-file-atomic": "^3.0.0", - "xdg-basedir": "^4.0.0" - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "requires": { - "safe-buffer": "5.1.2" + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } } }, "content-type": { @@ -8032,9 +7087,9 @@ } }, "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" }, "cookie-signature": { "version": "1.0.6", @@ -8058,11 +7113,11 @@ "dev": true }, "cron": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", - "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-2.0.0.tgz", + "integrity": "sha512-RPeRunBCFr/WEo7WLp8Jnm45F/ziGJiHVvVQEBSDTSGu6uHW49b2FOP2O14DcXlGJRLhwE7TIoDzHHK4KmlL6g==", "requires": { - "moment-timezone": "^0.5.x" + "luxon": "^1.23.x" } }, "cross-spawn": { @@ -8076,16 +7131,10 @@ "which": "^2.0.1" } }, - "crypto-random-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", - "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", - "dev": true - }, "debug": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" @@ -8125,31 +7174,25 @@ "strip-bom": "^4.0.0" } }, - "defer-to-connect": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", - "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", - "dev": true - }, - "delegates": { + "delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", + "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" }, "diff": { "version": "5.0.0", @@ -8175,25 +7218,10 @@ "esutils": "^2.0.2" } }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "dev": true, - "requires": { - "is-obj": "^2.0.0" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", - "dev": true - }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { "version": "1.4.172", @@ -8210,7 +7238,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "end-of-stream": { "version": "1.4.4", @@ -8220,15 +7248,6 @@ "once": "^1.4.0" } }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - } - }, "es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -8241,16 +7260,10 @@ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", "dev": true }, - "escape-goat": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", - "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", - "dev": true - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "escape-string-regexp": { "version": "4.0.0", @@ -8259,32 +7272,31 @@ "dev": true }, "eslint": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.3.0.tgz", - "integrity": "sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", + "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.0.4", - "@humanwhocodes/config-array": "^0.6.0", + "@eslint/eslintrc": "^1.3.0", + "@humanwhocodes/config-array": "^0.9.2", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.0", + "eslint-scope": "^7.1.1", "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.1.0", - "espree": "^9.1.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.2", "esquery": "^1.4.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^6.0.1", - "globals": "^13.6.0", - "ignore": "^4.0.6", + "globals": "^13.15.0", + "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", @@ -8292,12 +7304,10 @@ "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", "regexpp": "^3.2.0", - "semver": "^7.2.1", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0", @@ -8305,9 +7315,9 @@ }, "dependencies": { "eslint-scope": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz", - "integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -8320,11 +7330,14 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } } } }, @@ -8356,20 +7369,20 @@ } }, "eslint-visitor-keys": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz", - "integrity": "sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "dev": true }, "espree": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz", - "integrity": "sha512-ZgYLvCS1wxOczBYGcQT9DDWgicXwJ4dbocr9uYN+/eresBAUuBu+O4WzB21ufQ/JqQT8gyp7hJ3z8SHii32mTQ==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", + "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", "dev": true, "requires": { - "acorn": "^8.6.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^3.1.0" + "acorn": "^8.7.1", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" } }, "esprima": { @@ -8427,7 +7440,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "expand-template": { "version": "2.0.3", @@ -8435,37 +7448,38 @@ "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" }, "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", + "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", "requires": { - "accepts": "~1.3.7", + "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", + "body-parser": "1.20.0", + "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.0", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", + "proxy-addr": "~2.0.7", + "qs": "6.10.3", "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -8483,6 +7497,11 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, @@ -8497,9 +7516,9 @@ } }, "express-rate-limit": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.3.0.tgz", - "integrity": "sha512-932Io1VGKjM3ppi7xW9sb1J5nVkEJSUiOtHw2oE+JyHks1e+AXuOBSXbJKM0mcXwEnW1TibJibQ455Ow1YFjfg==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.4.0.tgz", + "integrity": "sha512-lxQRZI4gi3qAWTf0/Uqsyugsz57h8bd7QyllXBgJvd6DJKokzW7C5DTaNvwzvAQzwHGFaItybfYGhC8gpu0V2A==", "requires": {} }, "fast-deep-equal": { @@ -8509,9 +7528,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", - "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -8577,16 +7596,16 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "dependencies": { @@ -8601,7 +7620,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, @@ -8649,9 +7668,9 @@ "dev": true }, "follow-redirects": { - "version": "1.14.8", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", - "integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", + "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" }, "foreground-child": { "version": "2.0.0", @@ -8663,6 +7682,16 @@ "signal-exit": "^3.0.2" } }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -8671,7 +7700,7 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" }, "fromentries": { "version": "1.3.2", @@ -8684,14 +7713,6 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "requires": { - "minipass": "^3.0.0" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8705,42 +7726,17 @@ "dev": true, "optional": true }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, "generic-pool": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", @@ -8758,6 +7754,16 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -8769,24 +7775,15 @@ "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -8806,93 +7803,42 @@ "is-glob": "^4.0.3" } }, - "global-dirs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", - "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", - "dev": true, - "requires": { - "ini": "2.0.0" - }, - "dependencies": { - "ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true - } - } - }, "globals": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz", - "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==", + "version": "13.16.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", + "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", "dev": true, "requires": { "type-fest": "^0.20.2" } }, "globby": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", - "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.1.1", - "ignore": "^5.1.4", - "merge2": "^1.3.0", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", "slash": "^3.0.0" } }, - "got": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", - "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", - "dev": true, - "requires": { - "@sindresorhus/is": "^0.14.0", - "@szmarczak/http-timer": "^1.1.2", - "cacheable-request": "^6.0.0", - "decompress-response": "^3.3.0", - "duplexer3": "^0.1.4", - "get-stream": "^4.1.0", - "lowercase-keys": "^1.0.1", - "mimic-response": "^1.0.1", - "p-cancelable": "^1.0.0", - "to-readable-stream": "^1.0.0", - "url-parse-lax": "^3.0.0" - }, - "dependencies": { - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "dev": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true - } - } - }, "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", "dev": true }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } }, "has-flag": { "version": "4.0.0", @@ -8900,16 +7846,10 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "has-yarn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", - "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", - "dev": true + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "hasha": { "version": "5.2.2", @@ -8941,22 +7881,16 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "http-cache-semantics": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", - "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true - }, "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" } }, "iconv-lite": { @@ -8973,9 +7907,9 @@ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz", - "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, "ignore-by-default": { @@ -8994,12 +7928,6 @@ "resolve-from": "^4.0.0" } }, - "import-lazy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", - "dev": true - }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -9023,9 +7951,9 @@ } }, "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", @@ -9046,29 +7974,12 @@ "binary-extensions": "^2.0.0" } }, - "is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "requires": { - "ci-info": "^2.0.0" - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", "dev": true }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -9078,40 +7989,12 @@ "is-extglob": "^2.1.1" } }, - "is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "requires": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - } - }, - "is-npm": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", - "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", - "dev": true - }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -9147,12 +8030,6 @@ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, - "is-yarn-global": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", - "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", - "dev": true - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -9266,12 +8143,6 @@ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true }, - "json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", - "dev": true - }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -9296,24 +8167,6 @@ "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", "dev": true }, - "keyv": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", - "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", - "dev": true, - "requires": { - "json-buffer": "3.0.0" - } - }, - "latest-version": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", - "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", - "dev": true, - "requires": { - "package-json": "^6.3.0" - } - }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -9346,7 +8199,7 @@ "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, "lodash.merge": { @@ -9365,11 +8218,10 @@ "is-unicode-supported": "^0.1.0" } }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "dev": true + "luxon": { + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz", + "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==" }, "make-dir": { "version": "3.1.0", @@ -9397,7 +8249,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "merge-descriptors": { "version": "1.0.1", @@ -9416,13 +8268,13 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.2", + "picomatch": "^2.3.1" } }, "mime": { @@ -9431,16 +8283,16 @@ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" }, "mime-db": { - "version": "1.51.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" }, "mime-types": { - "version": "2.1.34", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", - "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "requires": { - "mime-db": "1.51.0" + "mime-db": "1.52.0" } }, "mimic-response": { @@ -9462,94 +8314,57 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" }, - "minipass": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", - "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", - "requires": { - "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, "mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" }, "mocha": { - "version": "9.1.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz", - "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.0.0.tgz", + "integrity": "sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==", "dev": true, "requires": { "@ungap/promise-all-settled": "1.1.2", "ansi-colors": "4.1.1", "browser-stdout": "1.3.1", - "chokidar": "3.5.2", - "debug": "4.3.2", + "chokidar": "3.5.3", + "debug": "4.3.4", "diff": "5.0.0", "escape-string-regexp": "4.0.0", "find-up": "5.0.0", - "glob": "7.1.7", - "growl": "1.10.5", + "glob": "7.2.0", "he": "1.2.0", "js-yaml": "4.1.0", "log-symbols": "4.1.0", - "minimatch": "3.0.4", + "minimatch": "5.0.1", "ms": "2.1.3", - "nanoid": "3.1.25", + "nanoid": "3.3.3", "serialize-javascript": "6.0.0", "strip-json-comments": "3.1.1", "supports-color": "8.1.1", - "which": "2.0.2", - "workerpool": "6.1.5", + "workerpool": "6.2.1", "yargs": "16.2.0", "yargs-parser": "20.2.4", "yargs-unparser": "2.0.0" }, "dependencies": { - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" } }, "ms": { @@ -9569,19 +8384,6 @@ } } }, - "moment": { - "version": "2.29.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz", - "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==" - }, - "moment-timezone": { - "version": "0.5.34", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz", - "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==", - "requires": { - "moment": ">= 2.9.0" - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -9616,9 +8418,9 @@ } }, "nanoid": { - "version": "3.1.25", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.25.tgz", - "integrity": "sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", "dev": true }, "napi-build-utils": { @@ -9633,36 +8435,27 @@ "dev": true }, "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, "nise": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", - "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz", + "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": ">=5", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", "path-to-regexp": "^1.7.0" }, "dependencies": { - "@sinonjs/fake-timers": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", - "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true }, "path-to-regexp": { @@ -9677,9 +8470,9 @@ } }, "node-abi": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.5.0.tgz", - "integrity": "sha512-LtHvNIBgOy5mO8mPEUtkCW/YCRWYEKshIvqhe1GHHyXEHEB5mgICyYnAcl4qan3uFeRROErKGzatFHPf6kDxWw==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.22.0.tgz", + "integrity": "sha512-u4uAs/4Zzmp/jjsD9cyFYDXeISfUWaAVWshPmDZOFOv4Xl4SbzTXm53I04C2uRueYJ+0t5PEtLH/owbn2Npf/w==", "requires": { "semver": "^7.3.5" } @@ -9700,9 +8493,9 @@ "dev": true }, "nodemon": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", - "integrity": "sha512-gdHMNx47Gw7b3kWxJV64NI+Q5nfl0y5DgDbiVtShiwa7Z0IZ07Ll4RLFo6AjrhzMtoEZn5PDE3/c2AbVsiCkpA==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.19.tgz", + "integrity": "sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A==", "dev": true, "requires": { "chokidar": "^3.5.2", @@ -9711,10 +8504,10 @@ "minimatch": "^3.0.4", "pstree.remy": "^1.1.8", "semver": "^5.7.1", + "simple-update-notifier": "^1.0.7", "supports-color": "^5.5.0", "touch": "^3.1.0", - "undefsafe": "^2.0.5", - "update-notifier": "^5.1.0" + "undefsafe": "^2.0.5" }, "dependencies": { "debug": { @@ -9764,28 +8557,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "normalize-url": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", - "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", - "dev": true - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -9952,15 +8723,15 @@ } } }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -9987,12 +8758,6 @@ "word-wrap": "^1.2.3" } }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", - "dev": true - }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -10038,26 +8803,6 @@ "release-zalgo": "^1.0.0" } }, - "package-json": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", - "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", - "dev": true, - "requires": { - "got": "^9.6.0", - "registry-auth-token": "^4.0.0", - "registry-url": "^5.0.0", - "semver": "^6.2.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "packet-reader": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", @@ -10107,14 +8852,14 @@ "dev": true }, "pg": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", - "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "version": "8.7.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz", + "integrity": "sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==", "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", "pg-connection-string": "^2.5.0", - "pg-pool": "^3.4.1", + "pg-pool": "^3.5.1", "pg-protocol": "^1.5.0", "pg-types": "^2.1.0", "pgpass": "1.x" @@ -10131,9 +8876,9 @@ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" }, "pg-pool": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", - "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz", + "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==", "requires": {} }, "pg-protocol": { @@ -10168,9 +8913,9 @@ "dev": true }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "pkg-dir": { @@ -10245,18 +8990,17 @@ } }, "prebuild-install": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.0.0.tgz", - "integrity": "sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", "requires": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^1.0.1", "node-abi": "^3.3.0", - "npmlog": "^4.0.1", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", @@ -10270,12 +9014,6 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, - "prepend-http": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", - "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", - "dev": true - }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -10290,12 +9028,6 @@ "fromentries": "^1.2.0" } }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", @@ -10334,19 +9066,13 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "dev": true }, - "pupa": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", - "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", - "dev": true, - "requires": { - "escape-goat": "^2.0.0" - } - }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } }, "queue-microtask": { "version": "1.2.3", @@ -10375,12 +9101,12 @@ "requires": {} }, "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", + "bytes": "3.1.2", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -10403,6 +9129,16 @@ } } }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -10413,29 +9149,16 @@ } }, "redis": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/redis/-/redis-4.0.6.tgz", - "integrity": "sha512-IaPAxgF5dV0jx+A9l6yd6R9/PAChZIoAskDVRzUODeLDNhsMlq7OLLTmu0AwAr0xjrJ1bibW5xdpRwqIQ8Q0Xg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.2.0.tgz", + "integrity": "sha512-bCR0gKVhIXFg8zCQjXEANzgI01DDixtPZgIUZHBCmwqixnu+MK3Tb2yqGjh+HCLASQVVgApiwhNkv+FoedZOGQ==", "requires": { - "@node-redis/bloom": "1.0.1", - "@node-redis/client": "1.0.5", - "@node-redis/graph": "1.0.0", - "@node-redis/json": "1.0.2", - "@node-redis/search": "1.0.5", - "@node-redis/time-series": "1.0.2" - } - }, - "redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=" - }, - "redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=", - "requires": { - "redis-errors": "^1.0.0" + "@redis/bloom": "1.0.2", + "@redis/client": "1.2.0", + "@redis/graph": "1.0.1", + "@redis/json": "1.0.3", + "@redis/search": "1.0.6", + "@redis/time-series": "1.0.3" } }, "regenerator-runtime": { @@ -10449,24 +9172,6 @@ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", "dev": true }, - "registry-auth-token": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", - "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, - "registry-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", - "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", - "dev": true, - "requires": { - "rc": "^1.2.8" - } - }, "release-zalgo": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", @@ -10494,15 +9199,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", - "dev": true, - "requires": { - "lowercase-keys": "^1.0.0" - } - }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -10538,9 +9234,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "requires": { "lru-cache": "^6.0.0" }, @@ -10552,49 +9248,27 @@ "requires": { "yallist": "^4.0.0" } - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, - "semver-diff": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", - "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", - "dev": true, - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true } } }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "~1.7.2", + "http-errors": "2.0.0", "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", + "ms": "2.1.3", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "debug": { @@ -10608,14 +9282,14 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" } } }, "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -10629,25 +9303,26 @@ } }, "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.1" + "send": "0.18.0" } }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "shebang-command": { "version": "2.0.0", @@ -10664,10 +9339,21 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", + "dev": true }, "simple-concat": { "version": "1.0.1", @@ -10684,17 +9370,34 @@ "simple-concat": "^1.0.0" } }, + "simple-update-notifier": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz", + "integrity": "sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew==", + "dev": true, + "requires": { + "semver": "~7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } + } + }, "sinon": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-12.0.1.tgz", - "integrity": "sha512-iGu29Xhym33ydkAT+aNQFBINakjq69kKO6ByPvTsm3yyIACfyQttRTP03aBP/I8GfhFmLzrnKwNNkr0ORb1udg==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.0.tgz", + "integrity": "sha512-ugA6BFmE+WrJdh0owRZHToLd32Uw3Lxq6E6LtNRU+xTVBefx632h03Q7apXWRsRdZAJ41LB8aUfn2+O4jsDNMw==", "dev": true, "requires": { "@sinonjs/commons": "^1.8.3", - "@sinonjs/fake-timers": "^8.1.0", - "@sinonjs/samsam": "^6.0.2", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^6.1.1", "diff": "^5.0.0", - "nise": "^5.1.0", + "nise": "^5.1.1", "supports-color": "^7.2.0" } }, @@ -10730,18 +9433,6 @@ "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", "requires": { "readable-stream": "^3.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "sprintf-js": { @@ -10756,9 +9447,9 @@ "integrity": "sha1-R1OT/56RR5rqYtyvDKPRSYOn+0A=" }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "string_decoder": { "version": "1.1.1", @@ -10768,31 +9459,6 @@ "safe-buffer": "~5.1.0" } }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -10842,26 +9508,6 @@ "get-port": "^3.1.0" } }, - "tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "dependencies": { - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } - }, "tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -10871,13 +9517,6 @@ "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" - }, - "dependencies": { - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - } } }, "tar-stream": { @@ -10890,18 +9529,6 @@ "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } } }, "test-exclude": { @@ -10937,12 +9564,6 @@ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true }, - "to-readable-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", - "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", - "dev": true - }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10953,9 +9574,9 @@ } }, "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "touch": { "version": "3.1.0", @@ -10974,12 +9595,12 @@ "requires": {} }, "ts-node": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", - "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.2.tgz", + "integrity": "sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==", "dev": true, "requires": { - "@cspotcode/source-map-support": "0.7.0", + "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", @@ -10990,6 +9611,7 @@ "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "dependencies": { @@ -11019,7 +9641,7 @@ "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "requires": { "safe-buffer": "^5.0.1" } @@ -11069,9 +9691,9 @@ } }, "typescript": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz", - "integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "dev": true }, "undefsafe": { @@ -11080,19 +9702,10 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, - "unique-string": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", - "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", - "dev": true, - "requires": { - "crypto-random-string": "^2.0.0" - } - }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "update-browserslist-db": { "version": "1.0.4", @@ -11104,28 +9717,6 @@ "picocolors": "^1.0.0" } }, - "update-notifier": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", - "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", - "dev": true, - "requires": { - "boxen": "^5.0.0", - "chalk": "^4.1.0", - "configstore": "^5.0.1", - "has-yarn": "^2.1.0", - "import-lazy": "^2.1.0", - "is-ci": "^2.0.0", - "is-installed-globally": "^0.4.0", - "is-npm": "^5.0.0", - "is-yarn-global": "^0.3.0", - "latest-version": "^5.1.0", - "pupa": "^2.1.1", - "semver": "^7.3.4", - "semver-diff": "^3.1.1", - "xdg-basedir": "^4.0.0" - } - }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -11135,15 +9726,6 @@ "punycode": "^2.1.0" } }, - "url-parse-lax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", - "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", - "dev": true, - "requires": { - "prepend-http": "^2.0.0" - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -11166,6 +9748,12 @@ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -11186,42 +9774,6 @@ "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", "dev": true }, - "wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "requires": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "requires": { - "string-width": "^4.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - } - } - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -11229,9 +9781,9 @@ "dev": true }, "workerpool": { - "version": "6.1.5", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.5.tgz", - "integrity": "sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", "dev": true }, "wrap-ansi": { @@ -11281,12 +9833,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "xdg-basedir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", - "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", - "dev": true - }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -11298,6 +9844,11 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/package.json b/package.json index 3f4aa49..0f92f7c 100644 --- a/package.json +++ b/package.json @@ -18,38 +18,38 @@ "author": "Ajay Ramachandran", "license": "MIT", "dependencies": { - "axios": "^0.24.0", - "better-sqlite3": "^7.4.5", - "cron": "^1.8.2", - "express": "^4.17.1", + "axios": "^0.27.2", + "better-sqlite3": "^7.6.0", + "cron": "^2.0.0", + "express": "^4.18.1", "express-promise-router": "^4.1.1", - "express-rate-limit": "^6.3.0", + "express-rate-limit": "^6.4.0", "lodash": "^4.17.21", - "pg": "^8.7.1", + "pg": "^8.7.3", "rate-limit-redis": "^3.0.1", - "redis": "^4.0.6", + "redis": "^4.2.0", "sync-mysql": "^3.0.1" }, "devDependencies": { - "@types/better-sqlite3": "^7.4.1", - "@types/cron": "^1.7.3", + "@types/better-sqlite3": "^7.5.0", + "@types/cron": "^2.0.0", "@types/express": "^4.17.13", - "@types/lodash": "^4.14.178", - "@types/mocha": "^9.0.0", - "@types/node": "^16.11.11", - "@types/pg": "^8.6.1", - "@typescript-eslint/eslint-plugin": "^5.5.0", - "@typescript-eslint/parser": "^5.5.0", - "eslint": "^8.3.0", - "mocha": "^9.1.3", - "nodemon": "^2.0.15", + "@types/lodash": "^4.14.182", + "@types/mocha": "^9.1.1", + "@types/node": "^18.0.3", + "@types/pg": "^8.6.5", + "@typescript-eslint/eslint-plugin": "^5.30.6", + "@typescript-eslint/parser": "^5.30.6", + "eslint": "^8.19.0", + "mocha": "^10.0.0", + "nodemon": "^2.0.19", "nyc": "^15.1.0", - "sinon": "^12.0.1", + "sinon": "^14.0.0", "ts-mock-imports": "^1.3.8", - "ts-node": "^10.4.0", - "typescript": "^4.5.2" + "ts-node": "^10.8.2", + "typescript": "^4.7.4" }, "engines": { - "node": ">=10" + "node": ">=16" } } diff --git a/src/utils/redis.ts b/src/utils/redis.ts index 7627d0f..76a607a 100644 --- a/src/utils/redis.ts +++ b/src/utils/redis.ts @@ -1,8 +1,8 @@ import { config } from "../config"; import { Logger } from "./logger"; import { createClient } from "redis"; -import { RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply } from "@node-redis/client/dist/lib/commands"; -import { ClientCommandOptions } from "@node-redis/client/dist/lib/client"; +import { RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply } from "@redis/client/dist/lib/commands"; +import { RedisClientOptions } from "@redis/client/dist/lib/client"; import { RedisReply } from "rate-limit-redis"; interface RedisSB { @@ -11,7 +11,7 @@ interface RedisSB { setEx(key: RedisCommandArgument, seconds: number, value: RedisCommandArgument): Promise; del(...keys: [RedisCommandArgument]): Promise; increment?(key: RedisCommandArgument): Promise; - sendCommand(args: RedisCommandArguments, options?: ClientCommandOptions): Promise; + sendCommand(args: RedisCommandArguments, options?: RedisClientOptions): Promise; quit(): Promise; } @@ -29,7 +29,7 @@ if (config.redis?.enabled) { Logger.info("Connected to redis"); const client = createClient(config.redis); client.connect(); - exportClient = client; + exportClient = client as RedisSB; const timeoutDuration = 40; const get = client.get.bind(client); From 39ad5fb62a4de5f0283606baf1ab63755e4908ff Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Thu, 14 Jul 2022 20:13:47 +0200 Subject: [PATCH 061/168] voteOnSponsorTime: move warning check before call to categoryVote --- src/routes/voteOnSponsorTime.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 941189f..021d60f 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -367,6 +367,19 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID return { status: 400 }; } + const MILLISECONDS_IN_HOUR = 3600000; + const now = Date.now(); + const warnings = (await db.prepare("all", `SELECT "reason" FROM warnings WHERE "userID" = ? AND "issueTime" > ? AND enabled = 1`, + [nonAnonUserID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))], + )); + + if (warnings.length >= config.maxNumberOfActiveWarnings) { + const warningReason = warnings[0]?.reason; + return { status: 403, message: "Vote rejected due to a warning from a moderator. This means that we noticed you were making some common mistakes that are not malicious, and we just want to clarify the rules. " + + "Could you please send a message in Discord or Matrix so we can further help you?" + + `${(warningReason.length > 0 ? ` Warning reason: '${warningReason}'` : "")}` }; + } + // no type but has category, categoryVote if (!type && category) { return categoryVote(UUID, nonAnonUserID, isVIP, isTempVIP, isOwnSubmission, category, hashedIP, finalResponse); @@ -396,19 +409,6 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID } } - const MILLISECONDS_IN_HOUR = 3600000; - const now = Date.now(); - const warnings = (await db.prepare("all", `SELECT "reason" FROM warnings WHERE "userID" = ? AND "issueTime" > ? AND enabled = 1`, - [nonAnonUserID, Math.floor(now - (config.hoursAfterWarningExpires * MILLISECONDS_IN_HOUR))], - )); - - if (warnings.length >= config.maxNumberOfActiveWarnings) { - const warningReason = warnings[0]?.reason; - return { status: 403, message: "Vote rejected due to a warning from a moderator. This means that we noticed you were making some common mistakes that are not malicious, and we just want to clarify the rules. " + - "Could you please send a message in Discord or Matrix so we can further help you?" + - `${(warningReason.length > 0 ? ` Warning reason: '${warningReason}'` : "")}` }; - } - const voteTypeEnum = (type == 0 || type == 1 || type == 20) ? voteTypes.normal : voteTypes.incorrect; // no restrictions on checkDuration From 70c98e081991fcb25a50f888818943260d6e949f Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Thu, 14 Jul 2022 20:22:44 +0200 Subject: [PATCH 062/168] voteOnSponsorTime: add ban check to categoryVote --- src/routes/voteOnSponsorTime.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 021d60f..ef5d925 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -247,7 +247,8 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i const timeSubmitted = Date.now(); const voteAmount = (isVIP || isTempVIP) ? 500 : 1; - const ableToVote = isVIP || isTempVIP || finalResponse.finalStatus === 200 || true; + const ableToVote = finalResponse.finalStatus === 200 + && (await db.prepare("get", `SELECT "userID" FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID])) === undefined; if (ableToVote) { // Add the vote From f24a1415daf4531ec149ea795c13060e151a1899 Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Thu, 14 Jul 2022 20:24:22 +0200 Subject: [PATCH 063/168] voteOnSponsorTime: check for VIP/ownSubmission before doing math in categoryVote --- src/routes/voteOnSponsorTime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index ef5d925..bd92ac9 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -291,7 +291,7 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i //TODO: In the future, raise this number from zero to make it harder to change categories // VIPs change it every time - if (nextCategoryCount - currentCategoryCount >= Math.max(Math.ceil(submissionInfo?.votes / 2), 2) || isVIP || isTempVIP || isOwnSubmission) { + if (isVIP || isTempVIP || isOwnSubmission || nextCategoryCount - currentCategoryCount >= Math.max(Math.ceil(submissionInfo?.votes / 2), 2)) { // Replace the category await db.prepare("run", `update "sponsorTimes" set "category" = ? where "UUID" = ?`, [category, UUID]); } From 9c0f0d8e4f4532a32ffcb53b53c0827f7db69c28 Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Thu, 14 Jul 2022 20:28:45 +0200 Subject: [PATCH 064/168] voteOnSponsorTime: simplify currentCategoryCount computation in categoryVote --- src/routes/voteOnSponsorTime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index bd92ac9..7f308ef 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -279,7 +279,7 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i // Change this value from 1 in the future to make it harder to change categories // Done this way without ORs incase the value is zero - const currentCategoryCount = (currentCategoryInfo === undefined || currentCategoryInfo === null) ? startingVotes : currentCategoryInfo.votes; + const currentCategoryCount = currentCategoryInfo?.votes ?? startingVotes; // Add submission as vote if (!currentCategoryInfo && submissionInfo) { From 55a8b5d514a515fe2f43f69476050b36931df81d Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Thu, 14 Jul 2022 20:56:44 +0200 Subject: [PATCH 065/168] voteOnSponsorTime: add test cases for categoryVote as warned/banned user --- test/cases/voteOnSponsorTime.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index e2bff51..f915fc2 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -59,6 +59,8 @@ describe("voteOnSponsorTime", () => { await db.prepare("run", insertSponsorTimeQuery, ["category-change-test-1", 8, 12, 0, 1, "category-change-uuid-6", categoryChangeUserHash, 0, 50, "intro", "skip", 0, 0]); await db.prepare("run", insertSponsorTimeQuery, ["category-change-test-1", 9, 14, 0, 0, "category-change-uuid-7", categoryChangeUserHash, 0, 50, "intro", "skip", 0, 0]); await db.prepare("run", insertSponsorTimeQuery, ["category-change-test-1", 7, 12, 0, 1, "category-change-uuid-8", categoryChangeUserHash, 0, 50, "intro", "skip", 0, 0]); + await db.prepare("run", insertSponsorTimeQuery, ["category-change-test-2", 7, 14, 0, 0, "category-warnvote-uuid-0", categoryChangeUserHash, 0, 50, "intro", "skip", 0, 0]); + await db.prepare("run", insertSponsorTimeQuery, ["category-change-test-2", 8, 13, 0, 0, "category-banvote-uuid-0", categoryChangeUserHash, 0, 50, "intro", "skip", 0, 0]); await db.prepare("run", insertSponsorTimeQuery, ["duration-update", 1, 10, 0, 0, "duration-update-uuid-1", "testman", 0, 0, "intro", "skip", 0, 0]); await db.prepare("run", insertSponsorTimeQuery, ["full-video", 1, 10, 0, 0, "full-video-uuid-1", "testman", 0, 0, "sponsor", "full", 0, 0]); // videoDuration change @@ -445,6 +447,32 @@ describe("voteOnSponsorTime", () => { .catch(err => done(err)); }); + it("Should not be able to vote for a category of a segment (Too many warning)", (done) => { + const UUID = "category-warnvote-uuid-0"; + const category = "preview"; + postVoteCategory("warn-voteuser01", UUID, category) + .then(res => { + assert.strictEqual(res.status, 403); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to vote for a category as a shadowbanned user, but it shouldn't add your vote to the database", (done) => { + const UUID = "category-banvote-uuid-0"; + const category = "preview"; + postVoteCategory("randomID4", UUID, category) + .then(async res => { + assert.strictEqual(res.status, 200); + const row = await getSegmentCategory(UUID); + const categoryRows = await db.prepare("all", `SELECT votes, category FROM "categoryVotes" WHERE "UUID" = ?`, [UUID]); + assert.strictEqual(row.category, "intro"); + assert.strictEqual(categoryRows.length, 0); + done(); + }) + .catch(err => done(err)); + }); + it("Should not be able to category-vote on an invalid UUID submission", (done) => { const UUID = "invalid-uuid"; postVoteCategory("randomID3", UUID, "intro") From 5c089dab13ebb2208484716a29d55abc7a9acadf Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 14 Jul 2022 17:51:26 -0400 Subject: [PATCH 066/168] don't crash from reputation --- src/utils/reputation.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/reputation.ts b/src/utils/reputation.ts index 5bea3f9..873fa5f 100644 --- a/src/utils/reputation.ts +++ b/src/utils/reputation.ts @@ -55,6 +55,8 @@ function convertRange(value: number, currentMin: number, currentMax: number, tar } export function calculateReputationFromMetrics(metrics: ReputationDBResult): number { + if (!metrics) return 0; + // Grace period if (metrics.totalSubmissions < 5) { return 0; From 9ab939456bb53344a67efb339f39e6322057dda2 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 15 Jul 2022 00:21:08 -0400 Subject: [PATCH 067/168] Don't cache empty arrays Maybe help with #483 --- src/routes/getSkipSegments.ts | 6 +++--- src/utils/queryCacher.ts | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 368d250..660ea69 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -262,7 +262,7 @@ async function chooseSegments(videoID: VideoID, service: Service, segments: DBSe const fetchData = async () => await buildSegmentGroups(segments); const groups = useCache - ? await QueryCacher.get(fetchData, skipSegmentGroupsKey(videoID, service)) + ? await QueryCacher.get(fetchData, skipSegmentGroupsKey(videoID, service), false) : await fetchData(); // Filter for only 1 item for POI categories and Full video @@ -337,7 +337,7 @@ async function buildSegmentGroups(segments: DBSegment[]): Promise { const result: OverlappingSegmentGroup[] = []; - group.segments.forEach((segment) => { + for (const segment of group.segments) { const bestGroup = result.find((group) => { // At least one segment in the group must have high % overlap or the same action type // Since POI and Full video segments will always have <= 0 overlap, they will always be in their own groups @@ -360,7 +360,7 @@ function splitPercentOverlap(groups: OverlappingSegmentGroup[]): OverlappingSegm } else { result.push({ segments: [segment], votes: segment.votes, reputation: segment.reputation, locked: segment.locked, required: segment.required }); } - }); + } return result; }); diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index d4f8647..43d54c9 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -4,7 +4,7 @@ import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, ski import { Service, VideoID, VideoIDHash } from "../types/segments.model"; import { Feature, HashedUserID, UserID } from "../types/user.model"; -async function get(fetchFromDB: () => Promise, key: string): Promise { +async function get(fetchFromDB: () => Promise, key: string, cacheEmpty = true): Promise { try { const reply = await redis.get(key); if (reply) { @@ -16,7 +16,9 @@ async function get(fetchFromDB: () => Promise, key: string): Promise { const data = await fetchFromDB(); - redis.set(key, JSON.stringify(data)).catch((err) => Logger.error(err)); + if (cacheEmpty || data) { + redis.set(key, JSON.stringify(data)).catch((err) => Logger.error(err)); + } return data; } From b11f26037717aa3eec433df849c7cffe0b703dde Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 15 Jul 2022 00:23:55 -0400 Subject: [PATCH 068/168] Log error from reputation in build segment groups Maybe help with #483 --- src/routes/getSkipSegments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 660ea69..457c2b6 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -279,7 +279,7 @@ async function chooseSegments(videoID: VideoID, service: Service, segments: DBSe //Segments with less than -1 votes are already ignored before this function is called async function buildSegmentGroups(segments: DBSegment[]): Promise { const reputationPromises = segments.map(segment => - segment.userID ? getReputation(segment.userID) : null); + segment.userID ? getReputation(segment.userID).catch((e) => Logger.error(e)) : null); //Create groups of segments that are similar to eachother //Segments must be sorted by their startTime so that we can build groups chronologically: From f2904da653d4b4e5e783f3df3d244174d513e0e8 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 15 Jul 2022 00:26:22 -0400 Subject: [PATCH 069/168] Fix type error --- src/routes/getSkipSegments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 457c2b6..78a367b 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -302,7 +302,7 @@ async function buildSegmentGroups(segments: DBSegment[]): Promise 0) { currentGroup.reputation += segment.reputation; } From 0f3efc6fb2348520cc0b7f4ad90162a343f124ca Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Sat, 16 Jul 2022 14:54:01 +0200 Subject: [PATCH 070/168] send hidden segments when requested via requiredSegments --- src/routes/getSkipSegments.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 368d250..24e9c25 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -15,7 +15,11 @@ import { getService } from "../utils/getService"; async function prepareCategorySegments(req: Request, videoID: VideoID, service: Service, segments: DBSegment[], cache: SegmentCache = { shadowHiddenSegmentIPs: {} }, useCache: boolean): Promise { const shouldFilter: boolean[] = await Promise.all(segments.map(async (segment) => { - if (segment.votes < -1 && !segment.required) { + if (segment.required) { + return true; //required - always send + } + + if (segment.hidden || segment.votes < -1) { return false; //too untrustworthy, just ignore it } @@ -169,8 +173,8 @@ async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service const fetchFromDB = () => db .prepare( "all", - `SELECT "videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "videoDuration", "reputation", "shadowHidden", "hashedVideoID", "timeSubmitted", "description" FROM "sponsorTimes" - WHERE "hashedVideoID" LIKE ? AND "service" = ? AND "hidden" = 0 ORDER BY "startTime"`, + `SELECT "videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "videoDuration", "hidden", "reputation", "shadowHidden", "hashedVideoID", "timeSubmitted", "description" FROM "sponsorTimes" + WHERE "hashedVideoID" LIKE ? AND "service" = ? ORDER BY "startTime"`, [`${hashedVideoIDPrefix}%`, service] ) as Promise; @@ -185,8 +189,8 @@ async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): P const fetchFromDB = () => db .prepare( "all", - `SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "videoDuration", "reputation", "shadowHidden", "timeSubmitted", "description" FROM "sponsorTimes" - WHERE "videoID" = ? AND "service" = ? AND "hidden" = 0 ORDER BY "startTime"`, + `SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "videoDuration", "hidden", "reputation", "shadowHidden", "timeSubmitted", "description" FROM "sponsorTimes" + WHERE "videoID" = ? AND "service" = ? ORDER BY "startTime"`, [videoID, service] ) as Promise; From 4ac70d2d7e82004f40514cba83083c6eb23e22da Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Sat, 16 Jul 2022 15:02:32 +0200 Subject: [PATCH 071/168] add test cases for getting (shadow)hidden segments with requiredSegments --- test/cases/getSkipSegments.ts | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/cases/getSkipSegments.ts b/test/cases/getSkipSegments.ts index 780107f..b252f11 100644 --- a/test/cases/getSkipSegments.ts +++ b/test/cases/getSkipSegments.ts @@ -23,6 +23,8 @@ describe("getSkipSegments", () => { await db.prepare("run", query, ["requiredSegmentVid", 60, 70, -2, 0, "requiredSegmentVid2", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, 0, ""]); await db.prepare("run", query, ["requiredSegmentVid", 80, 90, -2, 0, "requiredSegmentVid3", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, 0, ""]); await db.prepare("run", query, ["requiredSegmentVid", 80, 90, 2, 0, "requiredSegmentVid4", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, 0, ""]); + await db.prepare("run", query, ["requiredSegmentVid", 60, 70, 0, 0, "requiredSegmentVid-hidden", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 1, 0, ""]); + await db.prepare("run", query, ["requiredSegmentVid", 80, 90, 0, 0, "requiredSegmentVid-shadowhidden", "testman", 0, 50, "sponsor", "skip", "YouTube", 0, 0, 1, ""]); await db.prepare("run", query, ["chapterVid", 60, 80, 2, 0, "chapterVid-1", "testman", 0, 50, "chapter", "chapter", "YouTube", 0, 0, 0, "Chapter 1"]); await db.prepare("run", query, ["chapterVid", 70, 75, 2, 0, "chapterVid-2", "testman", 0, 50, "chapter", "chapter", "YouTube", 0, 0, 0, "Chapter 2"]); await db.prepare("run", query, ["chapterVid", 71, 75, 2, 0, "chapterVid-3", "testman", 0, 50, "chapter", "chapter", "YouTube", 0, 0, 0, "Chapter 3"]); @@ -447,4 +449,42 @@ describe("getSkipSegments", () => { }) .catch(err => done(err)); }); + + it("Should be able to get hidden segments with requiredSegments", (done) => { + const required3 = "requiredSegmentVid3"; + const requiredHidden = "requiredSegmentVid-hidden"; + client.get(endpoint, { params: { videoID: "requiredSegmentVid", requiredSegments: `["${requiredHidden}","${required3}"]` } }) + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + assert.strictEqual(data.length, 2); + const expected = [{ + UUID: requiredHidden, + }, { + UUID: required3, + }]; + assert.ok(partialDeepEquals(data, expected)); + done(); + }) + .catch(err => done(err)); + }); + + it("Should be able to get shadowhidden segments with requiredSegments", (done) => { + const required2 = "requiredSegmentVid2"; + const requiredShadowHidden = "requiredSegmentVid-shadowhidden"; + client.get(endpoint, { params: { videoID: "requiredSegmentVid", requiredSegments: `["${required2}","${requiredShadowHidden}"]` } }) + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + assert.strictEqual(data.length, 2); + const expected = [{ + UUID: required2, + }, { + UUID: requiredShadowHidden, + }]; + assert.ok(partialDeepEquals(data, expected)); + done(); + }) + .catch(err => done(err)); + }); }); From 288f7d45e7bd0bae61d6fe79882da0980bff7b2b Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 20 Jul 2022 00:23:02 -0400 Subject: [PATCH 072/168] Revert "Don't cache empty arrays" This reverts commit 9ab939456bb53344a67efb339f39e6322057dda2. --- src/routes/getSkipSegments.ts | 6 +++--- src/utils/queryCacher.ts | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 78a367b..8d623e7 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -262,7 +262,7 @@ async function chooseSegments(videoID: VideoID, service: Service, segments: DBSe const fetchData = async () => await buildSegmentGroups(segments); const groups = useCache - ? await QueryCacher.get(fetchData, skipSegmentGroupsKey(videoID, service), false) + ? await QueryCacher.get(fetchData, skipSegmentGroupsKey(videoID, service)) : await fetchData(); // Filter for only 1 item for POI categories and Full video @@ -337,7 +337,7 @@ async function buildSegmentGroups(segments: DBSegment[]): Promise { const result: OverlappingSegmentGroup[] = []; - for (const segment of group.segments) { + group.segments.forEach((segment) => { const bestGroup = result.find((group) => { // At least one segment in the group must have high % overlap or the same action type // Since POI and Full video segments will always have <= 0 overlap, they will always be in their own groups @@ -360,7 +360,7 @@ function splitPercentOverlap(groups: OverlappingSegmentGroup[]): OverlappingSegm } else { result.push({ segments: [segment], votes: segment.votes, reputation: segment.reputation, locked: segment.locked, required: segment.required }); } - } + }); return result; }); diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index 43d54c9..d4f8647 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -4,7 +4,7 @@ import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, ski import { Service, VideoID, VideoIDHash } from "../types/segments.model"; import { Feature, HashedUserID, UserID } from "../types/user.model"; -async function get(fetchFromDB: () => Promise, key: string, cacheEmpty = true): Promise { +async function get(fetchFromDB: () => Promise, key: string): Promise { try { const reply = await redis.get(key); if (reply) { @@ -16,9 +16,7 @@ async function get(fetchFromDB: () => Promise, key: string, cacheEmpty = t const data = await fetchFromDB(); - if (cacheEmpty || data) { - redis.set(key, JSON.stringify(data)).catch((err) => Logger.error(err)); - } + redis.set(key, JSON.stringify(data)).catch((err) => Logger.error(err)); return data; } From f4b66d30ec27e811ca13f69e19598e819f44e95b Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 20 Jul 2022 00:31:51 -0400 Subject: [PATCH 073/168] Use pool query since this should catch idle errors https://github.com/brianc/node-postgres/issues/1324#issuecomment-623311914 Helps with https://github.com/ajayyy/SponsorBlockServer/issues/487 --- src/databases/Postgres.ts | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 2d2e925..5cf1f7e 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -94,10 +94,8 @@ export class Postgres implements IDatabase { Logger.debug(`prepare (postgres): type: ${type}, query: ${query}, params: ${params}`); - let client: PoolClient; try { - client = await this.getClient(type); - const queryResult = await client.query({ text: query, values: params }); + const queryResult = await this.getPool(type).query({ text: query, values: params }); switch (type) { case "get": { @@ -116,24 +114,18 @@ export class Postgres implements IDatabase { } } catch (err) { Logger.error(`prepare (postgres): ${err}`); - } finally { - try { - client?.release(); - } catch (err) { - Logger.error(`prepare (postgres): ${err}`); - } } } - private getClient(type: string): Promise { + private getPool(type: string): Pool { const readAvailable = this.poolRead && (type === "get" || type === "all"); const ignroreReadDueToFailure = this.lastPoolReadFail > Date.now() - 1000 * 30; const readDueToFailure = this.lastPoolFail > Date.now() - 1000 * 30; if (readAvailable && !ignroreReadDueToFailure && (readDueToFailure || Math.random() > 1 / (this.config.postgresReadOnly.weight + 1))) { - return this.poolRead.connect(); + return this.poolRead; } else { - return this.pool.connect(); + return this.pool; } } From 1e441c3ebf9b7337c54506338ed43f9a0a25a992 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 20 Jul 2022 00:40:07 -0400 Subject: [PATCH 074/168] Only use read replica for shorter queries Help with https://github.com/ajayyy/SponsorBlockServer/issues/487 --- src/databases/IDatabase.ts | 8 ++++++-- src/databases/Postgres.ts | 10 +++++----- src/routes/getSkipSegments.ts | 8 +++++--- src/routes/voteOnSponsorTime.ts | 24 ++++++++++++------------ 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/src/databases/IDatabase.ts b/src/databases/IDatabase.ts index 86774f4..0211203 100644 --- a/src/databases/IDatabase.ts +++ b/src/databases/IDatabase.ts @@ -1,7 +1,11 @@ +export interface QueryOption { + useReplica?: boolean; +} + export interface IDatabase { init(): Promise; - prepare(type: QueryType, query: string, params?: any[]): Promise; + prepare(type: QueryType, query: string, params?: any[], options?: QueryOption): Promise; } -export type QueryType = "get" | "all" | "run"; +export type QueryType = "get" | "all" | "run"; \ No newline at end of file diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 5cf1f7e..cd8efdf 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -1,5 +1,5 @@ import { Logger } from "../utils/logger"; -import { IDatabase, QueryType } from "./IDatabase"; +import { IDatabase, QueryOption, QueryType } from "./IDatabase"; import { Client, Pool, PoolClient, types } from "pg"; import fs from "fs"; @@ -82,7 +82,7 @@ export class Postgres implements IDatabase { } } - async prepare(type: QueryType, query: string, params?: any[]): Promise { + async prepare(type: QueryType, query: string, params?: any[], options: QueryOption = {}): Promise { // Convert query to use numbered parameters let count = 1; for (let char = 0; char < query.length; char++) { @@ -95,7 +95,7 @@ export class Postgres implements IDatabase { Logger.debug(`prepare (postgres): type: ${type}, query: ${query}, params: ${params}`); try { - const queryResult = await this.getPool(type).query({ text: query, values: params }); + const queryResult = await this.getPool(type, options).query({ text: query, values: params }); switch (type) { case "get": { @@ -117,8 +117,8 @@ export class Postgres implements IDatabase { } } - private getPool(type: string): Pool { - const readAvailable = this.poolRead && (type === "get" || type === "all"); + private getPool(type: string, options: QueryOption): Pool { + const readAvailable = this.poolRead && options.useReplica && (type === "get" || type === "all"); const ignroreReadDueToFailure = this.lastPoolReadFail > Date.now() - 1000 * 30; const readDueToFailure = this.lastPoolFail > Date.now() - 1000 * 30; if (readAvailable && !ignroreReadDueToFailure && (readDueToFailure || diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 8d623e7..40fbf38 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -33,7 +33,7 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, service: const service = getService(req?.query?.service as string); const fetchData = () => privateDB.prepare("all", 'SELECT "hashedIP" FROM "sponsorTimes" WHERE "videoID" = ? AND "timeSubmitted" = ? AND "service" = ?', - [videoID, segment.timeSubmitted, service]) as Promise<{ hashedIP: HashedIP }[]>; + [videoID, segment.timeSubmitted, service], { useReplica: true }) as Promise<{ hashedIP: HashedIP }[]>; cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted] = await QueryCacher.get(fetchData, shadowHiddenIPKey(videoID, segment.timeSubmitted, service)); } @@ -171,7 +171,8 @@ async function getSegmentsFromDBByHash(hashedVideoIDPrefix: VideoIDHash, service "all", `SELECT "videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "videoDuration", "reputation", "shadowHidden", "hashedVideoID", "timeSubmitted", "description" FROM "sponsorTimes" WHERE "hashedVideoID" LIKE ? AND "service" = ? AND "hidden" = 0 ORDER BY "startTime"`, - [`${hashedVideoIDPrefix}%`, service] + [`${hashedVideoIDPrefix}%`, service], + { useReplica: true } ) as Promise; if (hashedVideoIDPrefix.length === 4) { @@ -187,7 +188,8 @@ async function getSegmentsFromDBByVideoID(videoID: VideoID, service: Service): P "all", `SELECT "startTime", "endTime", "votes", "locked", "UUID", "userID", "category", "actionType", "videoDuration", "reputation", "shadowHidden", "timeSubmitted", "description" FROM "sponsorTimes" WHERE "videoID" = ? AND "service" = ? AND "hidden" = 0 ORDER BY "startTime"`, - [videoID, service] + [videoID, service], + { useReplica: true } ) as Promise; return await QueryCacher.get(fetchFromDB, skipSegmentsKey(videoID, service)); diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 7f308ef..c30e976 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -211,7 +211,7 @@ async function sendWebhooks(voteData: VoteData) { async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, isTempVIP: boolean, isOwnSubmission: boolean, category: Category , hashedIP: HashedIP, finalResponse: FinalResponse): Promise<{ status: number, message?: string }> { // Check if they've already made a vote - const usersLastVoteInfo = await privateDB.prepare("get", `select count(*) as votes, category from "categoryVotes" where "UUID" = ? and "userID" = ? group by category`, [UUID, userID]); + const usersLastVoteInfo = await privateDB.prepare("get", `select count(*) as votes, category from "categoryVotes" where "UUID" = ? and "userID" = ? group by category`, [UUID, userID], { useReplica: true }); if (usersLastVoteInfo?.category === category) { // Double vote, ignore @@ -219,7 +219,7 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i } const segmentInfo = (await db.prepare("get", `SELECT "category", "actionType", "videoID", "hashedVideoID", "service", "userID", "locked" FROM "sponsorTimes" WHERE "UUID" = ?`, - [UUID])) as {category: Category, actionType: ActionType, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID, locked: number}; + [UUID], { useReplica: true })) as {category: Category, actionType: ActionType, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID, locked: number}; if (segmentInfo.actionType === ActionType.Full) { return { status: 400, message: "Not allowed to change category of a full video segment" }; @@ -232,7 +232,7 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i } // Ignore vote if the next category is locked - const nextCategoryLocked = await db.prepare("get", `SELECT "videoID", "category" FROM "lockCategories" WHERE "videoID" = ? AND "service" = ? AND "category" = ?`, [segmentInfo.videoID, segmentInfo.service, category]); + const nextCategoryLocked = await db.prepare("get", `SELECT "videoID", "category" FROM "lockCategories" WHERE "videoID" = ? AND "service" = ? AND "category" = ?`, [segmentInfo.videoID, segmentInfo.service, category], { useReplica: true }); if (nextCategoryLocked && !isVIP) { return { status: 200 }; } @@ -242,13 +242,13 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i return { status: 200 }; } - const nextCategoryInfo = await db.prepare("get", `select votes from "categoryVotes" where "UUID" = ? and category = ?`, [UUID, category]); + const nextCategoryInfo = await db.prepare("get", `select votes from "categoryVotes" where "UUID" = ? and category = ?`, [UUID, category], { useReplica: true }); const timeSubmitted = Date.now(); const voteAmount = (isVIP || isTempVIP) ? 500 : 1; const ableToVote = finalResponse.finalStatus === 200 - && (await db.prepare("get", `SELECT "userID" FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID])) === undefined; + && (await db.prepare("get", `SELECT "userID" FROM "shadowBannedUsers" WHERE "userID" = ?`, [userID], { useReplica: true })) === undefined; if (ableToVote) { // Add the vote @@ -271,9 +271,9 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i } // See if the submissions category is ready to change - const currentCategoryInfo = await db.prepare("get", `select votes from "categoryVotes" where "UUID" = ? and category = ?`, [UUID, segmentInfo.category]); + const currentCategoryInfo = await db.prepare("get", `select votes from "categoryVotes" where "UUID" = ? and category = ?`, [UUID, segmentInfo.category], { useReplica: true }); - const submissionInfo = await db.prepare("get", `SELECT "userID", "timeSubmitted", "votes" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]); + const submissionInfo = await db.prepare("get", `SELECT "userID", "timeSubmitted", "votes" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID], { useReplica: true }); const isSubmissionVIP = submissionInfo && await isUserVIP(submissionInfo.userID); const startingVotes = isSubmissionVIP ? 10000 : 1; @@ -391,7 +391,7 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID const isSegmentLocked = segmentInfo.locked; const isVideoLocked = async () => !!(await db.prepare("get", `SELECT "category" FROM "lockCategories" WHERE "videoID" = ? AND "service" = ? AND "category" = ? AND "actionType" = ?`, - [segmentInfo.videoID, segmentInfo.service, segmentInfo.category, segmentInfo.actionType])); + [segmentInfo.videoID, segmentInfo.service, segmentInfo.category, segmentInfo.actionType], { useReplica: true })); if (isSegmentLocked || await isVideoLocked()) { finalResponse.blockVote = true; finalResponse.webhookType = VoteWebhookType.Rejected; @@ -420,7 +420,7 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID try { // check if vote has already happened - const votesRow = await privateDB.prepare("get", `SELECT "type" FROM "votes" WHERE "userID" = ? AND "UUID" = ?`, [userID, UUID]); + const votesRow = await privateDB.prepare("get", `SELECT "type" FROM "votes" WHERE "userID" = ? AND "UUID" = ?`, [userID, UUID], { useReplica: true }); // -1 for downvote, 1 for upvote. Maybe more depending on reputation in the future // oldIncrementAmount will be zero if row is null @@ -478,9 +478,9 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID && !(originalType === VoteType.Malicious && segmentInfo.actionType !== ActionType.Chapter) && !finalResponse.blockVote && finalResponse.finalStatus === 200 - && (await db.prepare("get", `SELECT "userID" FROM "sponsorTimes" WHERE "userID" = ?`, [nonAnonUserID])) !== undefined - && (await db.prepare("get", `SELECT "userID" FROM "shadowBannedUsers" WHERE "userID" = ?`, [nonAnonUserID])) === undefined - && (await privateDB.prepare("get", `SELECT "UUID" FROM "votes" WHERE "UUID" = ? AND "hashedIP" = ? AND "userID" != ?`, [UUID, hashedIP, userID])) === undefined); + && (await db.prepare("get", `SELECT "userID" FROM "sponsorTimes" WHERE "userID" = ?`, [nonAnonUserID]), { useReplica: true }) !== undefined + && (await db.prepare("get", `SELECT "userID" FROM "shadowBannedUsers" WHERE "userID" = ?`, [nonAnonUserID]), { useReplica: true }) === undefined + && (await privateDB.prepare("get", `SELECT "UUID" FROM "votes" WHERE "UUID" = ? AND "hashedIP" = ? AND "userID" != ?`, [UUID, hashedIP, userID]), { useReplica: true }) === undefined); const ableToVote = isVIP || isTempVIP || userAbleToVote; From f1ed8eff84015f71fb8c1266c3d050b2b34cde7e Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 20 Jul 2022 00:43:59 -0400 Subject: [PATCH 075/168] Fix useReplica change breaking voting --- src/routes/voteOnSponsorTime.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index c30e976..0601715 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -478,9 +478,9 @@ export async function vote(ip: IPAddress, UUID: SegmentUUID, paramUserID: UserID && !(originalType === VoteType.Malicious && segmentInfo.actionType !== ActionType.Chapter) && !finalResponse.blockVote && finalResponse.finalStatus === 200 - && (await db.prepare("get", `SELECT "userID" FROM "sponsorTimes" WHERE "userID" = ?`, [nonAnonUserID]), { useReplica: true }) !== undefined - && (await db.prepare("get", `SELECT "userID" FROM "shadowBannedUsers" WHERE "userID" = ?`, [nonAnonUserID]), { useReplica: true }) === undefined - && (await privateDB.prepare("get", `SELECT "UUID" FROM "votes" WHERE "UUID" = ? AND "hashedIP" = ? AND "userID" != ?`, [UUID, hashedIP, userID]), { useReplica: true }) === undefined); + && (await db.prepare("get", `SELECT "userID" FROM "sponsorTimes" WHERE "userID" = ?`, [nonAnonUserID], { useReplica: true })) !== undefined + && (await db.prepare("get", `SELECT "userID" FROM "shadowBannedUsers" WHERE "userID" = ?`, [nonAnonUserID], { useReplica: true })) === undefined + && (await privateDB.prepare("get", `SELECT "UUID" FROM "votes" WHERE "UUID" = ? AND "hashedIP" = ? AND "userID" != ?`, [UUID, hashedIP, userID], { useReplica: true })) === undefined); const ableToVote = isVIP || isTempVIP || userAbleToVote; From dd47f876164dee0cd073c7d0f7061f5ae0732442 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 20 Jul 2022 01:21:13 -0400 Subject: [PATCH 076/168] Add more things to read from replica --- src/routes/getSavedTimeForUser.ts | 2 +- src/routes/getUserInfo.ts | 16 ++++++++-------- src/routes/getUsername.ts | 2 +- src/routes/getViewsForUser.ts | 2 +- src/utils/features.ts | 2 +- src/utils/isUserVIP.ts | 3 ++- src/utils/reputation.ts | 2 +- 7 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/routes/getSavedTimeForUser.ts b/src/routes/getSavedTimeForUser.ts index b7c7424..e95610a 100644 --- a/src/routes/getSavedTimeForUser.ts +++ b/src/routes/getSavedTimeForUser.ts @@ -18,7 +18,7 @@ export async function getSavedTimeForUser(req: Request, res: Response): Promise< userID = await getHashCache(userID); try { - const row = await db.prepare("get", 'SELECT SUM(((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views") as "minutesSaved" FROM "sponsorTimes" WHERE "userID" = ? AND "votes" > -1 AND "shadowHidden" != 1 ', [maxRewardTimePerSegmentInSeconds, maxRewardTimePerSegmentInSeconds, userID]); + const row = await db.prepare("get", 'SELECT SUM(((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views") as "minutesSaved" FROM "sponsorTimes" WHERE "userID" = ? AND "votes" > -1 AND "shadowHidden" != 1 ', [maxRewardTimePerSegmentInSeconds, maxRewardTimePerSegmentInSeconds, userID], { useReplica: true }); if (row.minutesSaved != null) { return res.send({ diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index 0c913c3..8f31a01 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -15,7 +15,7 @@ async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ min const row = await db.prepare("get", `SELECT SUM(((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views") as "minutesSaved", count(*) as "segmentCount" FROM "sponsorTimes" - WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [maxRewardTime, maxRewardTime, userID]); + WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [maxRewardTime, maxRewardTime, userID], { useReplica: true }); if (row.minutesSaved != null) { return { minutesSaved: row.minutesSaved, @@ -34,7 +34,7 @@ async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ min async function dbGetIgnoredSegmentCount(userID: HashedUserID): Promise { try { - const row = await db.prepare("get", `SELECT COUNT(*) as "ignoredSegmentCount" FROM "sponsorTimes" WHERE "userID" = ? AND ( "votes" <= -2 OR "shadowHidden" = 1 )`, [userID]); + const row = await db.prepare("get", `SELECT COUNT(*) as "ignoredSegmentCount" FROM "sponsorTimes" WHERE "userID" = ? AND ( "votes" <= -2 OR "shadowHidden" = 1 )`, [userID], { useReplica: true }); return row?.ignoredSegmentCount ?? 0; } catch (err) { return null; @@ -52,7 +52,7 @@ async function dbGetUsername(userID: HashedUserID) { async function dbGetViewsForUser(userID: HashedUserID) { try { - const row = await db.prepare("get", `SELECT SUM("views") as "viewCount" FROM "sponsorTimes" WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [userID]); + const row = await db.prepare("get", `SELECT SUM("views") as "viewCount" FROM "sponsorTimes" WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [userID], { useReplica: true }); return row?.viewCount ?? 0; } catch (err) { return false; @@ -61,7 +61,7 @@ async function dbGetViewsForUser(userID: HashedUserID) { async function dbGetIgnoredViewsForUser(userID: HashedUserID) { try { - const row = await db.prepare("get", `SELECT SUM("views") as "ignoredViewCount" FROM "sponsorTimes" WHERE "userID" = ? AND ( "votes" <= -2 OR "shadowHidden" = 1 )`, [userID]); + const row = await db.prepare("get", `SELECT SUM("views") as "ignoredViewCount" FROM "sponsorTimes" WHERE "userID" = ? AND ( "votes" <= -2 OR "shadowHidden" = 1 )`, [userID], { useReplica: true }); return row?.ignoredViewCount ?? 0; } catch (err) { return false; @@ -70,7 +70,7 @@ async function dbGetIgnoredViewsForUser(userID: HashedUserID) { async function dbGetWarningsForUser(userID: HashedUserID): Promise { try { - const row = await db.prepare("get", `SELECT COUNT(*) as total FROM "warnings" WHERE "userID" = ? AND "enabled" = 1`, [userID]); + const row = await db.prepare("get", `SELECT COUNT(*) as total FROM "warnings" WHERE "userID" = ? AND "enabled" = 1`, [userID], { useReplica: true }); return row?.total ?? 0; } catch (err) { Logger.error(`Couldn't get warnings for user ${userID}. returning 0`); @@ -80,7 +80,7 @@ async function dbGetWarningsForUser(userID: HashedUserID): Promise { async function dbGetLastSegmentForUser(userID: HashedUserID): Promise { try { - const row = await db.prepare("get", `SELECT "UUID" FROM "sponsorTimes" WHERE "userID" = ? ORDER BY "timeSubmitted" DESC LIMIT 1`, [userID]); + const row = await db.prepare("get", `SELECT "UUID" FROM "sponsorTimes" WHERE "userID" = ? ORDER BY "timeSubmitted" DESC LIMIT 1`, [userID], { useReplica: true }); return row?.UUID ?? null; } catch (err) { return null; @@ -89,7 +89,7 @@ async function dbGetLastSegmentForUser(userID: HashedUserID): Promise { try { - const row = await db.prepare("get", `SELECT reason FROM "warnings" WHERE "userID" = ? AND "enabled" = 1 ORDER BY "issueTime" DESC LIMIT 1`, [userID]); + const row = await db.prepare("get", `SELECT reason FROM "warnings" WHERE "userID" = ? AND "enabled" = 1 ORDER BY "issueTime" DESC LIMIT 1`, [userID], { useReplica: true }); return row?.reason ?? ""; } catch (err) { Logger.error(`Couldn't get reason for user ${userID}. returning blank`); @@ -99,7 +99,7 @@ async function dbGetActiveWarningReasonForUser(userID: HashedUserID): Promise { try { - const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID]); + const row = await db.prepare("get", `SELECT count(*) as "userCount" FROM "shadowBannedUsers" WHERE "userID" = ? LIMIT 1`, [userID], { useReplica: true }); return row?.userCount > 0 ?? false; } catch (err) { return false; diff --git a/src/routes/getUsername.ts b/src/routes/getUsername.ts index 6411434..28098fe 100644 --- a/src/routes/getUsername.ts +++ b/src/routes/getUsername.ts @@ -15,7 +15,7 @@ export async function getUsername(req: Request, res: Response): Promise { return await QueryCacher.get(async () => { - const result = await db.prepare("get", 'SELECT "feature" from "userFeatures" WHERE "userID" = ? AND "feature" = ?', [userID, feature]); + const result = await db.prepare("get", 'SELECT "feature" from "userFeatures" WHERE "userID" = ? AND "feature" = ?', [userID, feature], { useReplica: true }); return !!result; }, userFeatureKey(userID, feature)); } \ No newline at end of file diff --git a/src/utils/isUserVIP.ts b/src/utils/isUserVIP.ts index b4ac6c1..ed1ba09 100644 --- a/src/utils/isUserVIP.ts +++ b/src/utils/isUserVIP.ts @@ -2,5 +2,6 @@ import { db } from "../databases/databases"; import { HashedUserID } from "../types/user.model"; export async function isUserVIP(userID: HashedUserID): Promise { - return (await db.prepare("get", `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ? LIMIT 1`, [userID]))?.userCount > 0; + return (await db.prepare("get", `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ? LIMIT 1`, + [userID], { useReplica: true }))?.userCount > 0; } diff --git a/src/utils/reputation.ts b/src/utils/reputation.ts index 873fa5f..092f384 100644 --- a/src/utils/reputation.ts +++ b/src/utils/reputation.ts @@ -40,7 +40,7 @@ export async function getReputation(userID: UserID): Promise { SELECT * FROM "lockCategories" as l WHERE l."videoID" = "a"."videoID" AND l."service" = "a"."service" AND l."category" = "a"."category" LIMIT 1) THEN 1 ELSE 0 END) AS "mostUpvotedInLockedVideoSum" - FROM "sponsorTimes" as "a" WHERE "userID" = ?`, [userID, weekAgo, pastDate, userID]) as Promise; + FROM "sponsorTimes" as "a" WHERE "userID" = ?`, [userID, weekAgo, pastDate, userID], { useReplica: true }) as Promise; const result = await QueryCacher.get(fetchFromDB, reputationKey(userID)); From 96a75a833570be053598de7be559362ad6035f92 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 20 Jul 2022 14:42:04 -0400 Subject: [PATCH 077/168] Add retry logic to postgres requests Maybe solves https://github.com/ajayyy/SponsorBlockServer/issues/487 --- src/databases/Postgres.ts | 45 ++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index cd8efdf..1a27b08 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -32,6 +32,8 @@ export class Postgres implements IDatabase { private poolRead: Pool; private lastPoolReadFail = 0; + private maxTries = 3; + constructor(private config: DatabaseConfig) {} async init(): Promise { @@ -94,27 +96,36 @@ export class Postgres implements IDatabase { Logger.debug(`prepare (postgres): type: ${type}, query: ${query}, params: ${params}`); - try { - const queryResult = await this.getPool(type, options).query({ text: query, values: params }); + let tries = 0; + do { + tries++; - switch (type) { - case "get": { - const value = queryResult.rows[0]; - Logger.debug(`result (postgres): ${JSON.stringify(value)}`); - return value; + try { + const queryResult = await this.getPool(type, options).query({ text: query, values: params }); + + switch (type) { + case "get": { + const value = queryResult.rows[0]; + Logger.debug(`result (postgres): ${JSON.stringify(value)}`); + return value; + } + case "all": { + const values = queryResult.rows; + Logger.debug(`result (postgres): ${JSON.stringify(values)}`); + return values; + } + case "run": { + return; + } } - case "all": { - const values = queryResult.rows; - Logger.debug(`result (postgres): ${JSON.stringify(values)}`); - return values; - } - case "run": { - break; + } catch (err) { + if (err instanceof Error && err.message.includes("terminating connection due to conflict with recovery")) { + options.useReplica = false; } + + Logger.error(`prepare (postgres) try ${tries}: ${err}`); } - } catch (err) { - Logger.error(`prepare (postgres): ${err}`); - } + } while ((type === "get" || type === "all") && tries < this.maxTries); } private getPool(type: string, options: QueryOption): Pool { From 6e1df18c384f551c4177f0cf0a50bbd816837927 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 20 Jul 2022 17:20:19 -0400 Subject: [PATCH 078/168] Allow updating warning messages --- src/routes/postWarning.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/postWarning.ts b/src/routes/postWarning.ts index c6b1f09..8a503e7 100644 --- a/src/routes/postWarning.ts +++ b/src/routes/postWarning.ts @@ -52,8 +52,8 @@ export async function postWarning(req: Request, res: Response): Promise Date: Wed, 20 Jul 2022 18:52:27 -0400 Subject: [PATCH 079/168] Allow removing your own warning --- src/routes/postWarning.ts | 12 +++++------- test/cases/postWarning.ts | 29 +++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/routes/postWarning.ts b/src/routes/postWarning.ts index 8a503e7..f003570 100644 --- a/src/routes/postWarning.ts +++ b/src/routes/postWarning.ts @@ -22,17 +22,15 @@ function checkExpiredWarning(warning: warningEntry): boolean { } export async function postWarning(req: Request, res: Response): Promise { - // exit early if no body passed in - if (!req.body.userID && !req.body.issuerUserID) return res.status(400).json({ "message": "Missing parameters" }); - // Collect user input data - const issuerUserID: HashedUserID = await getHashCache( req.body.issuerUserID); - const userID: UserID = req.body.userID; + if (!req.body.userID) return res.status(400).json({ "message": "Missing parameters" }); + + const issuerUserID: HashedUserID = req.body.issuerUserID ? await getHashCache(req.body.issuerUserID as UserID) : null; + const userID: HashedUserID = issuerUserID ? req.body.userID : await getHashCache(req.body.userID as UserID); const issueTime = new Date().getTime(); const enabled: boolean = req.body.enabled ?? true; const reason: string = req.body.reason ?? ""; - // Ensure user is a VIP - if (!await isUserVIP(issuerUserID)) { + if ((!issuerUserID && enabled) ||(issuerUserID && !await isUserVIP(issuerUserID))) { Logger.warn(`Permission violation: User ${issuerUserID} attempted to warn user ${userID}.`); return res.status(403).json({ "message": "Not a VIP" }); } diff --git a/test/cases/postWarning.ts b/test/cases/postWarning.ts index 93730b3..b49910f 100644 --- a/test/cases/postWarning.ts +++ b/test/cases/postWarning.ts @@ -9,6 +9,8 @@ describe("postWarning", () => { const endpoint = "/api/warnUser"; const getWarning = (userID: string) => db.prepare("get", `SELECT "userID", "issueTime", "issuerUserID", enabled, "reason" FROM warnings WHERE "userID" = ?`, [userID]); + const warnedUser = getHash("warning-0"); + before(async () => { await db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES (?)`, [getHash("warning-vip")]); }); @@ -16,7 +18,7 @@ describe("postWarning", () => { it("Should be able to create warning if vip (exp 200)", (done) => { const json = { issuerUserID: "warning-vip", - userID: "warning-0", + userID: warnedUser, reason: "warning-reason-0" }; client.post(endpoint, json) @@ -37,7 +39,7 @@ describe("postWarning", () => { it("Should be not be able to create a duplicate warning if vip", (done) => { const json = { issuerUserID: "warning-vip", - userID: "warning-0", + userID: warnedUser, }; client.post(endpoint, json) @@ -57,7 +59,7 @@ describe("postWarning", () => { it("Should be able to remove warning if vip", (done) => { const json = { issuerUserID: "warning-vip", - userID: "warning-0", + userID: warnedUser, enabled: false }; @@ -100,7 +102,7 @@ describe("postWarning", () => { it("Should re-enable disabled warning", (done) => { const json = { issuerUserID: "warning-vip", - userID: "warning-0", + userID: warnedUser, enabled: true }; @@ -116,4 +118,23 @@ describe("postWarning", () => { }) .catch(err => done(err)); }); + + it("Should be able to remove your own warning", (done) => { + const json = { + userID: "warning-0", + enabled: false + }; + + client.post(endpoint, json) + .then(async res => { + assert.strictEqual(res.status, 200); + const data = await getWarning(warnedUser); + const expected = { + enabled: 0 + }; + assert.ok(partialDeepEquals(data, expected)); + done(); + }) + .catch(err => done(err)); + }); }); From c1d345441c87d924f5d496122cdd12f4d7a03e99 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 21 Jul 2022 00:15:40 -0400 Subject: [PATCH 080/168] Pin github actions --- .github/workflows/take-action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/take-action.yml b/.github/workflows/take-action.yml index 4529ffa..ebddca3 100644 --- a/.github/workflows/take-action.yml +++ b/.github/workflows/take-action.yml @@ -7,6 +7,6 @@ jobs: runs-on: ubuntu-latest steps: - name: take the issue - uses: bdougie/take-action@main + uses: bdougie/take-action@28b86cd8d25593f037406ecbf96082db2836e928 env: GITHUB_TOKEN: ${{ github.token }} From 603bad49676a8ad219244b08d8b6099973d0947c Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 21 Jul 2022 14:19:41 -0400 Subject: [PATCH 081/168] Add another warning test --- src/routes/postWarning.ts | 2 +- test/cases/postWarning.ts | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/routes/postWarning.ts b/src/routes/postWarning.ts index f003570..f9eaa4c 100644 --- a/src/routes/postWarning.ts +++ b/src/routes/postWarning.ts @@ -30,7 +30,7 @@ export async function postWarning(req: Request, res: Response): Promise { }) .catch(err => done(err)); }); + + it("Should be able to add your own warning", (done) => { + const json = { + userID: "warning-0" + }; + + client.post(endpoint, json) + .then(async res => { + assert.strictEqual(res.status, 403); + const data = await getWarning(warnedUser); + const expected = { + enabled: 0 + }; + assert.ok(partialDeepEquals(data, expected)); + done(); + }) + .catch(err => done(err)); + }); }); From b616ac990bf56ba1b9a02dcfd6839c9d64e833a1 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 22 Jul 2022 16:39:31 -0400 Subject: [PATCH 082/168] Add statement timeout for read --- src/databases/Postgres.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 1a27b08..a0014be 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -50,7 +50,10 @@ export class Postgres implements IDatabase { }); if (this.config.postgresReadOnly && this.config.postgresReadOnly.enabled) { - this.poolRead = new Pool(this.config.postgresReadOnly); + this.poolRead = new Pool({ + ...this.config.postgresReadOnly, + statement_timeout: 120 + }); this.poolRead.on("error", (err, client) => { Logger.error(err.stack); this.lastPoolReadFail = Date.now(); @@ -119,9 +122,7 @@ export class Postgres implements IDatabase { } } } catch (err) { - if (err instanceof Error && err.message.includes("terminating connection due to conflict with recovery")) { - options.useReplica = false; - } + options.useReplica = false; Logger.error(`prepare (postgres) try ${tries}: ${err}`); } From 10491da7c2f51ec453eb5a54852b4d704b78b67d Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 22 Jul 2022 17:27:32 -0400 Subject: [PATCH 083/168] Raise timeout --- src/databases/Postgres.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index a0014be..71571b1 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -1,6 +1,6 @@ import { Logger } from "../utils/logger"; import { IDatabase, QueryOption, QueryType } from "./IDatabase"; -import { Client, Pool, PoolClient, types } from "pg"; +import { Client, Pool, types } from "pg"; import fs from "fs"; import { CustomPostgresConfig, CustomPostgresReadOnlyConfig } from "../types/config.model"; @@ -52,7 +52,7 @@ export class Postgres implements IDatabase { if (this.config.postgresReadOnly && this.config.postgresReadOnly.enabled) { this.poolRead = new Pool({ ...this.config.postgresReadOnly, - statement_timeout: 120 + statement_timeout: 300 }); this.poolRead.on("error", (err, client) => { Logger.error(err.stack); From 071aae5cf7b8a363b983c80bb787718047d5f8e8 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 22 Jul 2022 17:31:17 -0400 Subject: [PATCH 084/168] Add timeout for write too --- src/databases/Postgres.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 71571b1..dbaddb1 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -37,7 +37,10 @@ export class Postgres implements IDatabase { constructor(private config: DatabaseConfig) {} async init(): Promise { - this.pool = new Pool(this.config.postgres); + this.pool = new Pool({ + ...this.config.postgres, + statement_timeout: 25000 + }); this.pool.on("error", (err, client) => { Logger.error(err.stack); this.lastPoolFail = Date.now(); From ad7080d80185a61160a438a84d487d6e7760886a Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 22 Jul 2022 19:03:46 -0400 Subject: [PATCH 085/168] Force replica if failed using normal alternates now --- src/databases/IDatabase.ts | 1 + src/databases/Postgres.ts | 13 ++++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/databases/IDatabase.ts b/src/databases/IDatabase.ts index 0211203..9cc82d4 100644 --- a/src/databases/IDatabase.ts +++ b/src/databases/IDatabase.ts @@ -1,5 +1,6 @@ export interface QueryOption { useReplica?: boolean; + forceReplica?: boolean; } export interface IDatabase { diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index dbaddb1..681a193 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -103,11 +103,13 @@ export class Postgres implements IDatabase { Logger.debug(`prepare (postgres): type: ${type}, query: ${query}, params: ${params}`); let tries = 0; + let lastPool: Pool = null; do { tries++; try { - const queryResult = await this.getPool(type, options).query({ text: query, values: params }); + lastPool = this.getPool(type, options); + const queryResult = await lastPool.query({ text: query, values: params }); switch (type) { case "get": { @@ -125,7 +127,12 @@ export class Postgres implements IDatabase { } } } catch (err) { - options.useReplica = false; + if (lastPool === this.pool) { + // Only applies if it is get or all request + options.forceReplica = true; + } else if (lastPool === this.poolRead) { + options.useReplica = false; + } Logger.error(`prepare (postgres) try ${tries}: ${err}`); } @@ -136,7 +143,7 @@ export class Postgres implements IDatabase { const readAvailable = this.poolRead && options.useReplica && (type === "get" || type === "all"); const ignroreReadDueToFailure = this.lastPoolReadFail > Date.now() - 1000 * 30; const readDueToFailure = this.lastPoolFail > Date.now() - 1000 * 30; - if (readAvailable && !ignroreReadDueToFailure && (readDueToFailure || + if (readAvailable && !ignroreReadDueToFailure && (options.forceReplica || readDueToFailure || Math.random() > 1 / (this.config.postgresReadOnly.weight + 1))) { return this.poolRead; } else { From 9764c01428dc5a812af488b1f5b3a1ced1614f05 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 22 Jul 2022 20:00:38 -0400 Subject: [PATCH 086/168] Add timeout for replica requests on non replica too --- src/databases/Postgres.ts | 12 +++++++++--- src/utils/promiseTimeout.ts | 11 +++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 src/utils/promiseTimeout.ts diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 681a193..cdcc3a1 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -4,6 +4,7 @@ import { Client, Pool, types } from "pg"; import fs from "fs"; import { CustomPostgresConfig, CustomPostgresReadOnlyConfig } from "../types/config.model"; +import { promiseTimeout } from "../utils/promiseTimeout"; // return numeric (pg_type oid=1700) as float types.setTypeParser(1700, function(val) { @@ -31,6 +32,7 @@ export class Postgres implements IDatabase { private poolRead: Pool; private lastPoolReadFail = 0; + private readTimeout = 400; private maxTries = 3; @@ -109,7 +111,7 @@ export class Postgres implements IDatabase { try { lastPool = this.getPool(type, options); - const queryResult = await lastPool.query({ text: query, values: params }); + const queryResult = await promiseTimeout(lastPool.query({ text: query, values: params }), options.useReplica ? this.readTimeout : null); switch (type) { case "get": { @@ -136,11 +138,11 @@ export class Postgres implements IDatabase { Logger.error(`prepare (postgres) try ${tries}: ${err}`); } - } while ((type === "get" || type === "all") && tries < this.maxTries); + } while (this.isReadQuery(type) && tries < this.maxTries); } private getPool(type: string, options: QueryOption): Pool { - const readAvailable = this.poolRead && options.useReplica && (type === "get" || type === "all"); + const readAvailable = this.poolRead && options.useReplica && this.isReadQuery(type); const ignroreReadDueToFailure = this.lastPoolReadFail > Date.now() - 1000 * 30; const readDueToFailure = this.lastPoolFail > Date.now() - 1000 * 30; if (readAvailable && !ignroreReadDueToFailure && (options.forceReplica || readDueToFailure || @@ -151,6 +153,10 @@ export class Postgres implements IDatabase { } } + private isReadQuery(type: string): boolean { + return type === "get" || type === "all"; + } + private async createDB() { const client = new Client({ ...this.config.postgres, diff --git a/src/utils/promiseTimeout.ts b/src/utils/promiseTimeout.ts new file mode 100644 index 0000000..2053367 --- /dev/null +++ b/src/utils/promiseTimeout.ts @@ -0,0 +1,11 @@ +export function promiseTimeout(promise: Promise, timeout: number): Promise { + return new Promise((resolve, reject) => { + if (timeout) { + setTimeout(() => { + reject(new Error("Promise timed out")); + }, timeout); + } + + promise.then(resolve, reject); + }); +} \ No newline at end of file From f93c197d057fa06cc149b8f9c9aa1f2f2c2dc508 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 22 Jul 2022 20:56:29 -0400 Subject: [PATCH 087/168] Add timeout to shadowhiddenn ip --- src/routes/getSkipSegments.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 4742ec0..34d410c 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -11,6 +11,7 @@ import { Logger } from "../utils/logger"; import { QueryCacher } from "../utils/queryCacher"; import { getReputation } from "../utils/reputation"; import { getService } from "../utils/getService"; +import { promiseTimeout } from "../utils/promiseTimeout"; async function prepareCategorySegments(req: Request, videoID: VideoID, service: Service, segments: DBSegment[], cache: SegmentCache = { shadowHiddenSegmentIPs: {} }, useCache: boolean): Promise { @@ -38,7 +39,12 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, service: const service = getService(req?.query?.service as string); const fetchData = () => privateDB.prepare("all", 'SELECT "hashedIP" FROM "sponsorTimes" WHERE "videoID" = ? AND "timeSubmitted" = ? AND "service" = ?', [videoID, segment.timeSubmitted, service], { useReplica: true }) as Promise<{ hashedIP: HashedIP }[]>; - cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted] = await QueryCacher.get(fetchData, shadowHiddenIPKey(videoID, segment.timeSubmitted, service)); + try { + cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted] = await promiseTimeout(QueryCacher.get(fetchData, shadowHiddenIPKey(videoID, segment.timeSubmitted, service)), 150); + } catch (e) { + // give up on shadowhide for now + cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted] = null; + } } const ipList = cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted]; From 597b26ba7ca37a27006c253d7de331e7d05a63d1 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 22 Jul 2022 21:05:38 -0400 Subject: [PATCH 088/168] Clear cache when submission rejected --- src/routes/postSkipSegments.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index cdded50..4ef2748 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -250,7 +250,7 @@ async function checkInvalidFields(videoID: VideoID, userID: UserID, hashedUserID } async function checkEachSegmentValid(rawIP: IPAddress, paramUserID: UserID, userID: HashedUserID, videoID: VideoID, - segments: IncomingSegment[], service: string, isVIP: boolean, lockedCategoryList: Array): Promise { + segments: IncomingSegment[], service: Service, isVIP: boolean, lockedCategoryList: Array): Promise { for (let i = 0; i < segments.length; i++) { if (segments[i] === undefined || segments[i].segment === undefined || segments[i].category === undefined) { @@ -265,7 +265,13 @@ async function checkEachSegmentValid(rawIP: IPAddress, paramUserID: UserID, user // Reject segment if it's in the locked categories list const lockIndex = lockedCategoryList.findIndex(c => segments[i].category === c.category && segments[i].actionType === c.actionType); if (!isVIP && lockIndex !== -1) { - // TODO: Do something about the fradulent submission + QueryCacher.clearSegmentCache({ + videoID, + hashedVideoID: await getHashCache(videoID, 1), + service, + userID + }); + Logger.warn(`Caught a submission for a locked category. userID: '${userID}', videoID: '${videoID}', category: '${segments[i].category}', times: ${segments[i].segment}`); return { pass: false, From 2983bdb6163b748d4801d8b7f031e62f54d73acc Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 22 Jul 2022 21:06:21 -0400 Subject: [PATCH 089/168] Add warning about segments not appearing --- src/routes/postSkipSegments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 4ef2748..29c4fd3 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -277,7 +277,7 @@ async function checkEachSegmentValid(rawIP: IPAddress, paramUserID: UserID, user pass: false, errorCode: 403, errorMessage: - `Users have voted that new segments aren't needed for the following category: ` + + `NOTE: THERE IS A BUG RIGHT NOW CAUSING SEGMENTS NOT TO APPEAR, try refreshing. Users have voted that new segments aren't needed for the following category: ` + `'${segments[i].category}'\n` + `${lockedCategoryList[lockIndex].reason?.length !== 0 ? `\nReason: '${lockedCategoryList[lockIndex].reason}'` : ""}\n` + `${(segments[i].category === "sponsor" ? "\nMaybe the segment you are submitting is a different category that you have not enabled and is not a sponsor. " + From db2f9e11f7a16ff2cae9a15722c5e6c17f36bc1e Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 23 Jul 2022 14:12:06 -0400 Subject: [PATCH 090/168] Remove warning about disapearing degments bug --- src/routes/postSkipSegments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 29c4fd3..4ef2748 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -277,7 +277,7 @@ async function checkEachSegmentValid(rawIP: IPAddress, paramUserID: UserID, user pass: false, errorCode: 403, errorMessage: - `NOTE: THERE IS A BUG RIGHT NOW CAUSING SEGMENTS NOT TO APPEAR, try refreshing. Users have voted that new segments aren't needed for the following category: ` + + `Users have voted that new segments aren't needed for the following category: ` + `'${segments[i].category}'\n` + `${lockedCategoryList[lockIndex].reason?.length !== 0 ? `\nReason: '${lockedCategoryList[lockIndex].reason}'` : ""}\n` + `${(segments[i].category === "sponsor" ? "\nMaybe the segment you are submitting is a different category that you have not enabled and is not a sponsor. " + From 23add072f3880ec07f9f75deae31eadc9c5ef063 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 24 Jul 2022 15:40:40 -0400 Subject: [PATCH 091/168] Wait for any successful query instead of just most recent --- src/databases/Postgres.ts | 12 ++++++--- src/routes/getSkipSegments.ts | 4 +-- src/utils/promiseTimeout.ts | 47 ++++++++++++++++++++++++++++++++--- 3 files changed, 54 insertions(+), 9 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index cdcc3a1..5b384e8 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -1,10 +1,10 @@ import { Logger } from "../utils/logger"; import { IDatabase, QueryOption, QueryType } from "./IDatabase"; -import { Client, Pool, types } from "pg"; +import { Client, Pool, QueryResult, types } from "pg"; import fs from "fs"; import { CustomPostgresConfig, CustomPostgresReadOnlyConfig } from "../types/config.model"; -import { promiseTimeout } from "../utils/promiseTimeout"; +import { timeoutPomise, PromiseWithState, savePromiseState, nextFulfilment } from "../utils/promiseTimeout"; // return numeric (pg_type oid=1700) as float types.setTypeParser(1700, function(val) { @@ -104,6 +104,8 @@ export class Postgres implements IDatabase { Logger.debug(`prepare (postgres): type: ${type}, query: ${query}, params: ${params}`); + const pendingQueries: PromiseWithState>[] = []; + let tries = 0; let lastPool: Pool = null; do { @@ -111,7 +113,11 @@ export class Postgres implements IDatabase { try { lastPool = this.getPool(type, options); - const queryResult = await promiseTimeout(lastPool.query({ text: query, values: params }), options.useReplica ? this.readTimeout : null); + + pendingQueries.push(savePromiseState(lastPool.query({ text: query, values: params }))); + const currentPromises = [...pendingQueries]; + if (options.useReplica) currentPromises.push(savePromiseState(timeoutPomise(this.readTimeout))); + const queryResult = await nextFulfilment(currentPromises); switch (type) { case "get": { diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 34d410c..5c2dd6a 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -11,7 +11,7 @@ import { Logger } from "../utils/logger"; import { QueryCacher } from "../utils/queryCacher"; import { getReputation } from "../utils/reputation"; import { getService } from "../utils/getService"; -import { promiseTimeout } from "../utils/promiseTimeout"; +import { promiseOrTimeout } from "../utils/promiseTimeout"; async function prepareCategorySegments(req: Request, videoID: VideoID, service: Service, segments: DBSegment[], cache: SegmentCache = { shadowHiddenSegmentIPs: {} }, useCache: boolean): Promise { @@ -40,7 +40,7 @@ async function prepareCategorySegments(req: Request, videoID: VideoID, service: const fetchData = () => privateDB.prepare("all", 'SELECT "hashedIP" FROM "sponsorTimes" WHERE "videoID" = ? AND "timeSubmitted" = ? AND "service" = ?', [videoID, segment.timeSubmitted, service], { useReplica: true }) as Promise<{ hashedIP: HashedIP }[]>; try { - cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted] = await promiseTimeout(QueryCacher.get(fetchData, shadowHiddenIPKey(videoID, segment.timeSubmitted, service)), 150); + cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted] = await promiseOrTimeout(QueryCacher.get(fetchData, shadowHiddenIPKey(videoID, segment.timeSubmitted, service)), 150); } catch (e) { // give up on shadowhide for now cache.shadowHiddenSegmentIPs[videoID][segment.timeSubmitted] = null; diff --git a/src/utils/promiseTimeout.ts b/src/utils/promiseTimeout.ts index 2053367..8a6ecf8 100644 --- a/src/utils/promiseTimeout.ts +++ b/src/utils/promiseTimeout.ts @@ -1,11 +1,50 @@ -export function promiseTimeout(promise: Promise, timeout: number): Promise { +export class PromiseTimeoutError extends Error { + promise?: Promise; + + constructor(promise?: Promise) { + super("Promise timed out"); + + this.promise = promise; + } +} + +export interface PromiseWithState extends Promise { + isResolved: boolean; + isRejected: boolean; +} + +export function promiseOrTimeout(promise: Promise, timeout?: number): Promise { + return Promise.race([timeoutPomise(timeout), promise]); +} + +export function timeoutPomise(timeout?: number): Promise { return new Promise((resolve, reject) => { if (timeout) { setTimeout(() => { - reject(new Error("Promise timed out")); + reject(new PromiseTimeoutError()); }, timeout); } - - promise.then(resolve, reject); }); +} + +export function savePromiseState(promise: Promise): PromiseWithState { + const p = promise as PromiseWithState; + p.isResolved = false; + p.isRejected = false; + + p.then(() => { + p.isResolved = true; + }).catch(() => { + p.isRejected = true; + }); + + return p; +} + +/** + * Allows rejection or resolve + * Allows past resolves too, but not past rejections + */ +export function nextFulfilment(promises: PromiseWithState[]): Promise { + return Promise.race(promises.filter((p) => !p.isRejected)); } \ No newline at end of file From 954c3db6495085dbcf1eade54f7bc5f40587abe3 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 24 Jul 2022 15:41:09 -0400 Subject: [PATCH 092/168] Change postgres timeout values --- src/databases/Postgres.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 5b384e8..59c0acb 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -32,7 +32,7 @@ export class Postgres implements IDatabase { private poolRead: Pool; private lastPoolReadFail = 0; - private readTimeout = 400; + private readTimeout = 250; private maxTries = 3; @@ -57,7 +57,7 @@ export class Postgres implements IDatabase { if (this.config.postgresReadOnly && this.config.postgresReadOnly.enabled) { this.poolRead = new Pool({ ...this.config.postgresReadOnly, - statement_timeout: 300 + statement_timeout: 1000 }); this.poolRead.on("error", (err, client) => { Logger.error(err.stack); From 46805f48300b700b8cef23edfba0f791ed8aaeea Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 28 Jul 2022 12:22:49 -0400 Subject: [PATCH 093/168] Require permission for filler submissions --- src/config.ts | 1 + src/routes/addFeature.ts | 6 ++++-- src/routes/getUserInfo.ts | 17 +++++++++++++---- src/routes/postSkipSegments.ts | 10 ++++++---- src/types/config.model.ts | 1 + src/types/segments.model.ts | 2 +- src/types/user.model.ts | 3 ++- src/utils/permissions.ts | 30 ++++++++++++++++++++++++++---- 8 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/config.ts b/src/config.ts index ee9e26f..c680c23 100644 --- a/src/config.ts +++ b/src/config.ts @@ -44,6 +44,7 @@ addDefaults(config, { discordReportChannelWebhookURL: null, discordMaliciousReportWebhookURL: null, minReputationToSubmitChapter: 0, + minReputationToSubmitFiller: 0, getTopUsersCacheTimeMinutes: 240, globalSalt: null, mode: "", diff --git a/src/routes/addFeature.ts b/src/routes/addFeature.ts index 8319015..d7aeaff 100644 --- a/src/routes/addFeature.ts +++ b/src/routes/addFeature.ts @@ -18,10 +18,12 @@ interface AddFeatureRequest extends Request { const allowedFeatures = { vip: [ - Feature.ChapterSubmitter + Feature.ChapterSubmitter, + Feature.FillerSubmitter ], admin: [ - Feature.ChapterSubmitter + Feature.ChapterSubmitter, + Feature.FillerSubmitter ] } diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index 8f31a01..f9bf492 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -5,9 +5,9 @@ import { Request, Response } from "express"; import { Logger } from "../utils/logger"; import { HashedUserID, UserID } from "../types/user.model"; import { getReputation } from "../utils/reputation"; -import { SegmentUUID } from "../types/segments.model"; +import { Category, SegmentUUID } from "../types/segments.model"; import { config } from "../config"; -import { canSubmitChapter } from "../utils/permissions"; +import { canSubmit } from "../utils/permissions"; const maxRewardTime = config.maxRewardTimePerSegmentInSeconds; async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ minutesSaved: number, segmentCount: number }> { @@ -106,6 +106,15 @@ async function dbGetBanned(userID: HashedUserID): Promise { } } +async function getPermissions(userID: HashedUserID): Promise> { + const result: Record = {}; + for (const category of config.categoryList) { + result[category] = (await canSubmit(userID, category as Category)).canSubmit; + } + + return result; +} + type cases = Record const executeIfFunction = (f: any) => @@ -130,7 +139,7 @@ const dbGetValue = (userID: HashedUserID, property: string): Promise getReputation(userID), vip: () => isUserVIP(userID), lastSegmentID: () => dbGetLastSegmentForUser(userID), - canSubmitChapter: () => canSubmitChapter(userID) + permissions: () => getPermissions(userID) })("")(property); }; @@ -140,7 +149,7 @@ async function getUserInfo(req: Request, res: Response): Promise { const defaultProperties: string[] = ["userID", "userName", "minutesSaved", "segmentCount", "ignoredSegmentCount", "viewCount", "ignoredViewCount", "warnings", "warningReason", "reputation", "vip", "lastSegmentID"]; - const allProperties: string[] = [...defaultProperties, "banned", "canSubmitChapter"]; + const allProperties: string[] = [...defaultProperties, "banned", "permissions"]; let paramValues: string[] = req.query.values ? JSON.parse(req.query.values as string) : req.query.value diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 4ef2748..05edcd9 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -21,7 +21,7 @@ import { parseUserAgent } from "../utils/userAgent"; import { getService } from "../utils/getService"; import axios from "axios"; import { vote } from "./voteOnSponsorTime"; -import { canSubmitChapter } from "../utils/permissions"; +import { canSubmit } from "../utils/permissions"; type CheckResult = { pass: boolean, @@ -230,8 +230,10 @@ async function checkInvalidFields(videoID: VideoID, userID: UserID, hashedUserID invalidFields.push("segment description"); } - if (segmentPair.actionType === ActionType.Chapter && !(await canSubmitChapter(hashedUserID))) { - invalidFields.push("permission to submit chapters"); + const permission = await canSubmit(hashedUserID, segmentPair.category); + if (!permission.canSubmit) { + invalidFields.push(`permission to submit ${segmentPair.category}`); + errors.push(permission.reason); } } @@ -241,7 +243,7 @@ async function checkInvalidFields(videoID: VideoID, userID: UserID, hashedUserID const formattedErrors = errors.reduce((p, c, i) => p + (i !== 0 ? ". " : " ") + c, ""); return { pass: false, - errorMessage: `No valid ${formattedFields} field(s) provided.${formattedErrors}`, + errorMessage: `No valid ${formattedFields}.${formattedErrors}`, errorCode: 400 }; } diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 9030e69..47b3108 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -28,6 +28,7 @@ export interface SBSConfig { neuralBlockURL?: string; discordNeuralBlockRejectWebhookURL?: string; minReputationToSubmitChapter: number; + minReputationToSubmitFiller: number; userCounterURL?: string; proxySubmission?: string; behindProxy: string | boolean; diff --git a/src/types/segments.model.ts b/src/types/segments.model.ts index 324d196..841bb99 100644 --- a/src/types/segments.model.ts +++ b/src/types/segments.model.ts @@ -5,7 +5,7 @@ import { HashedUserID, UserID } from "./user.model"; export type SegmentUUID = string & { __segmentUUIDBrand: unknown }; export type VideoID = string & { __videoIDBrand: unknown }; export type VideoDuration = number & { __videoDurationBrand: unknown }; -export type Category = ("sponsor" | "selfpromo" | "interaction" | "intro" | "outro" | "preview" | "music_offtopic" | "poi_highlight" | "chapter") & { __categoryBrand: unknown }; +export type Category = ("sponsor" | "selfpromo" | "interaction" | "intro" | "outro" | "preview" | "music_offtopic" | "filler" | "poi_highlight" | "chapter") & { __categoryBrand: unknown }; export type VideoIDHash = VideoID & HashedValue; export type IPAddress = string & { __ipAddressBrand: unknown }; export type HashedIP = IPAddress & HashedValue; diff --git a/src/types/user.model.ts b/src/types/user.model.ts index 355e8bb..c3009df 100644 --- a/src/types/user.model.ts +++ b/src/types/user.model.ts @@ -4,5 +4,6 @@ export type UserID = string & { __userIDBrand: unknown }; export type HashedUserID = UserID & HashedValue; export enum Feature { - ChapterSubmitter = 0 + ChapterSubmitter = 0, + FillerSubmitter = 1 } \ No newline at end of file diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index 4269ed0..2227cda 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -1,11 +1,33 @@ import { config } from "../config"; +import { Category } from "../types/segments.model"; import { Feature, HashedUserID } from "../types/user.model"; import { hasFeature } from "./features"; import { isUserVIP } from "./isUserVIP"; import { getReputation } from "./reputation"; -export async function canSubmitChapter(userID: HashedUserID): Promise { - return (await isUserVIP(userID)) - || (await getReputation(userID)) > config.minReputationToSubmitChapter - || (await hasFeature(userID, Feature.ChapterSubmitter)); +interface CanSubmitResult { + canSubmit: boolean; + reason?: string; +} + +export async function canSubmit(userID: HashedUserID, category: Category): Promise { + switch (category) { + case "chapter": + return { + canSubmit: (await isUserVIP(userID)) + || (await getReputation(userID)) > config.minReputationToSubmitChapter + || (await hasFeature(userID, Feature.ChapterSubmitter)) + }; + case "filler": + return { + canSubmit: (await isUserVIP(userID)) + || (await getReputation(userID)) > config.minReputationToSubmitFiller + || (await hasFeature(userID, Feature.FillerSubmitter)), + reason: "Someone is submitting over 180,000 spam filler submissions and refuses to stop even after talking with them, so we have to restrict it for now. You can request submission access on chat.sponsor.ajay.app, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" + }; + } + + return { + canSubmit: true + }; } \ No newline at end of file From 17a790b6d98b550dd2433de20d482ef5ea59006a Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 28 Jul 2022 13:00:58 -0400 Subject: [PATCH 094/168] More accurate spam number --- src/utils/permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index 2227cda..11622ba 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -23,7 +23,7 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi canSubmit: (await isUserVIP(userID)) || (await getReputation(userID)) > config.minReputationToSubmitFiller || (await hasFeature(userID, Feature.FillerSubmitter)), - reason: "Someone is submitting over 180,000 spam filler submissions and refuses to stop even after talking with them, so we have to restrict it for now. You can request submission access on chat.sponsor.ajay.app, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" + reason: "Someone has submitted over 1.9 million spam filler submissions and refuses to stop even after talking with them, so we have to restrict it for now. You can request submission access on chat.sponsor.ajay.app, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" }; } From b9354e44ae1a3bf18f2c740025c3cf221d0cd854 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 28 Jul 2022 13:10:21 -0400 Subject: [PATCH 095/168] Fix missing semicolon --- src/app.ts | 2 +- src/routes/addFeature.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app.ts b/src/app.ts index 1aa7739..05c0e1f 100644 --- a/src/app.ts +++ b/src/app.ts @@ -197,7 +197,7 @@ function setupRoutes(router: Router) { router.get("/api/lockReason", getLockReason); - router.post("/api/feature", addFeature) + router.post("/api/feature", addFeature); // ratings router.get("/api/ratings/rate/:prefix", getRating); diff --git a/src/routes/addFeature.ts b/src/routes/addFeature.ts index d7aeaff..9994ff3 100644 --- a/src/routes/addFeature.ts +++ b/src/routes/addFeature.ts @@ -25,7 +25,7 @@ const allowedFeatures = { Feature.ChapterSubmitter, Feature.FillerSubmitter ] -} +}; export async function addFeature(req: AddFeatureRequest, res: Response): Promise { const { body: { userID, adminUserID } } = req; From af7634b498ee63670b9ffd229b75849e3e913cdd Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 28 Jul 2022 13:55:43 -0400 Subject: [PATCH 096/168] Fix ad feature auth logic --- src/routes/addFeature.ts | 10 +++++----- src/utils/isUserVIP.ts | 2 +- test/cases/addFeatures.ts | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/routes/addFeature.ts b/src/routes/addFeature.ts index 9994ff3..e5e5519 100644 --- a/src/routes/addFeature.ts +++ b/src/routes/addFeature.ts @@ -3,7 +3,7 @@ import { db } from "../databases/databases"; import { config } from "../config"; import { Request, Response } from "express"; import { isUserVIP } from "../utils/isUserVIP"; -import { Feature, HashedUserID } from "../types/user.model"; +import { Feature, HashedUserID, UserID } from "../types/user.model"; import { Logger } from "../utils/logger"; import { QueryCacher } from "../utils/queryCacher"; @@ -38,11 +38,11 @@ export async function addFeature(req: AddFeatureRequest, res: Response): Promise } // hash the userID - const adminUserIDInput = await getHashCache(adminUserID); - const isAdmin = adminUserIDInput !== config.adminUserID; - const isVIP = (await isUserVIP(userID)) || isAdmin; + const adminUserIDInput = await getHashCache(adminUserID as UserID); + const isAdmin = adminUserIDInput === config.adminUserID; + const isVIP = (await isUserVIP(adminUserIDInput)) || isAdmin; - if (!isAdmin && !isVIP) { + if (!isVIP) { // not authorized return res.sendStatus(403); } diff --git a/src/utils/isUserVIP.ts b/src/utils/isUserVIP.ts index ed1ba09..50cc462 100644 --- a/src/utils/isUserVIP.ts +++ b/src/utils/isUserVIP.ts @@ -2,6 +2,6 @@ import { db } from "../databases/databases"; import { HashedUserID } from "../types/user.model"; export async function isUserVIP(userID: HashedUserID): Promise { - return (await db.prepare("get", `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ? LIMIT 1`, + return (await db.prepare("get", `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ? LIMIT 1`, [userID], { useReplica: true }))?.userCount > 0; } diff --git a/test/cases/addFeatures.ts b/test/cases/addFeatures.ts index 9b2c574..657555e 100644 --- a/test/cases/addFeatures.ts +++ b/test/cases/addFeatures.ts @@ -41,7 +41,7 @@ describe("addFeatures", () => { it("can add features", async () => { for (const feature of validFeatures) { - const result = await postAddFeatures(hashedUserID1, vipUserID, feature, "true"); + const result = await postAddFeatures(hashedUserID1, privateVipUserID, feature, "true"); assert.strictEqual(result.status, 200); assert.strictEqual(await hasFeature(hashedUserID1, feature), true); @@ -51,7 +51,7 @@ describe("addFeatures", () => { it("can remove features", async () => { const feature = Feature.ChapterSubmitter; - const result = await postAddFeatures(hashedUserID2, vipUserID, feature, "false"); + const result = await postAddFeatures(hashedUserID2, privateVipUserID, feature, "false"); assert.strictEqual(result.status, 200); assert.strictEqual(await hasFeature(hashedUserID2, feature), false); @@ -60,7 +60,7 @@ describe("addFeatures", () => { it("can update features", async () => { const feature = Feature.ChapterSubmitter; - const result = await postAddFeatures(hashedUserID3, vipUserID, feature, "true"); + const result = await postAddFeatures(hashedUserID3, privateVipUserID, feature, "true"); assert.strictEqual(result.status, 200); assert.strictEqual(await hasFeature(hashedUserID3, feature), true); From 5e49ce22b25e27a02e230fe0f6ec22b5cfe8146b Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 28 Jul 2022 16:59:23 -0400 Subject: [PATCH 097/168] new chat link --- src/utils/permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index 11622ba..f9dd5a3 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -23,7 +23,7 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi canSubmit: (await isUserVIP(userID)) || (await getReputation(userID)) > config.minReputationToSubmitFiller || (await hasFeature(userID, Feature.FillerSubmitter)), - reason: "Someone has submitted over 1.9 million spam filler submissions and refuses to stop even after talking with them, so we have to restrict it for now. You can request submission access on chat.sponsor.ajay.app, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" + reason: "Someone has submitted over 1.9 million spam filler submissions and refuses to stop even after talking with them, so we have to restrict it for now. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" }; } From 186f07b20cbb27906f9cc299f83a5e894067eacc Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 29 Jul 2022 11:29:39 -0400 Subject: [PATCH 098/168] change filler rules --- src/utils/permissions.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index f9dd5a3..41a30fe 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -1,4 +1,5 @@ import { config } from "../config"; +import { db } from "../databases/databases"; import { Category } from "../types/segments.model"; import { Feature, HashedUserID } from "../types/user.model"; import { hasFeature } from "./features"; @@ -22,7 +23,8 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi return { canSubmit: (await isUserVIP(userID)) || (await getReputation(userID)) > config.minReputationToSubmitFiller - || (await hasFeature(userID, Feature.FillerSubmitter)), + || (await hasFeature(userID, Feature.FillerSubmitter)) + || (await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND category != 'filler' AND "timeSubmitted" < 1654010691000 LIMIT 4`, [userID], { useReplica: true }))?.submissionCount > 3, reason: "Someone has submitted over 1.9 million spam filler submissions and refuses to stop even after talking with them, so we have to restrict it for now. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" }; } From da1ed70c666f9521cad2d24e22765841201f909f Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 29 Jul 2022 11:47:28 -0400 Subject: [PATCH 099/168] adjust permissions and use parrallel processing --- src/databases/Postgres.ts | 2 +- src/routes/getSkipSegments.ts | 2 +- src/utils/permissions.ts | 17 ++++++++------ src/utils/{promiseTimeout.ts => promise.ts} | 25 +++++++++++++++++++++ 4 files changed, 37 insertions(+), 9 deletions(-) rename src/utils/{promiseTimeout.ts => promise.ts} (63%) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 59c0acb..f145aad 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -4,7 +4,7 @@ import { Client, Pool, QueryResult, types } from "pg"; import fs from "fs"; import { CustomPostgresConfig, CustomPostgresReadOnlyConfig } from "../types/config.model"; -import { timeoutPomise, PromiseWithState, savePromiseState, nextFulfilment } from "../utils/promiseTimeout"; +import { timeoutPomise, PromiseWithState, savePromiseState, nextFulfilment } from "../utils/promise"; // return numeric (pg_type oid=1700) as float types.setTypeParser(1700, function(val) { diff --git a/src/routes/getSkipSegments.ts b/src/routes/getSkipSegments.ts index 5c2dd6a..7da485e 100644 --- a/src/routes/getSkipSegments.ts +++ b/src/routes/getSkipSegments.ts @@ -11,7 +11,7 @@ import { Logger } from "../utils/logger"; import { QueryCacher } from "../utils/queryCacher"; import { getReputation } from "../utils/reputation"; import { getService } from "../utils/getService"; -import { promiseOrTimeout } from "../utils/promiseTimeout"; +import { promiseOrTimeout } from "../utils/promise"; async function prepareCategorySegments(req: Request, videoID: VideoID, service: Service, segments: DBSegment[], cache: SegmentCache = { shadowHiddenSegmentIPs: {} }, useCache: boolean): Promise { diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index 41a30fe..15f8374 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -4,6 +4,7 @@ import { Category } from "../types/segments.model"; import { Feature, HashedUserID } from "../types/user.model"; import { hasFeature } from "./features"; import { isUserVIP } from "./isUserVIP"; +import { oneOf } from "./promise"; import { getReputation } from "./reputation"; interface CanSubmitResult { @@ -15,16 +16,18 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi switch (category) { case "chapter": return { - canSubmit: (await isUserVIP(userID)) - || (await getReputation(userID)) > config.minReputationToSubmitChapter - || (await hasFeature(userID, Feature.ChapterSubmitter)) + canSubmit: await oneOf([isUserVIP(userID), + (async () => (await getReputation(userID)) > config.minReputationToSubmitChapter)(), + hasFeature(userID, Feature.ChapterSubmitter) + ]) }; case "filler": return { - canSubmit: (await isUserVIP(userID)) - || (await getReputation(userID)) > config.minReputationToSubmitFiller - || (await hasFeature(userID, Feature.FillerSubmitter)) - || (await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND category != 'filler' AND "timeSubmitted" < 1654010691000 LIMIT 4`, [userID], { useReplica: true }))?.submissionCount > 3, + canSubmit: await oneOf([isUserVIP(userID), + (async () => (await getReputation(userID)) > config.minReputationToSubmitFiller)(), + hasFeature(userID, Feature.FillerSubmitter), + (async () => (await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND category != 'filler' AND "timeSubmitted" < 1654010691000 LIMIT 4`, [userID], { useReplica: true }))?.submissionCount > 3)() + ]), reason: "Someone has submitted over 1.9 million spam filler submissions and refuses to stop even after talking with them, so we have to restrict it for now. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" }; } diff --git a/src/utils/promiseTimeout.ts b/src/utils/promise.ts similarity index 63% rename from src/utils/promiseTimeout.ts rename to src/utils/promise.ts index 8a6ecf8..e84e2ff 100644 --- a/src/utils/promiseTimeout.ts +++ b/src/utils/promise.ts @@ -1,3 +1,5 @@ +import { Logger } from "./logger"; + export class PromiseTimeoutError extends Error { promise?: Promise; @@ -47,4 +49,27 @@ export function savePromiseState(promise: Promise): PromiseWithState { */ export function nextFulfilment(promises: PromiseWithState[]): Promise { return Promise.race(promises.filter((p) => !p.isRejected)); +} + +export function oneOf(promises: Promise[]): Promise { + return new Promise((resolve, reject) => { + let fulfilments = 0; + for (const promise of promises) { + promise.then((result) => { + fulfilments++; + + if (result || fulfilments === promises.length) { + resolve(result); + } + }).catch((err) => { + fulfilments++; + + if (fulfilments === promises.length) { + reject(err); + } else { + Logger.error(`oneOf ignore error (promise): ${err}`); + } + }); + } + }); } \ No newline at end of file From 04eabd5141b053a6ecea19c4f397973e8c83748f Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 29 Jul 2022 11:49:08 -0400 Subject: [PATCH 100/168] Fix error --- test/cases/postSkipSegments.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cases/postSkipSegments.ts b/test/cases/postSkipSegments.ts index 47ccff2..422ea88 100644 --- a/test/cases/postSkipSegments.ts +++ b/test/cases/postSkipSegments.ts @@ -1280,7 +1280,7 @@ describe("postSkipSegments", () => { }); it("Should return 400 if videoID is empty", (done) => { - const videoID = null as string; + const videoID = null as unknown as string; postSkipSegmentParam({ videoID, startTime: 1, From ee496891104f6565565ede067df550257ec6635a Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 29 Jul 2022 11:55:48 -0400 Subject: [PATCH 101/168] Ignore full in all reputation cases Closes #490 --- src/utils/reputation.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/reputation.ts b/src/utils/reputation.ts index 092f384..27eb720 100644 --- a/src/utils/reputation.ts +++ b/src/utils/reputation.ts @@ -28,9 +28,9 @@ export async function getReputation(userID: UserID): Promise { THEN 1 ELSE 0 END) AS "nonSelfDownvotedSubmissions", SUM(CASE WHEN "timeSubmitted" > 1596240000000 THEN "votes" ELSE 0 END) AS "votedSum", SUM(locked) AS "lockedSum", - SUM(CASE WHEN "timeSubmitted" < ? AND "timeSubmitted" > 1596240000000 AND "actionType" != 'full' AND "votes" > 0 THEN 1 ELSE 0 END) AS "semiOldUpvotedSubmissions", - SUM(CASE WHEN "timeSubmitted" < ? AND "timeSubmitted" > 1596240000000 AND "actionType" != 'full' AND "votes" > 0 THEN 1 ELSE 0 END) AS "oldUpvotedSubmissions", - SUM(CASE WHEN "votes" > 0 AND "actionType" != 'full' + SUM(CASE WHEN "timeSubmitted" < ? AND "timeSubmitted" > 1596240000000 AND "votes" > 0 THEN 1 ELSE 0 END) AS "semiOldUpvotedSubmissions", + SUM(CASE WHEN "timeSubmitted" < ? AND "timeSubmitted" > 1596240000000 AND "votes" > 0 THEN 1 ELSE 0 END) AS "oldUpvotedSubmissions", + SUM(CASE WHEN "votes" > 0 AND NOT EXISTS ( SELECT * FROM "sponsorTimes" as c WHERE (c."votes" > "a"."votes" OR c."locked" > "a"."locked") AND @@ -40,7 +40,7 @@ export async function getReputation(userID: UserID): Promise { SELECT * FROM "lockCategories" as l WHERE l."videoID" = "a"."videoID" AND l."service" = "a"."service" AND l."category" = "a"."category" LIMIT 1) THEN 1 ELSE 0 END) AS "mostUpvotedInLockedVideoSum" - FROM "sponsorTimes" as "a" WHERE "userID" = ?`, [userID, weekAgo, pastDate, userID], { useReplica: true }) as Promise; + FROM "sponsorTimes" as "a" WHERE "userID" = ? "actionType" != 'full'`, [userID, weekAgo, pastDate, userID], { useReplica: true }) as Promise; const result = await QueryCacher.get(fetchFromDB, reputationKey(userID)); From ef93c692e5af26b45196806d58cc51a36435d437 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 29 Jul 2022 11:57:55 -0400 Subject: [PATCH 102/168] fix reputation query typo --- src/utils/reputation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/reputation.ts b/src/utils/reputation.ts index 27eb720..a6421e6 100644 --- a/src/utils/reputation.ts +++ b/src/utils/reputation.ts @@ -40,7 +40,7 @@ export async function getReputation(userID: UserID): Promise { SELECT * FROM "lockCategories" as l WHERE l."videoID" = "a"."videoID" AND l."service" = "a"."service" AND l."category" = "a"."category" LIMIT 1) THEN 1 ELSE 0 END) AS "mostUpvotedInLockedVideoSum" - FROM "sponsorTimes" as "a" WHERE "userID" = ? "actionType" != 'full'`, [userID, weekAgo, pastDate, userID], { useReplica: true }) as Promise; + FROM "sponsorTimes" as "a" WHERE "userID" = ? AND "actionType" != 'full'`, [userID, weekAgo, pastDate, userID], { useReplica: true }) as Promise; const result = await QueryCacher.get(fetchFromDB, reputationKey(userID)); From 6239e53091d3e9f7473753e6765324db22341341 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 29 Jul 2022 12:06:38 -0400 Subject: [PATCH 103/168] Fix flaky test --- test/cases/voteOnSponsorTime.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index f915fc2..d189eff 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -685,14 +685,15 @@ describe("voteOnSponsorTime", () => { it("Should not be able to revive full video segment as non-vip", (done) => { const UUID = "full-video-uuid-1"; - postVote("VIPUser", UUID, 0); // downvote as VIP - postVote("randomID3", UUID, 1) - .then(async res => { - assert.strictEqual(res.status, 200); - const row = await getSegmentVotes(UUID); - assert.strictEqual(row.votes, -2); - done(); - }) - .catch(err => done(err)); + postVote("VIPUser", UUID, 0).then(() => { + postVote("randomID3", UUID, 1) + .then(async res => { + assert.strictEqual(res.status, 200); + const row = await getSegmentVotes(UUID); + assert.strictEqual(row.votes, -2); + done(); + }) + .catch(err => done(err)); + }); }); }); From cb1cc10278fc687d3019565c81014a6addb939f5 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 31 Jul 2022 21:05:39 -0400 Subject: [PATCH 104/168] Extend restriction to all categories --- src/utils/permissions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index 15f8374..d59c65c 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -21,14 +21,14 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi hasFeature(userID, Feature.ChapterSubmitter) ]) }; - case "filler": + default: return { canSubmit: await oneOf([isUserVIP(userID), (async () => (await getReputation(userID)) > config.minReputationToSubmitFiller)(), hasFeature(userID, Feature.FillerSubmitter), (async () => (await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND category != 'filler' AND "timeSubmitted" < 1654010691000 LIMIT 4`, [userID], { useReplica: true }))?.submissionCount > 3)() ]), - reason: "Someone has submitted over 1.9 million spam filler submissions and refuses to stop even after talking with them, so we have to restrict it for now. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" + reason: "Unfortunately, someone is doing a targetd attack and as a temporary emergency measure, segment submission for new users is disabled. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" }; } From cd35a58f83904bf5b3a786889fd0de00dc9af3cc Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 31 Jul 2022 21:08:23 -0400 Subject: [PATCH 105/168] Fix typo --- src/utils/permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index d59c65c..3eba2ea 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -28,7 +28,7 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi hasFeature(userID, Feature.FillerSubmitter), (async () => (await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND category != 'filler' AND "timeSubmitted" < 1654010691000 LIMIT 4`, [userID], { useReplica: true }))?.submissionCount > 3)() ]), - reason: "Unfortunately, someone is doing a targetd attack and as a temporary emergency measure, segment submission for new users is disabled. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" + reason: "Unfortunately, someone is doing a targeted attack and as a temporary emergency measure, segment submission for new users is disabled. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" }; } From 877c3b71074fc7b968be18909051cd284e5ab068 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 9 Aug 2022 22:01:09 -0400 Subject: [PATCH 106/168] Change submission requirements --- src/utils/permissions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index 3eba2ea..832451e 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -26,7 +26,7 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi canSubmit: await oneOf([isUserVIP(userID), (async () => (await getReputation(userID)) > config.minReputationToSubmitFiller)(), hasFeature(userID, Feature.FillerSubmitter), - (async () => (await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND category != 'filler' AND "timeSubmitted" < 1654010691000 LIMIT 4`, [userID], { useReplica: true }))?.submissionCount > 3)() + (async () => (await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND category != 'filler' AND "timeSubmitted" < 1660096797000 LIMIT 4`, [userID], { useReplica: true }))?.submissionCount > 3)() ]), reason: "Unfortunately, someone is doing a targeted attack and as a temporary emergency measure, segment submission for new users is disabled. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" }; From 479890fc0d94bda668010cc980e126bc38fe57fa Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 11 Aug 2022 21:37:13 -0400 Subject: [PATCH 107/168] Add default expiry to query cache Resolves #461 --- src/utils/queryCacher.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index d4f8647..c3548be 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -4,6 +4,8 @@ import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, ski import { Service, VideoID, VideoIDHash } from "../types/segments.model"; import { Feature, HashedUserID, UserID } from "../types/user.model"; +const expiryTime = 2 * 60 * 60; + async function get(fetchFromDB: () => Promise, key: string): Promise { try { const reply = await redis.get(key); @@ -16,7 +18,7 @@ async function get(fetchFromDB: () => Promise, key: string): Promise { const data = await fetchFromDB(); - redis.set(key, JSON.stringify(data)).catch((err) => Logger.error(err)); + redis.setEx(key, expiryTime, JSON.stringify(data)).catch((err) => Logger.error(err)); return data; } @@ -67,7 +69,7 @@ async function getAndSplit(fetchFromDB: (values: U[]) => Pr } for (const key in newResults) { - redis.set(key, JSON.stringify(newResults[key])).catch((err) => Logger.error(err)); + redis.setEx(key, expiryTime, JSON.stringify(newResults[key])).catch((err) => Logger.error(err)); } }); } From f4c104215bd589033e3a9e309279e29be9843c0a Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 12 Aug 2022 15:11:50 -0400 Subject: [PATCH 108/168] Lower requirements --- src/utils/permissions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index 832451e..ebd727a 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -26,9 +26,9 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi canSubmit: await oneOf([isUserVIP(userID), (async () => (await getReputation(userID)) > config.minReputationToSubmitFiller)(), hasFeature(userID, Feature.FillerSubmitter), - (async () => (await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND category != 'filler' AND "timeSubmitted" < 1660096797000 LIMIT 4`, [userID], { useReplica: true }))?.submissionCount > 3)() + (async () => (await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND category != 'filler' AND "timeSubmitted" < 1660096797000 LIMIT 1`, [userID], { useReplica: true }))?.submissionCount > 0)() ]), - reason: "Unfortunately, someone is doing a targeted attack and as a temporary emergency measure, segment submission for new users is disabled. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" + reason: "Yes, it has been a week, please be patient. Unfortunately, someone is doing a targeted attack and as a temporary emergency measure, segment submission for new users is disabled. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" }; } From 6804e7d7a8c5d60924fb7a8e055f372a335117a8 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 12 Aug 2022 15:26:20 -0400 Subject: [PATCH 109/168] Add logging for permission rejections --- src/routes/postSkipSegments.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 05edcd9..6d0b3a7 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -202,7 +202,7 @@ async function checkUserActiveWarning(userID: string): Promise { } async function checkInvalidFields(videoID: VideoID, userID: UserID, hashedUserID: HashedUserID - , segments: IncomingSegment[]): Promise { + , segments: IncomingSegment[], videoDurationParam: number, userAgent: string): Promise { const invalidFields = []; const errors = []; if (typeof videoID !== "string" || videoID?.length == 0) { @@ -232,6 +232,7 @@ async function checkInvalidFields(videoID: VideoID, userID: UserID, hashedUserID const permission = await canSubmit(hashedUserID, segmentPair.category); if (!permission.canSubmit) { + Logger.warn(`Rejecting submission due to lack of permissions for category ${segmentPair.category}: ${segmentPair.segment} ${hashedUserID} ${videoID} ${videoDurationParam} ${userAgent}`); invalidFields.push(`permission to submit ${segmentPair.category}`); errors.push(permission.reason); } @@ -495,7 +496,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise Date: Fri, 12 Aug 2022 15:31:19 -0400 Subject: [PATCH 110/168] Lower postgres read timeout, raise statment timeout --- src/databases/Postgres.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index f145aad..dca5a46 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -32,7 +32,7 @@ export class Postgres implements IDatabase { private poolRead: Pool; private lastPoolReadFail = 0; - private readTimeout = 250; + private readTimeout = 150; private maxTries = 3; @@ -57,7 +57,7 @@ export class Postgres implements IDatabase { if (this.config.postgresReadOnly && this.config.postgresReadOnly.enabled) { this.poolRead = new Pool({ ...this.config.postgresReadOnly, - statement_timeout: 1000 + statement_timeout: 1500 }); this.poolRead.on("error", (err, client) => { Logger.error(err.stack); From 1ae4c9a349c58d3e02cad175bcca58b5a1e7324e Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 12 Aug 2022 15:34:40 -0400 Subject: [PATCH 111/168] Add hostname to status --- src/routes/getStatus.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/getStatus.ts b/src/routes/getStatus.ts index 770ca25..7f764f5 100644 --- a/src/routes/getStatus.ts +++ b/src/routes/getStatus.ts @@ -23,7 +23,8 @@ export async function getStatus(req: Request, res: Response): Promise startTime, processTime: Date.now() - startTime, loadavg: os.loadavg().slice(1), // only return 5 & 15 minute load average - statusRequests + statusRequests, + hostname: os.hostname() }; return value ? res.send(JSON.stringify(statusValues[value])) : res.send(statusValues); } catch (err) { From 9306729fdc84da9b51ca6ca3e58f2d267fa22666 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 15 Aug 2022 11:43:51 -0400 Subject: [PATCH 112/168] Remove submission restrictions for tests --- test.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test.json b/test.json index 95d2d79..593977b 100644 --- a/test.json +++ b/test.json @@ -57,5 +57,6 @@ "max": 20, "statusCode": 200 } - } + }, + "minReputationToSubmitFiller": -1 } From 7cb58b946a63dd1d89c87dc84a24b1ee3fbbf703 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 15 Aug 2022 12:03:44 -0400 Subject: [PATCH 113/168] Remove submission restriction for ci.json --- ci.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ci.json b/ci.json index 18fb97c..7cdfe67 100644 --- a/ci.json +++ b/ci.json @@ -67,5 +67,6 @@ "max": 20, "statusCode": 200 } - } + }, + "minReputationToSubmitFiller": -1 } From c3d30b18e27b0a5e139a28a2be9d220b7e67ab1f Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 15 Aug 2022 17:32:07 -0400 Subject: [PATCH 114/168] Increase postgres read timeout and make it configurable --- src/config.ts | 3 ++- src/databases/Postgres.ts | 3 +-- src/types/config.model.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/config.ts b/src/config.ts index c680c23..e1c3cba 100644 --- a/src/config.ts +++ b/src/config.ts @@ -87,7 +87,8 @@ addDefaults(config, { user: "", host: "", password: "", - port: 5432 + port: 5432, + readTimeout: 250 }, dumpDatabase: { enabled: false, diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index dca5a46..be78b6e 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -32,7 +32,6 @@ export class Postgres implements IDatabase { private poolRead: Pool; private lastPoolReadFail = 0; - private readTimeout = 150; private maxTries = 3; @@ -116,7 +115,7 @@ export class Postgres implements IDatabase { pendingQueries.push(savePromiseState(lastPool.query({ text: query, values: params }))); const currentPromises = [...pendingQueries]; - if (options.useReplica) currentPromises.push(savePromiseState(timeoutPomise(this.readTimeout))); + if (options.useReplica) currentPromises.push(savePromiseState(timeoutPomise(this.config.postgresReadOnly.readTimeout))); const queryResult = await nextFulfilment(currentPromises); switch (type) { diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 47b3108..8b6bbd1 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -11,6 +11,7 @@ export interface CustomPostgresConfig extends PoolConfig { export interface CustomPostgresReadOnlyConfig extends CustomPostgresConfig { weight: number; + readTimeout: number; } export interface SBSConfig { From 5a43f46ac0ad48662cbeb57715d30a9bd998c52c Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 19 Aug 2022 15:40:32 -0400 Subject: [PATCH 115/168] Configurable expiry time --- src/config.ts | 3 ++- src/types/config.model.ts | 1 + src/utils/queryCacher.ts | 5 ++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/config.ts b/src/config.ts index e1c3cba..b39effa 100644 --- a/src/config.ts +++ b/src/config.ts @@ -132,7 +132,8 @@ addDefaults(config, { host: "", port: 0 }, - disableOfflineQueue: true + disableOfflineQueue: true, + expiryTime: 24 * 60 * 60, } }); loadFromEnv(config); diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 8b6bbd1..da01d60 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -3,6 +3,7 @@ import * as redis from "redis"; interface RedisConfig extends redis.RedisClientOptions { enabled: boolean; + expiryTime: number; } export interface CustomPostgresConfig extends PoolConfig { diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index c3548be..3abeb31 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -3,8 +3,7 @@ import { Logger } from "../utils/logger"; import { skipSegmentsHashKey, skipSegmentsKey, reputationKey, ratingHashKey, skipSegmentGroupsKey, userFeatureKey } from "./redisKeys"; import { Service, VideoID, VideoIDHash } from "../types/segments.model"; import { Feature, HashedUserID, UserID } from "../types/user.model"; - -const expiryTime = 2 * 60 * 60; +import { config } from "../config"; async function get(fetchFromDB: () => Promise, key: string): Promise { try { @@ -18,7 +17,7 @@ async function get(fetchFromDB: () => Promise, key: string): Promise { const data = await fetchFromDB(); - redis.setEx(key, expiryTime, JSON.stringify(data)).catch((err) => Logger.error(err)); + redis.setEx(key, config.redis?.expiryTime, JSON.stringify(data)).catch((err) => Logger.error(err)); return data; } From 95596282731d455666322958b40d4bb99ad7dd88 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 19 Aug 2022 15:45:23 -0400 Subject: [PATCH 116/168] Fix error --- src/utils/queryCacher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index 3abeb31..3d70871 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -68,7 +68,7 @@ async function getAndSplit(fetchFromDB: (values: U[]) => Pr } for (const key in newResults) { - redis.setEx(key, expiryTime, JSON.stringify(newResults[key])).catch((err) => Logger.error(err)); + redis.setEx(key, config.redis?.expiryTime, JSON.stringify(newResults[key])).catch((err) => Logger.error(err)); } }); } From 4f637daeaa5e43bf463f8ed6efa2147bb7e8f2ea Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 19 Aug 2022 15:48:03 -0400 Subject: [PATCH 117/168] best way to deal with annoyance --- src/routes/postSkipSegments.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 6d0b3a7..8ecd038 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -496,6 +496,10 @@ export async function postSkipSegments(req: Request, res: Response): Promise Date: Fri, 19 Aug 2022 18:45:02 -0400 Subject: [PATCH 118/168] Add max connections and idle timeout --- src/config.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/config.ts b/src/config.ts index b39effa..79e9c76 100644 --- a/src/config.ts +++ b/src/config.ts @@ -79,7 +79,9 @@ addDefaults(config, { user: "", host: "", password: "", - port: 5432 + port: 5432, + max: 10, + idleTimeoutMillis: 10000 }, postgresReadOnly: { enabled: false, @@ -88,7 +90,9 @@ addDefaults(config, { host: "", password: "", port: 5432, - readTimeout: 250 + readTimeout: 250, + max: 10, + idleTimeoutMillis: 10000 }, dumpDatabase: { enabled: false, From 47a6a13bdb313d3b73ecbac0220c6485d0b0ff62 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 20 Aug 2022 01:56:13 -0400 Subject: [PATCH 119/168] Remove unused statement timeout with pgbouncer --- src/databases/Postgres.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index be78b6e..0d2e097 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -39,8 +39,7 @@ export class Postgres implements IDatabase { async init(): Promise { this.pool = new Pool({ - ...this.config.postgres, - statement_timeout: 25000 + ...this.config.postgres }); this.pool.on("error", (err, client) => { Logger.error(err.stack); @@ -55,8 +54,7 @@ export class Postgres implements IDatabase { if (this.config.postgresReadOnly && this.config.postgresReadOnly.enabled) { this.poolRead = new Pool({ - ...this.config.postgresReadOnly, - statement_timeout: 1500 + ...this.config.postgresReadOnly }); this.poolRead.on("error", (err, client) => { Logger.error(err.stack); From 6be2f96f2647da2c1f152f07b0d40f1a450d8798 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sat, 20 Aug 2022 17:57:40 -0400 Subject: [PATCH 120/168] Make max tries configurable --- src/config.ts | 6 ++++-- src/databases/Postgres.ts | 5 ++--- src/types/config.model.ts | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/config.ts b/src/config.ts index 79e9c76..936aa22 100644 --- a/src/config.ts +++ b/src/config.ts @@ -81,7 +81,8 @@ addDefaults(config, { password: "", port: 5432, max: 10, - idleTimeoutMillis: 10000 + idleTimeoutMillis: 10000, + maxTries: 3 }, postgresReadOnly: { enabled: false, @@ -92,7 +93,8 @@ addDefaults(config, { port: 5432, readTimeout: 250, max: 10, - idleTimeoutMillis: 10000 + idleTimeoutMillis: 10000, + maxTries: 3 }, dumpDatabase: { enabled: false, diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 0d2e097..35d9eaa 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -33,8 +33,6 @@ export class Postgres implements IDatabase { private poolRead: Pool; private lastPoolReadFail = 0; - private maxTries = 3; - constructor(private config: DatabaseConfig) {} async init(): Promise { @@ -141,7 +139,8 @@ export class Postgres implements IDatabase { Logger.error(`prepare (postgres) try ${tries}: ${err}`); } - } while (this.isReadQuery(type) && tries < this.maxTries); + } while (this.isReadQuery(type) && tries < (lastPool === this.pool + ? this.config.postgres.maxTries : this.config.postgresReadOnly.maxTries)); } private getPool(type: string, options: QueryOption): Pool { diff --git a/src/types/config.model.ts b/src/types/config.model.ts index da01d60..53b39cd 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -8,6 +8,7 @@ interface RedisConfig extends redis.RedisClientOptions { export interface CustomPostgresConfig extends PoolConfig { enabled: boolean; + maxTries: number; } export interface CustomPostgresReadOnlyConfig extends CustomPostgresConfig { From 38a418a37a8b11b36ecbfcf29e2a782831719826 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 22 Aug 2022 10:56:22 -0400 Subject: [PATCH 121/168] Remove ratings code --- src/app.ts | 11 ---- src/config.ts | 6 -- src/routes/ratings/getRating.ts | 91 ---------------------------- src/routes/ratings/postClearCache.ts | 53 ---------------- src/routes/ratings/postRating.ts | 65 -------------------- src/types/config.model.ts | 1 - 6 files changed, 227 deletions(-) delete mode 100644 src/routes/ratings/getRating.ts delete mode 100644 src/routes/ratings/postClearCache.ts delete mode 100644 src/routes/ratings/postRating.ts diff --git a/src/app.ts b/src/app.ts index 05c0e1f..92a94bf 100644 --- a/src/app.ts +++ b/src/app.ts @@ -43,9 +43,6 @@ import ExpressPromiseRouter from "express-promise-router"; import { Server } from "http"; import { youtubeApiProxy } from "./routes/youtubeApiProxy"; import { getChapterNames } from "./routes/getChapterNames"; -import { postRating } from "./routes/ratings/postRating"; -import { getRating } from "./routes/ratings/getRating"; -import { postClearCache as ratingPostClearCache } from "./routes/ratings/postClearCache"; import { getTopCategoryUsers } from "./routes/getTopCategoryUsers"; import { addUserAsTempVIP } from "./routes/addUserAsTempVIP"; import { addFeature } from "./routes/addFeature"; @@ -80,11 +77,9 @@ function setupRoutes(router: Router) { // Rate limit endpoint lists const voteEndpoints: RequestHandler[] = [voteOnSponsorTime]; const viewEndpoints: RequestHandler[] = [viewedVideoSponsorTime]; - const postRateEndpoints: RequestHandler[] = [postRating]; if (config.rateLimit) { if (config.rateLimit.vote) voteEndpoints.unshift(rateLimitMiddleware(config.rateLimit.vote, voteGetUserID)); if (config.rateLimit.view) viewEndpoints.unshift(rateLimitMiddleware(config.rateLimit.view)); - if (config.rateLimit.rate) postRateEndpoints.unshift(rateLimitMiddleware(config.rateLimit.rate)); } //add the get function @@ -199,12 +194,6 @@ function setupRoutes(router: Router) { router.post("/api/feature", addFeature); - // ratings - router.get("/api/ratings/rate/:prefix", getRating); - router.get("/api/ratings/rate", getRating); - router.post("/api/ratings/rate", postRateEndpoints); - router.post("/api/ratings/clearCache", ratingPostClearCache); - if (config.postgres?.enabled) { router.get("/database", (req, res) => dumpDatabase(req, res, true)); router.get("/database.json", (req, res) => dumpDatabase(req, res, false)); diff --git a/src/config.ts b/src/config.ts index 936aa22..4c74aba 100644 --- a/src/config.ts +++ b/src/config.ts @@ -62,12 +62,6 @@ addDefaults(config, { max: 10, statusCode: 200, message: "OK", - }, - rate: { - windowMs: 900000, - max: 20, - statusCode: 200, - message: "Success", } }, userCounterURL: null, diff --git a/src/routes/ratings/getRating.ts b/src/routes/ratings/getRating.ts deleted file mode 100644 index b3fd5c0..0000000 --- a/src/routes/ratings/getRating.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Request, Response } from "express"; -import { db } from "../../databases/databases"; -import { RatingType } from "../../types/ratings.model"; -import { Service, VideoID, VideoIDHash } from "../../types/segments.model"; -import { getService } from "../../utils/getService"; -import { hashPrefixTester } from "../../utils/hashPrefixTester"; -import { Logger } from "../../utils/logger"; -import { QueryCacher } from "../../utils/queryCacher"; -import { ratingHashKey } from "../../utils/redisKeys"; - -interface DBRating { - videoID: VideoID, - hashedVideoID: VideoIDHash, - service: Service, - type: RatingType, - count: number -} - -export async function getRating(req: Request, res: Response): Promise { - let hashPrefixes: VideoIDHash[] = []; - try { - hashPrefixes = req.query.hashPrefixes - ? JSON.parse(req.query.hashPrefixes as string) - : Array.isArray(req.query.prefix) - ? req.query.prefix - : [req.query.prefix ?? req.params.prefix]; - if (!Array.isArray(hashPrefixes)) { - return res.status(400).send("hashPrefixes parameter does not match format requirements."); - } - - hashPrefixes.map((hashPrefix) => hashPrefix?.toLowerCase()); - } catch(error) { - return res.status(400).send("Bad parameter: hashPrefixes (invalid JSON)"); - } - if (hashPrefixes.length === 0 || hashPrefixes.length > 75 - || hashPrefixes.some((hashPrefix) => !hashPrefix || !hashPrefixTester(hashPrefix))) { - return res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix - } - - let types: RatingType[] = []; - try { - types = req.query.types - ? JSON.parse(req.query.types as string) - : req.query.type - ? Array.isArray(req.query.type) - ? req.query.type - : [req.query.type] - : [RatingType.Upvote, RatingType.Downvote]; - if (!Array.isArray(types)) { - return res.status(400).send("Types parameter does not match format requirements."); - } - - types = types.map((type) => parseInt(type as unknown as string, 10)); - } catch(error) { - return res.status(400).send("Bad parameter: types (invalid JSON)"); - } - - const service: Service = getService(req.query.service, req.body.service); - - try { - const ratings = (await getRatings(hashPrefixes, service)) - .filter((rating) => types.includes(rating.type)) - .map((rating) => ({ - videoID: rating.videoID, - hash: rating.hashedVideoID, - service: rating.service, - type: rating.type, - count: rating.count - })); - - return res.status((ratings.length) ? 200 : 404) - .send(ratings ?? []); - } catch (err) { - Logger.error(err as string); - - return res.sendStatus(500); - } -} - -function getRatings(hashPrefixes: VideoIDHash[], service: Service): Promise { - const fetchFromDB = (hashPrefixes: VideoIDHash[]) => db - .prepare( - "all", - `SELECT "videoID", "hashedVideoID", "type", "count" FROM "ratings" WHERE "hashedVideoID" ~* ? AND "service" = ? ORDER BY "hashedVideoID"`, - [`^(?:${hashPrefixes.join("|")})`, service] - ) as Promise; - - return (hashPrefixes.every((hashPrefix) => hashPrefix.length === 4)) - ? QueryCacher.getAndSplit(fetchFromDB, (prefix) => ratingHashKey(prefix, service), "hashedVideoID", hashPrefixes) - : fetchFromDB(hashPrefixes); -} \ No newline at end of file diff --git a/src/routes/ratings/postClearCache.ts b/src/routes/ratings/postClearCache.ts deleted file mode 100644 index 38a8707..0000000 --- a/src/routes/ratings/postClearCache.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Logger } from "../../utils/logger"; -import { HashedUserID, UserID } from "../../types/user.model"; -import { getHash } from "../../utils/getHash"; -import { getHashCache } from "../../utils/getHashCache"; -import { Request, Response } from "express"; -import { Service, VideoID } from "../../types/segments.model"; -import { QueryCacher } from "../../utils/queryCacher"; -import { isUserVIP } from "../../utils/isUserVIP"; -import { VideoIDHash } from "../../types/segments.model"; -import { getService } from "../..//utils/getService"; - -export async function postClearCache(req: Request, res: Response): Promise { - const videoID = req.query.videoID as VideoID; - const userID = req.query.userID as UserID; - const service = getService(req.query.service as Service); - - const invalidFields = []; - if (typeof videoID !== "string") { - invalidFields.push("videoID"); - } - if (typeof userID !== "string") { - invalidFields.push("userID"); - } - - if (invalidFields.length !== 0) { - // invalid request - const fields = invalidFields.reduce((p, c, i) => p + (i !== 0 ? ", " : "") + c, ""); - return res.status(400).send(`No valid ${fields} field(s) provided`); - } - - // hash the userID as early as possible - const hashedUserID: HashedUserID = await getHashCache(userID); - // hash videoID - const hashedVideoID: VideoIDHash = getHash(videoID, 1); - - // Ensure user is a VIP - if (!(await isUserVIP(hashedUserID))){ - Logger.warn(`Permission violation: User ${hashedUserID} attempted to clear cache for video ${videoID}.`); - return res.status(403).json({ "message": "Not a VIP" }); - } - - try { - QueryCacher.clearRatingCache({ - hashedVideoID, - service - }); - return res.status(200).json({ - message: `Cache cleared on video ${videoID}` - }); - } catch(err) { - return res.sendStatus(500); - } -} diff --git a/src/routes/ratings/postRating.ts b/src/routes/ratings/postRating.ts deleted file mode 100644 index 866fe57..0000000 --- a/src/routes/ratings/postRating.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { db, privateDB } from "../../databases/databases"; -import { getHash } from "../../utils/getHash"; -import { getHashCache } from "../../utils/getHashCache"; -import { Logger } from "../../utils/logger"; -import { Request, Response } from "express"; -import { HashedUserID, UserID } from "../../types/user.model"; -import { HashedIP, IPAddress, VideoID } from "../../types/segments.model"; -import { getIP } from "../../utils/getIP"; -import { getService } from "../../utils/getService"; -import { RatingType, RatingTypes } from "../../types/ratings.model"; -import { config } from "../../config"; -import { QueryCacher } from "../../utils/queryCacher"; - -export async function postRating(req: Request, res: Response): Promise { - const privateUserID = req.body.userID as UserID; - const videoID = req.body.videoID as VideoID; - const service = getService(req.query.service, req.body.service); - const type = req.body.type as RatingType; - const enabled = req.body.enabled ?? true; - - if (privateUserID == undefined || videoID == undefined || service == undefined || type == undefined - || (typeof privateUserID !== "string") || (typeof videoID !== "string") || (typeof service !== "string") - || (typeof type !== "number") || (enabled && (typeof enabled !== "boolean")) || !RatingTypes.includes(type)) { - //invalid request - return res.sendStatus(400); - } - - const hashedIP: HashedIP = getHash(getIP(req) + config.globalSalt as IPAddress, 1); - const hashedUserID: HashedUserID = await getHashCache(privateUserID); - const hashedVideoID = getHash(videoID, 1); - - try { - // Check if this user has voted before - const existingVote = await privateDB.prepare("get", `SELECT count(*) as "count" FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "userID" = ?`, [videoID, service, type, hashedUserID]); - if (existingVote.count > 0 && !enabled) { - // Undo the vote - await privateDB.prepare("run", `DELETE FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "userID" = ?`, [videoID, service, type, hashedUserID]); - await db.prepare("run", `UPDATE "ratings" SET "count" = "count" - 1 WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]); - } else if (existingVote.count === 0 && enabled) { - // Make sure there hasn't been another vote from this IP - const existingIPVote = (await privateDB.prepare("get", `SELECT count(*) as "count" FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND "type" = ? AND "hashedIP" = ?`, [videoID, service, type, hashedIP])) - .count > 0; - if (existingIPVote) { // if exisiting vote, exit early instead - return res.sendStatus(200); - } - - // Create entry in privateDB - await privateDB.prepare("run", `INSERT INTO "ratings" ("videoID", "service", "type", "userID", "timeSubmitted", "hashedIP") VALUES (?, ?, ?, ?, ?, ?)`, [videoID, service, type, hashedUserID, Date.now(), hashedIP]); - - // Check if general rating already exists, if so increase it - const rating = await db.prepare("get", `SELECT count(*) as "count" FROM "ratings" WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]); - if (rating.count > 0) { - await db.prepare("run", `UPDATE "ratings" SET "count" = "count" + 1 WHERE "videoID" = ? AND "service" = ? AND type = ?`, [videoID, service, type]); - } else { - await db.prepare("run", `INSERT INTO "ratings" ("videoID", "service", "type", "count", "hashedVideoID") VALUES (?, ?, ?, 1, ?)`, [videoID, service, type, hashedVideoID]); - } - } - // clear rating cache - QueryCacher.clearRatingCache({ hashedVideoID, service }); - return res.sendStatus(200); - } catch (err) { - Logger.error(err as string); - return res.sendStatus(500); - } -} \ No newline at end of file diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 53b39cd..56a271c 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -52,7 +52,6 @@ export interface SBSConfig { rateLimit: { vote: RateLimitConfig; view: RateLimitConfig; - rate: RateLimitConfig; }; mysql?: any; privateMysql?: any; From 8dbfe17ee418fd372866d8f6c361a691e5e8a663 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 22 Aug 2022 11:04:33 -0400 Subject: [PATCH 122/168] remove ratings tests --- test/cases/ratings/getRating.ts | 118 --------------------------- test/cases/ratings/postClearCache.ts | 51 ------------ test/cases/ratings/postRating.ts | 118 --------------------------- 3 files changed, 287 deletions(-) delete mode 100644 test/cases/ratings/getRating.ts delete mode 100644 test/cases/ratings/postClearCache.ts delete mode 100644 test/cases/ratings/postRating.ts diff --git a/test/cases/ratings/getRating.ts b/test/cases/ratings/getRating.ts deleted file mode 100644 index 0f63ba7..0000000 --- a/test/cases/ratings/getRating.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { db } from "../../../src/databases/databases"; -import { getHash } from "../../../src/utils/getHash"; -import assert from "assert"; -import { client } from "../../utils/httpClient"; -import { AxiosResponse } from "axios"; -import { partialDeepEquals, arrayPartialDeepEquals } from "../../utils/partialDeepEquals"; - -const endpoint = "/api/ratings/rate"; -const getRating = (hash: string, params?: unknown): Promise => client.get(`${endpoint}/${hash}`, { params }); -const getBulkRating = (hashes: string[], params?: any): Promise => client.get(endpoint, { params: { ...params, prefix: hashes } }); - -const videoOneID = "some-likes-and-dislikes"; -const videoOneIDHash = getHash(videoOneID, 1); -const videoOnePartialHash = videoOneIDHash.substr(0, 4); -const videoTwoID = "some-likes-and-dislikes-2"; -const videoTwoIDHash = getHash(videoTwoID, 1); -const videoTwoPartialHash = videoTwoIDHash.substr(0, 4); - -describe("getRating", () => { - before(async () => { - const insertUserNameQuery = 'INSERT INTO "ratings" ("videoID", "service", "type", "count", "hashedVideoID") VALUES (?, ?, ?, ?, ?)'; - await db.prepare("run", insertUserNameQuery, [videoOneID, "YouTube", 0, 5, videoOneIDHash]); - await db.prepare("run", insertUserNameQuery, [videoOneID, "YouTube", 1, 10, videoOneIDHash]); - - await db.prepare("run", insertUserNameQuery, [videoTwoID, "YouTube", 0, 20, videoTwoIDHash]); - await db.prepare("run", insertUserNameQuery, [videoTwoID, "YouTube", 1, 30, videoTwoIDHash]); - }); - - it("Should be able to get dislikes and likes by default", (done) => { - getRating(videoOnePartialHash) - .then(res => { - assert.strictEqual(res.status, 200); - const expected = [{ - type: 0, - count: 5, - }, { - type: 1, - count: 10, - }]; - assert.ok(partialDeepEquals(res.data, expected)); - done(); - }) - .catch(err => done(err)); - }); - - it("Should be able to filter for only dislikes", (done) => { - getRating(videoOnePartialHash, { type: 0 }) - .then(res => { - assert.strictEqual(res.status, 200); - const expected = [{ - type: 0, - count: 5, - }]; - assert.ok(partialDeepEquals(res.data, expected)); - - done(); - }) - .catch(err => done(err)); - }); - - /* - This test will fail if tests are already ran with redis. - */ - it("Should be able to bulk fetch", (done) => { - getBulkRating([videoOnePartialHash, videoTwoPartialHash]) - .then(res => { - assert.strictEqual(res.status, 200); - const expected = [{ - type: 0, - count: 20, - hash: videoTwoIDHash, - }, - { - type: 1, - count: 30, - hash: videoTwoIDHash, - }, { - type: 0, - count: 5, - hash: videoOneIDHash, - }, { - type: 1, - count: 10, - hash: videoOneIDHash, - }]; - assert.ok(arrayPartialDeepEquals(res.data, expected)); - done(); - }) - .catch(err => done(err)); - }); - - it("Should return 400 for invalid hash", (done) => { - getRating("a") - .then(res => { - assert.strictEqual(res.status, 400); - done(); - }) - .catch(err => done(err)); - }); - - it("Should return 404 for nonexitent type", (done) => { - getRating(videoOnePartialHash, { type: 100 }) - .then(res => { - assert.strictEqual(res.status, 404); - done(); - }) - .catch(err => done(err)); - }); - - it("Should return 404 for nonexistent videoID", (done) => { - getRating("aaaa") - .then(res => { - assert.strictEqual(res.status, 404); - done(); - }) - .catch(err => done(err)); - }); -}); \ No newline at end of file diff --git a/test/cases/ratings/postClearCache.ts b/test/cases/ratings/postClearCache.ts deleted file mode 100644 index 1b2b69a..0000000 --- a/test/cases/ratings/postClearCache.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { db } from "../../../src/databases/databases"; -import { getHash } from "../../../src/utils/getHash"; -import assert from "assert"; -import { client } from "../../utils/httpClient"; - -const VIPUser = "clearCacheVIP"; -const regularUser = "regular-user"; -const endpoint = "/api/ratings/clearCache"; -const postClearCache = (userID: string, videoID: string) => client({ method: "post", url: endpoint, params: { userID, videoID } }); - -describe("ratings postClearCache", () => { - before(async () => { - await db.prepare("run", `INSERT INTO "vipUsers" ("userID") VALUES ('${getHash(VIPUser)}')`); - }); - - it("Should be able to clear cache amy video", (done) => { - postClearCache(VIPUser, "dne-video") - .then(res => { - assert.strictEqual(res.status, 200); - done(); - }) - .catch(err => done(err)); - }); - - it("Should get 403 as non-vip", (done) => { - postClearCache(regularUser, "clear-test") - .then(res => { - assert.strictEqual(res.status, 403); - done(); - }) - .catch(err => done(err)); - }); - - it("Should give 400 with missing videoID", (done) => { - client.post(endpoint, { params: { userID: VIPUser } }) - .then(res => { - assert.strictEqual(res.status, 400); - done(); - }) - .catch(err => done(err)); - }); - - it("Should give 400 with missing userID", (done) => { - client.post(endpoint, { params: { videoID: "clear-test" } }) - .then(res => { - assert.strictEqual(res.status, 400); - done(); - }) - .catch(err => done(err)); - }); -}); diff --git a/test/cases/ratings/postRating.ts b/test/cases/ratings/postRating.ts deleted file mode 100644 index be63f60..0000000 --- a/test/cases/ratings/postRating.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { db } from "../../../src/databases/databases"; -import { getHash } from "../../../src/utils/getHash"; -import assert from "assert"; -import { client } from "../../utils/httpClient"; -import { AxiosResponse } from "axios"; -import { partialDeepEquals } from "../../utils/partialDeepEquals"; - -const endpoint = "/api/ratings/rate/"; -const postRating = (body: unknown): Promise => client.post(endpoint, body); -const queryDatabase = (videoID: string) => db.prepare("all", `SELECT * FROM "ratings" WHERE "videoID" = ?`, [videoID]); - -const videoIDOne = "normal-video"; -const videoIDTwo = "multiple-rates"; -const ratingUserID = "rating-testman"; - -describe("postRating", () => { - before(async () => { - const insertUserNameQuery = 'INSERT INTO "ratings" ("videoID", "service", "type", "count", "hashedVideoID") VALUES (?, ?, ?, ?, ?)'; - await db.prepare("run", insertUserNameQuery, [videoIDTwo, "YouTube", 0, 3, getHash(videoIDTwo, 1)]); - }); - - it("Should be able to vote on a video", (done) => { - const videoID = videoIDOne; - postRating({ - userID: ratingUserID, - videoID, - type: 0 - }) - .then(async res => { - assert.strictEqual(res.status, 200); - const expected = [{ - hashedVideoID: getHash(videoID, 1), - videoID, - type: 0, - count: 1, - service: "YouTube" - }]; - assert.ok(partialDeepEquals(await queryDatabase(videoID), expected)); - done(); - }) - .catch(err => done(err)); - }); - - it("Should be able to undo a vote on a video", (done) => { - const videoID = videoIDOne; - postRating({ - userID: ratingUserID, - videoID, - type: 0, - enabled: false - }) - .then(async res => { - assert.strictEqual(res.status, 200); - const expected = [{ - type: 0, - count: 0 - }]; - assert.ok(partialDeepEquals(await queryDatabase(videoID), expected)); - done(); - }) - .catch(err => done(err)); - }); - - it("Should be able to vote after someone else on a video", (done) => { - const videoID = videoIDTwo; - postRating({ - userID: ratingUserID, - videoID, - type: 0 - }) - .then(async res => { - assert.strictEqual(res.status, 200); - const expected = [{ - type: 0, - count: 4 - }]; - assert.ok(partialDeepEquals(await queryDatabase(videoID), expected)); - done(); - }) - .catch(err => done(err)); - }); - - it("Should be able to vote a different type than existing votes on a video", (done) => { - const videoID = videoIDTwo; - postRating({ - userID: ratingUserID, - videoID, - type: 1 - }) - .then(async res => { - assert.strictEqual(res.status, 200); - const expected = [{ - type: 0, - count: 4 - }, { - type: 1, - count: 1 - }]; - assert.ok(partialDeepEquals(await queryDatabase(videoID), expected)); - done(); - }) - .catch(err => done(err)); - }); - - it("Should not be able to vote with nonexistent type", (done) => { - const videoID = videoIDOne; - postRating({ - userID: ratingUserID, - videoID, - type: 100 - }) - .then(res => { - assert.strictEqual(res.status, 400); - done(); - }) - .catch(err => done(err)); - }); -}); \ No newline at end of file From dad205e729dab9e5ca15e7366d1857137a991c16 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 22 Aug 2022 11:07:30 -0400 Subject: [PATCH 123/168] don't scan ratings dir for tests --- test/test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.ts b/test/test.ts index e13f4ba..d91da93 100644 --- a/test/test.ts +++ b/test/test.ts @@ -33,7 +33,7 @@ async function init() { // Instantiate a Mocha instance. const mocha = new Mocha(); - const testDirs = ["./test/cases", "./test/cases/ratings"]; + const testDirs = ["./test/cases"]; // Add each .ts file to the mocha instance testDirs.forEach(testDir => { From 44d541bafeb188be9d441fcf594489b98ba70af0 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 22 Aug 2022 11:59:08 -0400 Subject: [PATCH 124/168] Don't use replica for is vip --- src/utils/isUserVIP.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/isUserVIP.ts b/src/utils/isUserVIP.ts index 50cc462..0bdd374 100644 --- a/src/utils/isUserVIP.ts +++ b/src/utils/isUserVIP.ts @@ -3,5 +3,5 @@ import { HashedUserID } from "../types/user.model"; export async function isUserVIP(userID: HashedUserID): Promise { return (await db.prepare("get", `SELECT count(*) as "userCount" FROM "vipUsers" WHERE "userID" = ? LIMIT 1`, - [userID], { useReplica: true }))?.userCount > 0; + [userID]))?.userCount > 0; } From 9da1fc523af364db5c4e8ca3eae3f35d04346b85 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 25 Aug 2022 15:08:54 -0400 Subject: [PATCH 125/168] Parse float instead of int --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index 4c74aba..d7deb4b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -183,7 +183,7 @@ function loadFromEnv(config: SBSConfig, prefix = "") { } else if (process.env[fullKey]) { const value = process.env[fullKey]; if (isNumber(value)) { - config[key] = parseInt(value, 10); + config[key] = parseFloat(value); } else if (value.toLowerCase() === "true" || value.toLowerCase() === "false") { config[key] = value === "true"; } else if (key === "newLeafURLs") { From 027ff694a05c19008e00d627008669f4fc94237b Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 25 Aug 2022 20:44:16 -0400 Subject: [PATCH 126/168] Make redis timeout configurable --- src/config.ts | 1 + src/types/config.model.ts | 1 + src/utils/redis.ts | 3 +-- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/config.ts b/src/config.ts index d7deb4b..352dee0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -134,6 +134,7 @@ addDefaults(config, { }, disableOfflineQueue: true, expiryTime: 24 * 60 * 60, + getTimeout: 40 } }); loadFromEnv(config); diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 56a271c..1fb6d42 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -4,6 +4,7 @@ import * as redis from "redis"; interface RedisConfig extends redis.RedisClientOptions { enabled: boolean; expiryTime: number; + getTimeout: number; } export interface CustomPostgresConfig extends PoolConfig { diff --git a/src/utils/redis.ts b/src/utils/redis.ts index 76a607a..06a89d7 100644 --- a/src/utils/redis.ts +++ b/src/utils/redis.ts @@ -31,10 +31,9 @@ if (config.redis?.enabled) { client.connect(); exportClient = client as RedisSB; - const timeoutDuration = 40; const get = client.get.bind(client); exportClient.get = (key) => new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(), timeoutDuration); + const timeout = setTimeout(() => reject(), config.redis.getTimeout); get(key).then((reply) => { clearTimeout(timeout); resolve(reply); From cbf352173ad44b0cd55ee68feac67fa3269f46c8 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 25 Aug 2022 21:14:25 -0400 Subject: [PATCH 127/168] throw error if query fails --- src/databases/Postgres.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 35d9eaa..ff97c6f 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -141,6 +141,8 @@ export class Postgres implements IDatabase { } } while (this.isReadQuery(type) && tries < (lastPool === this.pool ? this.config.postgres.maxTries : this.config.postgresReadOnly.maxTries)); + + throw new Error(`prepare (postgres): ${type} ${query} failed after ${tries} tries`); } private getPool(type: string, options: QueryOption): Pool { From ab6fcb8943fa52d4ce0b90596f14f62c12f99e99 Mon Sep 17 00:00:00 2001 From: Ajay Ramachandran Date: Fri, 26 Aug 2022 20:49:31 -0400 Subject: [PATCH 128/168] delete unused directory --- nginx/cors.conf | 11 -- nginx/error.conf | 7 - nginx/error/error.html | 1 - nginx/error_map.conf | 15 -- nginx/nginx.conf | 317 ----------------------------------------- nginx/proxy.conf | 12 -- 6 files changed, 363 deletions(-) delete mode 100644 nginx/cors.conf delete mode 100644 nginx/error.conf delete mode 100644 nginx/error/error.html delete mode 100644 nginx/error_map.conf delete mode 100644 nginx/nginx.conf delete mode 100644 nginx/proxy.conf diff --git a/nginx/cors.conf b/nginx/cors.conf deleted file mode 100644 index 69e42ca..0000000 --- a/nginx/cors.conf +++ /dev/null @@ -1,11 +0,0 @@ -if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE'; - add_header 'Access-Control-Allow-Headers' 'Content-Type'; - # cache CORS for 24 hours - add_header 'Access-Control-Max-Age' 86400; - # return empty response for preflight - add_header 'Content-Type' 'text/plain; charset=UTF-8'; - add_header 'Content-Length' 0; - return 204; -} diff --git a/nginx/error.conf b/nginx/error.conf deleted file mode 100644 index 80c5f7b..0000000 --- a/nginx/error.conf +++ /dev/null @@ -1,7 +0,0 @@ -error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html; - -location = /error.html { - ssi on; - internal; - root /etc/nginx/error; -} \ No newline at end of file diff --git a/nginx/error/error.html b/nginx/error/error.html deleted file mode 100644 index ece1e33..0000000 --- a/nginx/error/error.html +++ /dev/null @@ -1 +0,0 @@ - https://status.sponsor.ajay.app \ No newline at end of file diff --git a/nginx/error_map.conf b/nginx/error_map.conf deleted file mode 100644 index 8de14c7..0000000 --- a/nginx/error_map.conf +++ /dev/null @@ -1,15 +0,0 @@ -map $status $status_text { - 400 'Bad Request'; - 401 'Unauthorized'; - 403 'Forbidden'; - 404 'Not Found'; - 405 'Method Not Allowed'; - 408 'Request Timeout'; - 409 'Conflict'; - 429 'Too Many Requests'; - 500 'Internal Server Error'; - 502 'Bad Gateway'; - 503 'Service Unavailable'; - 504 'Gateway Timeout'; - 505 'HTTP Version Not Supported'; -} \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf deleted file mode 100644 index a113bd8..0000000 --- a/nginx/nginx.conf +++ /dev/null @@ -1,317 +0,0 @@ -worker_processes 2; -worker_rlimit_nofile 500000; -worker_shutdown_timeout 10; - -events { - worker_connections 100000; # Default: 1024 - #use epoll; - #multi_accept on; -} - -http { - log_format no_ip '$remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" "$gzip_ratio"'; - - log_format user_agent '[$time_local] ' - '"$http_referer" "$http_user_agent" "$gzip_ratio"'; - - #limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s; - limit_req_log_level warn; - - include /etc/nginx/mime.types; - include /etc/nginx/proxy.conf; - - # error_map has to be at http level - include /etc/nginx/error_map.conf; - # Custom MIME definition - types { - text/csv csv; - } - # keepalive settings - #keepalive_requests 10; - keepalive_timeout 10s; - http2_idle_timeout 20s; # replaced by keepalive_timeout in 1.19.7 - - access_log off; - #error_log /etc/nginx/logs/error.log warn; - error_log /dev/null crit; - - upstream backend_GET { - least_conn; - - #keepalive 5; - #server localhost:4441; - #server localhost:4442; - #server localhost:4443; - #server localhost:4444; - #server localhost:4445; - #server localhost:4446; - #server localhost:4447; - #server localhost:4448; - #server 10.0.0.4:4441 max_fails=25 fail_timeout=20s; - - #server 10.0.0.3:4441 max_fails=25 fail_timeout=20s; - #server 10.0.0.3:4442 max_fails=25 fail_timeout=20s; - - server 10.0.0.5:4441 max_fails=25 fail_timeout=20s; - server 10.0.0.5:4442 max_fails=25 fail_timeout=20s; - - server 10.0.0.6:4441 max_fails=25 fail_timeout=20s; - server 10.0.0.6:4442 max_fails=25 fail_timeout=20s; - - server 10.0.0.9:4441 max_fails=25 fail_timeout=20s; - server 10.0.0.9:4442 max_fails=25 fail_timeout=20s; - - server 10.0.0.12:4441 max_fails=25 fail_timeout=20s; - server 10.0.0.12:4442 max_fails=25 fail_timeout=20s; - - server 10.0.0.10:4441 max_fails=25 fail_timeout=20s; - server 10.0.0.10:4442 max_fails=25 fail_timeout=20s; - - server 10.0.0.13:4441 max_fails=25 fail_timeout=20s; - server 10.0.0.13:4442 max_fails=25 fail_timeout=20s; - - server 10.0.0.14:4441 max_fails=25 fail_timeout=20s; - server 10.0.0.14:4442 max_fails=25 fail_timeout=20s; - - server 10.0.0.11:4441 max_fails=25 fail_timeout=20s; - server 10.0.0.11:4442 max_fails=25 fail_timeout=20s; - - server 10.0.0.16:4441 max_fails=25 fail_timeout=20s; - server 10.0.0.16:4442 max_fails=25 fail_timeout=20s; - - #server 10.0.0.17:4441 max_fails=25 fail_timeout=20s; - #server 10.0.0.17:4442 max_fails=25 fail_timeout=20s; - - #server 134.209.69.251:80 backup; - - #server 116.203.32.253:80 backup; - #server 116.203.32.253:80; - } - upstream backend_POST { - #server localhost:4441; - #server localhost:4442; - server 10.0.0.3:4441 max_fails=25 fail_timeout=15s; - server 10.0.0.4:4441 max_fails=25 fail_timeout=15s; - #server 10.0.0.3:4442; - } - upstream backend_db { - server 10.0.0.4:4441 max_fails=1 fail_timeout=3s; - #server 10.0.0.3:4441; - #server 10.0.0.4; - } - upstream backend_db_dl { - server 10.0.0.4; - } - - proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHEZONE:10m inactive=60m max_size=400m; - proxy_cache_key "$scheme$request_method$host$request_uri"; - add_header X-Cache $upstream_cache_status; - - server { - server_name sponsor.ajay.app api.sponsor.ajay.app; - - include /etc/nginx/error.conf; - set_real_ip_from 10.0.0.0/24; - real_ip_header proxy_protocol; - - location /news { - return 301 https://blog.ajay.app/sponsorblock; - } - - location /viewer { - return 301 https://sb.ltn.fi; - } - - location /test/ { - # return 404 ""; - proxy_pass http://10.0.0.4:4445/; - #proxy_pass https://sbtest.etcinit.com/; - } - - #access_log /etc/nginx/logs/requests.log no_ip buffer=64k; - - location /api/skipSegments { - include /etc/nginx/cors.conf; - #return 200 "[]"; - proxy_pass http://backend_$request_method; - proxy_cache CACHEZONE; - proxy_cache_valid 20s; - #limit_req zone=mylimit; - - #access_log /etc/nginx/logs/download.log no_ip; - gzip on; - if ($request_method = POST) { - access_log /etc/nginx/logs/submissions.log user_agent buffer=64k; - } - - #proxy_read_timeout 6s; - #proxy_next_upstream error timeout http_500 http_502; - } - - location /api/getTopUsers { - include /etc/nginx/cors.conf; - proxy_pass http://backend_GET; - proxy_cache CACHEZONE; - proxy_cache_valid 20m; - } - - location /api/getTotalStats { - include /etc/nginx/cors.conf; - proxy_pass http://backend_POST; - proxy_cache CACHEZONE; - proxy_cache_valid 20m; - #return 204; - } - - location /api/getTopCategoryUsers { - include /etc/nginx/cors.conf; - proxy_pass http://backend_POST; - proxy_cache CACHEZONE; - proxy_cache_valid 20m; - } - - location /api/getVideoSponsorTimes { - include /etc/nginx/cors.conf; - proxy_pass http://backend_GET; - } - - location /api/isUserVIP { - include /etc/nginx/cors.conf; - proxy_pass http://backend_GET; - } - - location /download/ { - #access_log /etc/nginx/logs/download.log no_ip buffer=64k; - gzip on; - proxy_max_temp_file_size 0; - #proxy_cache CACHEZONE; - #proxy_cache_valid 20m; - #proxy_http_version 1.0; - #gzip_types text/csv; - #gzip_comp_level 1; - #proxy_buffering off; - - - proxy_pass http://backend_db; - #alias /home/sbadmin/sponsor/docker/database-export/; - #return 307 https://rsync.sponsor.ajay.app$request_uri; - } - - location /database { - proxy_pass http://backend_db; - #return 200 "Disabled for load reasons"; - } - - location = /database.db { - return 404 "Sqlite database has been replaced with csv exports at https://sponsor.ajay.app/database. Sqlite exports might come back soon, but exported at longer intervals."; - #alias /home/sbadmin/sponsor/databases/sponsorTimes.db; - #alias /home/sbadmin/test-db/database.db; - } - - #location = /database/sponsorTimes.csv { - # alias /home/sbadmin/sponsorTimes.csv; - #} - - #location /api/voteOnSponsorTime { - # return 200 "Success"; - #} - - #location /api/viewedVideoSponsorTime { - # return 200 "Success"; - #} - - location /api { - include /etc/nginx/cors.conf; - proxy_pass http://backend_POST; - } - - location / { - root /home/sbadmin/SponsorBlockSite/public-prod; - error_page 404 /404.html; - } - - listen [::]:443 default_server ssl http2 ipv6only=on backlog=323999; - listen 443 default_server ssl http2 reuseport backlog=3000999; # managed by Certbot - listen 4443 default_server ssl http2 proxy_protocol reuseport backlog=3000999; - #listen 443 http3 reuseport; - #ssl_protocols TLSv1.2 TLSv1.3; - listen 8081 proxy_protocol; - port_in_redirect off; - ssl_certificate /home/sbadmin/certs/cert.pem; - ssl_certificate_key /home/sbadmin/certs/key.pem; - #ssl_certificate /etc/letsencrypt/live/sponsor.ajay.app-0001/fullchain.pem; # managed by Certbot - #ssl_certificate_key /etc/letsencrypt/live/sponsor.ajay.app-0001/privkey.pem; # managed by Certbot - include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot - } - - server { - server_name cdnsponsor.ajay.app; - error_page 404 /404.html; - - #location /database/ { - # alias /home/sbadmin/sponsor/docker/database-export/; - #} - - #location /download/ { - # alias /home/sbadmin/sponsor/docker/database-export/; - #} - - location / { - root /home/sbadmin/SponsorBlockSite/public-prod; - } - - - listen 443 ssl; # managed by Certbot - ssl_certificate /home/sbadmin/certs/cert.pem; - ssl_certificate_key /home/sbadmin/certs/key.pem; - #ssl_certificate /etc/letsencrypt/live/sponsor.ajay.app-0001/fullchain.pem; # managed by Certbot - #ssl_certificate_key /etc/letsencrypt/live/sponsor.ajay.app-0001/privkey.pem; # managed by Certbot - include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot - } - - server { - access_log off; - - return 301 https://$host$request_uri; - - listen [::]:80 ipv6only=on; - listen 8080 proxy_protocol; - listen 80; - server_name sponsor.ajay.app api.sponsor.ajay.app, cdnsponsor.ajay.app, wiki.sponsor.ajay.app; - return 404; # managed by Certbot - } - - server { - server_name wiki.sponsor.ajay.app; # managed by Certbot - - location /.well-known/ { - root /home/sbadmin/SponsorBlockSite/public-prod; - } - - location ~* ^/index.php/(?.*)$ { - return 301 /w/$pagename; - } - - location / { - proxy_pass http://10.0.0.3:8080; - } - - port_in_redirect off; - listen [::]:443 ssl http2; - listen 443 ssl http2; # managed by Certbot - listen 8081 proxy_protocol; - #listen 443 http3 reuseport; - #ssl_protocols TLSv1.2 TLSv1.3; - #listen 80; - ssl_certificate /home/sbadmin/certs/cert.pem; - ssl_certificate_key /home/sbadmin/certs/key.pem; - #ssl_certificate /etc/letsencrypt/live/sponsor.ajay.app-0001/fullchain.pem; # managed by Certbot - #ssl_certificate_key /etc/letsencrypt/live/sponsor.ajay.app-0001/privkey.pem; # managed by Certbot - include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot - ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot - } -} diff --git a/nginx/proxy.conf b/nginx/proxy.conf deleted file mode 100644 index 0af7066..0000000 --- a/nginx/proxy.conf +++ /dev/null @@ -1,12 +0,0 @@ -proxy_redirect off; -proxy_set_header Host $host; -proxy_set_header X-Real-IP $remote_addr; -proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; -proxy_set_header Connection ""; -client_max_body_size 10m; -client_body_buffer_size 128k; -proxy_connect_timeout 5s; -#proxy_send_timeout 10; -proxy_read_timeout 30s; -proxy_buffers 32 4k; -proxy_http_version 1.1; From 7060c0ab0dd74dd2bbea0fa7c695dac5ef83a44b Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 31 Aug 2022 01:55:38 -0400 Subject: [PATCH 129/168] Add access token system --- databases/_upgrade_private_11.sql | 18 ++++ package-lock.json | 1 + package.json | 1 + src/app.ts | 5 ++ src/config.ts | 9 ++ src/routes/generateToken.ts | 48 ++++++++++ src/routes/verifyToken.ts | 81 +++++++++++++++++ src/types/config.model.ts | 9 ++ src/utils/tokenUtils.ts | 144 ++++++++++++++++++++++++++++++ 9 files changed, 316 insertions(+) create mode 100644 databases/_upgrade_private_11.sql create mode 100644 src/routes/generateToken.ts create mode 100644 src/routes/verifyToken.ts create mode 100644 src/utils/tokenUtils.ts diff --git a/databases/_upgrade_private_11.sql b/databases/_upgrade_private_11.sql new file mode 100644 index 0000000..3dc80bc --- /dev/null +++ b/databases/_upgrade_private_11.sql @@ -0,0 +1,18 @@ +BEGIN TRANSACTION; + +CREATE TABLE IF NOT EXISTS "licenseKeys" ( + "licenseKey" TEXT NOT NULL PRIMARY KEY, + "time" INTEGER NOT NULL, + "type" TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS "oauthLicenseKeys" ( + "licenseKey" TEXT NOT NULL PRIMARY KEY, + "accessToken" TEXT NOT NULL, + "refreshToken" TEXT NOT NULL, + "expiresIn" INTEGER NOT NULL +); + +UPDATE "config" SET value = 11 WHERE key = 'version'; + +COMMIT; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 7b9530b..00e507e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "express": "^4.18.1", "express-promise-router": "^4.1.1", "express-rate-limit": "^6.4.0", + "form-data": "^4.0.0", "lodash": "^4.17.21", "pg": "^8.7.3", "rate-limit-redis": "^3.0.1", diff --git a/package.json b/package.json index 0f92f7c..29b7baa 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "express": "^4.18.1", "express-promise-router": "^4.1.1", "express-rate-limit": "^6.4.0", + "form-data": "^4.0.0", "lodash": "^4.17.21", "pg": "^8.7.3", "rate-limit-redis": "^3.0.1", diff --git a/src/app.ts b/src/app.ts index 92a94bf..6a0c7c8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -46,6 +46,8 @@ import { getChapterNames } from "./routes/getChapterNames"; import { getTopCategoryUsers } from "./routes/getTopCategoryUsers"; import { addUserAsTempVIP } from "./routes/addUserAsTempVIP"; import { addFeature } from "./routes/addFeature"; +import { generateTokenRequest } from "./routes/generateToken"; +import { verifyTokenRequest } from "./routes/verifyToken"; export function createServer(callback: () => void): Server { // Create a service (the app object is just a callback). @@ -194,6 +196,9 @@ function setupRoutes(router: Router) { router.post("/api/feature", addFeature); + router.get("/api/generateToken/:type", generateTokenRequest); + router.get("/api/verifyToken/", verifyTokenRequest); + if (config.postgres?.enabled) { router.get("/database", (req, res) => dumpDatabase(req, res, true)); router.get("/database.json", (req, res) => dumpDatabase(req, res, false)); diff --git a/src/config.ts b/src/config.ts index 352dee0..e07b5ca 100644 --- a/src/config.ts +++ b/src/config.ts @@ -135,6 +135,15 @@ addDefaults(config, { disableOfflineQueue: true, expiryTime: 24 * 60 * 60, getTimeout: 40 + }, + patreon: { + clientId: "", + clientSecret: "", + minPrice: 0, + redirectUri: "https://sponsor.ajay.app/api/generateToken/patreon" + }, + gumroad: { + productPermalinks: [] } }); loadFromEnv(config); diff --git a/src/routes/generateToken.ts b/src/routes/generateToken.ts new file mode 100644 index 0000000..ad33472 --- /dev/null +++ b/src/routes/generateToken.ts @@ -0,0 +1,48 @@ +import { Request, Response } from "express"; +import { config } from "../config"; +import { createAndSaveToken, TokenType } from "../utils/tokenUtils"; + + +interface GenerateTokenRequest extends Request { + query: { + code: string; + adminUserID?: string; + }, + params: { + type: TokenType; + } +} + +export async function generateTokenRequest(req: GenerateTokenRequest, res: Response): Promise { + const { query: { code, adminUserID }, params: { type } } = req; + + if (!code || !type) { + return res.status(400).send("Invalid request"); + } + + if (type === TokenType.patreon || (type === TokenType.local && adminUserID === config.adminUserID)) { + const licenseKey = await createAndSaveToken(type, code); + + if (licenseKey) { + return res.status(200).send(` +

+ Your access key: +

+

+ + ${licenseKey} + +

+

+ Copy this into the textbox in the other tab +

+ `); + } else { + return res.status(401).send(` +

+ Failed to generate an access key +

+ `); + } + } +} \ No newline at end of file diff --git a/src/routes/verifyToken.ts b/src/routes/verifyToken.ts new file mode 100644 index 0000000..9258f9d --- /dev/null +++ b/src/routes/verifyToken.ts @@ -0,0 +1,81 @@ +import axios from "axios"; +import { Request, Response } from "express"; +import { config } from "../config"; +import { privateDB } from "../databases/databases"; +import { Logger } from "../utils/logger"; +import { getPatreonIdentity, PatronStatus, refreshToken, TokenType } from "../utils/tokenUtils"; +import FormData from "form-data"; + +interface VerifyTokenRequest extends Request { + query: { + licenseKey: string; + } +} + +export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response): Promise { + const { query: { licenseKey } } = req; + + if (!licenseKey) { + return res.status(400).send("Invalid request"); + } + + const tokens = (await privateDB.prepare("get", `SELECT "accessToken", "refreshToken", "expiresIn" from "oauthLicenseKeys" WHERE "licenseKey" = ?` + , [licenseKey])) as {accessToken: string, refreshToken: string, expiresIn: number}; + if (tokens) { + const identity = await getPatreonIdentity(tokens.accessToken); + + if (tokens.expiresIn < 15 * 24 * 60 * 60) { + refreshToken(TokenType.patreon, licenseKey, tokens.refreshToken); + } + + if (identity) { + const membership = identity.included?.[0]?.attributes; + const allowed = !!membership && ((membership.patron_status === PatronStatus.active && membership.currently_entitled_amount_cents > 0) + || (membership.patron_status === PatronStatus.former && membership.campaign_lifetime_support_cents > 300)); + + return res.status(200).send({ + allowed + }); + } else { + return res.status(500); + } + } else { + // Check Local + const result = await privateDB.prepare("get", `SELECT "licenseKey" from "licenseKeys" WHERE "licenseKey" = ?`, [licenseKey]); + if (result) { + return res.status(200).send({ + allowed: true + }); + } else { + // Gumroad + return res.status(200).send({ + allowed: await checkAllGumroadProducts(licenseKey) + }); + } + + } +} + +async function checkAllGumroadProducts(licenseKey: string): Promise { + for (const link of config.gumroad.productPermalinks) { + try { + const formData = new FormData(); + formData.append("product_permalink", link); + formData.append("license_key", licenseKey); + + const result = await axios.request({ + url: "https://api.gumroad.com/v2/licenses/verify", + data: formData, + method: "POST", + headers: formData.getHeaders() + }); + + const allowed = result.status === 200 && result.data?.success; + if (allowed) return allowed; + } catch (e) { + Logger.error(`Gumroad fetch for ${link} failed: ${e}`); + } + } + + return false; +} \ No newline at end of file diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 1fb6d42..9a72713 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -65,6 +65,15 @@ export interface SBSConfig { dumpDatabase?: DumpDatabase; diskCacheURL: string; crons: CronJobOptions; + patreon: { + clientId: string, + clientSecret: string, + minPrice: number, + redirectUri: string + } + gumroad: { + productPermalinks: string[], + } } export interface WebhookConfig { diff --git a/src/utils/tokenUtils.ts b/src/utils/tokenUtils.ts new file mode 100644 index 0000000..7c5ad3c --- /dev/null +++ b/src/utils/tokenUtils.ts @@ -0,0 +1,144 @@ +import axios from "axios"; +import { config } from "../config"; +import { privateDB } from "../databases/databases"; +import { Logger } from "./logger"; +import FormData from "form-data"; +import { randomInt } from "node:crypto"; + +export enum TokenType { + patreon = "patreon", + local = "local", + gumroad = "gumroad" +} + +export enum PatronStatus { + active = "active_patron", + declined = "declined_patron", + former = "former_patron", +} + +export interface PatreonIdentityData { + included: Array<{ + attributes: { + currently_entitled_amount_cents: number, + campaign_lifetime_support_cents: number, + pledge_relationship_start: number, + patron_status: PatronStatus, + } + }> +} + +export async function createAndSaveToken(type: TokenType, code?: string): Promise { + switch(type) { + case TokenType.patreon: { + const domain = "https://www.patreon.com"; + try { + const formData = new FormData(); + formData.append("code", code); + formData.append("client_id", config.patreon.clientId); + formData.append("client_secret", config.patreon.clientSecret); + formData.append("grant_type", "authorization_code"); + formData.append("redirect_uri", config.patreon.redirectUri); + + const result = await axios.request({ + url: `${domain}/api/oauth2/token`, + data: formData, + method: "POST", + headers: formData.getHeaders() + }); + + if (result.status === 200) { + const licenseKey = generateToken(); + const time = Date.now(); + + await privateDB.prepare("run", `INSERT INTO "licenseKeys"("licenseKey", "time", "type") VALUES(?, ?, ?)`, [licenseKey, time, type]); + await privateDB.prepare("run", `INSERT INTO "oauthLicenseKeys"("licenseKey", "accessToken", "refreshToken", "expiresIn") VALUES(?, ?, ?, ?)` + , [licenseKey, result.data.access_token, result.data.refresh_token, result.data.expires_in]); + + + return licenseKey; + } + } catch (e) { + Logger.error(`token creation: ${e}`); + return null; + } + + break; + } + case TokenType.local: { + const licenseKey = generateToken(); + const time = Date.now(); + + await privateDB.prepare("run", `INSERT INTO "licenseKeys"("licenseKey", "time", "type") VALUES(?, ?, ?)`, [licenseKey, time, type]); + + return licenseKey; + } + } + + return null; +} + +export async function refreshToken(type: TokenType, licenseKey: string, refreshToken: string): Promise { + switch(type) { + case TokenType.patreon: { + try { + const formData = new FormData(); + formData.append("refreshToken", refreshToken); + formData.append("client_id", config.patreon.clientId); + formData.append("client_secret", config.patreon.clientSecret); + formData.append("grant_type", "refresh_token"); + + const domain = "https://www.patreon.com"; + const result = await axios.request({ + url: `${domain}/api/oauth2/token`, + data: formData, + method: "POST", + headers: formData.getHeaders() + }); + + if (result.status === 200) { + await privateDB.prepare("run", `UPDATE "oauthLicenseKeys" SET "accessToken" = ?, "refreshToken" = ?, "expiresIn" = ? WHERE "licenseKey" = ?` + , [result.data.access_token, result.data.refresh_token, result.data.expires_in, licenseKey]); + + return true; + } + } catch (e) { + Logger.error(`token refresh: ${e}`); + return false; + } + + break; + } + } + + return false; +} + +function generateToken(length = 40): string { + const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let result = ""; + + for (let i = 0; i < length; i++) { + result += charset[randomInt(charset.length)]; + } + + return result; +} + +export async function getPatreonIdentity(accessToken: string): Promise { + try { + const identityRequest = await axios.get(`https://www.patreon.com/api/oauth2/v2/identity?include=memberships&fields%5Bmember%5D=patron_status,currently_entitled_amount_cents,campaign_lifetime_support_cents,pledge_relationship_start`, { + headers: { + Authorization: `Bearer ${accessToken}` + } + }); + + if (identityRequest.status === 200) { + return identityRequest.data; + } + } catch (e) { + Logger.error(`identity request: ${e}`); + } + + return null; +} \ No newline at end of file From acec7e58e7a4ff5c9a344f5237349b8f93683c6d Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 1 Sep 2022 03:21:14 -0400 Subject: [PATCH 130/168] Add free chapters access --- src/app.ts | 2 +- src/routes/getUserInfo.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/app.ts b/src/app.ts index 6a0c7c8..b90daa9 100644 --- a/src/app.ts +++ b/src/app.ts @@ -197,7 +197,7 @@ function setupRoutes(router: Router) { router.post("/api/feature", addFeature); router.get("/api/generateToken/:type", generateTokenRequest); - router.get("/api/verifyToken/", verifyTokenRequest); + router.get("/api/verifyToken", verifyTokenRequest); if (config.postgres?.enabled) { router.get("/database", (req, res) => dumpDatabase(req, res, true)); diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index f9bf492..38c171d 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -8,6 +8,7 @@ import { getReputation } from "../utils/reputation"; import { Category, SegmentUUID } from "../types/segments.model"; import { config } from "../config"; import { canSubmit } from "../utils/permissions"; +import { oneOf } from "../utils/promise"; const maxRewardTime = config.maxRewardTimePerSegmentInSeconds; async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ minutesSaved: number, segmentCount: number }> { @@ -115,6 +116,13 @@ async function getPermissions(userID: HashedUserID): Promise { + return await oneOf([isUserVIP(userID), + (async () => (await getReputation(userID)) > 0)(), + (async () => !!(await db.prepare("get", `SELECT "timeSubmitted" FROM "sponsorTimes" WHERE "timeSubmitted" < 1590969600000 AND "userID" = ? LIMIT 1`, [userID], { useReplica: true })))() + ]); +} + type cases = Record const executeIfFunction = (f: any) => @@ -139,7 +147,8 @@ const dbGetValue = (userID: HashedUserID, property: string): Promise getReputation(userID), vip: () => isUserVIP(userID), lastSegmentID: () => dbGetLastSegmentForUser(userID), - permissions: () => getPermissions(userID) + permissions: () => getPermissions(userID), + freeChaptersAccess: () => getFreeChaptersAccess(userID) })("")(property); }; @@ -149,7 +158,7 @@ async function getUserInfo(req: Request, res: Response): Promise { const defaultProperties: string[] = ["userID", "userName", "minutesSaved", "segmentCount", "ignoredSegmentCount", "viewCount", "ignoredViewCount", "warnings", "warningReason", "reputation", "vip", "lastSegmentID"]; - const allProperties: string[] = [...defaultProperties, "banned", "permissions"]; + const allProperties: string[] = [...defaultProperties, "banned", "permissions", "freeChaptersAccess"]; let paramValues: string[] = req.query.values ? JSON.parse(req.query.values as string) : req.query.value From af2ef3d6a5ed0b57772307ab867795debafb1fcb Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 2 Sep 2022 02:15:22 -0400 Subject: [PATCH 131/168] Add product link --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index e07b5ca..8d3dbeb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -143,7 +143,7 @@ addDefaults(config, { redirectUri: "https://sponsor.ajay.app/api/generateToken/patreon" }, gumroad: { - productPermalinks: [] + productPermalinks: ["sponsorblock"] } }); loadFromEnv(config); From 420317a18cd9ce4b57090eec5b1c105516636887 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 2 Sep 2022 13:54:09 -0400 Subject: [PATCH 132/168] rename access to license --- src/routes/generateToken.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/generateToken.ts b/src/routes/generateToken.ts index ad33472..4c0771e 100644 --- a/src/routes/generateToken.ts +++ b/src/routes/generateToken.ts @@ -26,7 +26,7 @@ export async function generateTokenRequest(req: GenerateTokenRequest, res: Respo if (licenseKey) { return res.status(200).send(`

- Your access key: + Your license key:

@@ -40,7 +40,7 @@ export async function generateTokenRequest(req: GenerateTokenRequest, res: Respo } else { return res.status(401).send(`

- Failed to generate an access key + Failed to generate an license key

`); } From 6a61747573438c0977090db8aab3fc8e5fec7410 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 2 Sep 2022 14:55:20 -0400 Subject: [PATCH 133/168] Allow chapter submission --- src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.ts b/src/config.ts index 8d3dbeb..96f65de 100644 --- a/src/config.ts +++ b/src/config.ts @@ -20,7 +20,7 @@ addDefaults(config, { privateDBSchema: "./databases/_private.db.sql", readOnly: false, webhooks: [], - categoryList: ["sponsor", "selfpromo", "exclusive_access", "interaction", "intro", "outro", "preview", "music_offtopic", "filler", "poi_highlight"], + categoryList: ["sponsor", "selfpromo", "exclusive_access", "interaction", "intro", "outro", "preview", "music_offtopic", "filler", "poi_highlight", "chapter"], categorySupport: { sponsor: ["skip", "mute", "full"], selfpromo: ["skip", "mute", "full"], From b413795e45b883d4a7a782e3feecc61d32c0028d Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 2 Sep 2022 15:23:08 -0400 Subject: [PATCH 134/168] Change chapter name suggestion requirements --- src/routes/getChapterNames.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/getChapterNames.ts b/src/routes/getChapterNames.ts index 1b04e5b..af43dc2 100644 --- a/src/routes/getChapterNames.ts +++ b/src/routes/getChapterNames.ts @@ -22,7 +22,7 @@ export async function getChapterNames(req: Request, res: Response): Promise 0 OR ("views" > 100 AND "votes" >= 0)) AND "videoID" IN ( + WHERE ("locked" = 1 OR "votes" > 0 OR ("views" > 25 AND "votes" >= 0)) AND "videoID" IN ( SELECT "videoID" FROM "videoInfo" WHERE "channelID" = ? From f103a02a3447796b3702c2c7d7852438761054b4 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 2 Sep 2022 17:24:10 -0400 Subject: [PATCH 135/168] Don't check 80% for chapters --- src/routes/postSkipSegments.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 8ecd038..b911ca3 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -139,14 +139,16 @@ async function autoModerateSubmission(apiVideoInfo: APIVideoInfo, const segments = submission.segments; // map all times to float array - const allSegmentTimes = segments.map(segment => [parseFloat(segment.segment[0]), parseFloat(segment.segment[1])]); + const allSegmentTimes = segments.filter((s) => s.actionType !== ActionType.Chapter) + .map(segment => [parseFloat(segment.segment[0]), parseFloat(segment.segment[1])]); // add previous submissions by this user - const allSubmittedByUser = await db.prepare("all", `SELECT "startTime", "endTime" FROM "sponsorTimes" WHERE "userID" = ? AND "videoID" = ? AND "votes" > -1 AND "hidden" = 0`, [submission.userID, submission.videoID]); + const allSubmittedByUser = await db.prepare("all", `SELECT "startTime", "endTime" FROM "sponsorTimes" WHERE "userID" = ? AND "videoID" = ? AND "votes" > -1 AND "actionType" != 'chapter' AND "hidden" = 0` + , [submission.userID, submission.videoID]) as { startTime: string, endTime: string }[]; if (allSubmittedByUser) { //add segments the user has previously submitted - const allSubmittedTimes = allSubmittedByUser.map((segment: { startTime: string, endTime: string }) => [parseFloat(segment.startTime), parseFloat(segment.endTime)]); + const allSubmittedTimes = allSubmittedByUser.map((segment) => [parseFloat(segment.startTime), parseFloat(segment.endTime)]); allSegmentTimes.push(...allSubmittedTimes); } From 08ab7e816de4267286d9fe34b94e2d1061241da0 Mon Sep 17 00:00:00 2001 From: Ajay Date: Fri, 2 Sep 2022 17:48:47 -0400 Subject: [PATCH 136/168] Restrict changing chapters category --- src/routes/voteOnSponsorTime.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 0601715..641b8f2 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -221,11 +221,8 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i const segmentInfo = (await db.prepare("get", `SELECT "category", "actionType", "videoID", "hashedVideoID", "service", "userID", "locked" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID], { useReplica: true })) as {category: Category, actionType: ActionType, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID, locked: number}; - if (segmentInfo.actionType === ActionType.Full) { - return { status: 400, message: "Not allowed to change category of a full video segment" }; - } - if (segmentInfo.actionType === ActionType.Poi || category === "poi_highlight") { - return { status: 400, message: "Not allowed to change category for single point segments" }; + if (segmentInfo.actionType !== ActionType.Skip || category === "poi_highlight" || category === "chapter") { + return { status: 400, message: "Not allowed to change category of non skip segments" }; } if (!config.categoryList.includes(category)) { return { status: 400, message: "Category doesn't exist." }; From ec41102f07af3d9d2fe9a1d03c6a58f935bf01b1 Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 4 Sep 2022 22:36:26 -0400 Subject: [PATCH 137/168] Better chapter name error and increase min size --- src/routes/postSkipSegments.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index b911ca3..bca2062 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -227,11 +227,14 @@ async function checkInvalidFields(videoID: VideoID, userID: UserID, hashedUserID } if (typeof segmentPair.description !== "string" - || (segmentPair.actionType === ActionType.Chapter && segmentPair.description.length > 60 ) || (segmentPair.description.length !== 0 && segmentPair.actionType !== ActionType.Chapter)) { invalidFields.push("segment description"); } + if (segmentPair.actionType === ActionType.Chapter && segmentPair.description.length > 200) { + invalidFields.push("chapter name (too long)"); + } + const permission = await canSubmit(hashedUserID, segmentPair.category); if (!permission.canSubmit) { Logger.warn(`Rejecting submission due to lack of permissions for category ${segmentPair.category}: ${segmentPair.segment} ${hashedUserID} ${videoID} ${videoDurationParam} ${userAgent}`); From 9c72e20d1b04821480580f1003b3751433ff5485 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 7 Sep 2022 01:31:38 -0400 Subject: [PATCH 138/168] test for changing chapter category --- test/cases/voteOnSponsorTime.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index d189eff..c811294 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -293,6 +293,18 @@ describe("voteOnSponsorTime", () => { .catch(err => done(err)); }); + it("Should not able to change to chapter category", (done) => { + const UUID = "incorrect-category"; + postVoteCategory(randomID2, UUID, "chapter") + .then(async res => { + assert.strictEqual(res.status, 400); + const row = await getSegmentCategory(UUID); + assert.strictEqual(row.category, "sponsor"); + done(); + }) + .catch(err => done(err)); + }); + it("Should be able to change your vote for a category and it should add your vote to the database(segment unlocked, nextCatgeory unlocked)", (done) => { const UUID = "vote-uuid-4"; postVoteCategory(randomID2, UUID, "outro") From 252e2305f28e6864a16d5236fbc86946434e7e7c Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 7 Sep 2022 01:39:26 -0400 Subject: [PATCH 139/168] Fix undefined redis timeout breaking redis --- src/utils/redis.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/redis.ts b/src/utils/redis.ts index 06a89d7..ad4d6ef 100644 --- a/src/utils/redis.ts +++ b/src/utils/redis.ts @@ -33,9 +33,9 @@ if (config.redis?.enabled) { const get = client.get.bind(client); exportClient.get = (key) => new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(), config.redis.getTimeout); + const timeout = config.redis.getTimeout ? setTimeout(() => reject(), config.redis.getTimeout) : null; get(key).then((reply) => { - clearTimeout(timeout); + if (timeout !== null) clearTimeout(timeout); resolve(reply); }).catch((err) => reject(err)); }); From d1d7675a8caa4159a4e199ab0a633de5f7a6e8d5 Mon Sep 17 00:00:00 2001 From: Michael C Date: Fri, 2 Sep 2022 01:22:04 -0400 Subject: [PATCH 140/168] test fixes test fixes - fix timeout in redis (by @ajayyy) - allow "errors" in tempVIP test - remove duplicate warning in postSkipSegments - remove duplicate VIP in tempVIP - run tests against different user once tempVIP removed - fix typo in getHashCache fetching syntax and wording - use standard syntax in redisTest - fix spacing in getLockReason - typo in npm script name test cases - add getHashCache test case - add more tests to redisTest configuration - update config to use redis timeout - update docker-compose to use newest pinned version Co-Authored-By: Ajay Ramachandran --- ci.json | 4 +++- docker/docker-compose.yml | 4 ++-- package.json | 2 +- src/routes/dumpDatabase.ts | 6 +++--- src/utils/getHashCache.ts | 6 ++++-- src/utils/redis.ts | 7 +++++-- test/cases/getHashCache.ts | 31 ++++++++++++++++++++++++++++ test/cases/getLockReason.ts | 2 +- test/cases/postSkipSegments.ts | 1 - test/cases/redisTest.ts | 36 ++++++++++++++++++++++++++++++--- test/cases/tempVip.ts | 18 +++++------------ test/cases/voteOnSponsorTime.ts | 1 - 12 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 test/cases/getHashCache.ts diff --git a/ci.json b/ci.json index 7cdfe67..30e2f41 100644 --- a/ci.json +++ b/ci.json @@ -17,10 +17,12 @@ "port": 5432 }, "redis": { + "enabled": true, "socket": { "host": "localhost", "port": 6379 - } + }, + "expiryTime": 86400 }, "createDatabaseIfNotExist": true, "schemaFolder": "./databases", diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 7b6e1b5..acdd62b 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -2,7 +2,7 @@ version: '3' services: database: container_name: database - image: postgres:13 + image: postgres:14 env_file: - database.env volumes: @@ -12,7 +12,7 @@ services: restart: always redis: container_name: redis - image: redis:6.0 + image: redis:7.0 command: /usr/local/etc/redis/redis.conf volumes: - ./redis/redis.conf:/usr/local/etc/redis/redis.conf diff --git a/package.json b/package.json index 0f92f7c..ce1b24c 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "src/index.ts", "scripts": { "test": "npm run tsc && ts-node test/test.ts", - "test:coverate": "nyc npm run test", + "test:coverage": "nyc npm run test", "dev": "nodemon", "dev:bash": "nodemon -x 'npm test ; npm start'", "postgres:docker": "docker run --rm -p 5432:5432 -e POSTGRES_USER=ci_db_user -e POSTGRES_PASSWORD=ci_db_pass postgres:alpine", diff --git a/src/routes/dumpDatabase.ts b/src/routes/dumpDatabase.ts index 73557ad..ca579d6 100644 --- a/src/routes/dumpDatabase.ts +++ b/src/routes/dumpDatabase.ts @@ -5,7 +5,7 @@ import { config } from "../config"; import util from "util"; import fs from "fs"; import path from "path"; -import { ChildProcess, exec, ExecOptions, spawn } from "child_process"; +import { exec, ExecOptions } from "child_process"; const unlink = util.promisify(fs.unlink); const ONE_MINUTE = 1000 * 60; @@ -44,7 +44,7 @@ const credentials: ExecOptions = { PGPASSWORD: String(config.postgres.password), PGDATABASE: "sponsorTimes", } -} +}; interface TableDumpList { fileName: string; @@ -232,7 +232,7 @@ async function queueDump(): Promise { resolve(error ? stderr : stdout); }); - }) + }); dumpFiles.push({ fileName, diff --git a/src/utils/getHashCache.ts b/src/utils/getHashCache.ts index b3d0d7b..7ff0ef0 100644 --- a/src/utils/getHashCache.ts +++ b/src/utils/getHashCache.ts @@ -26,11 +26,13 @@ async function getFromRedis(key: HashedValue): Promise Logger.error(err)); + redis.set(redisKey, data).catch((err) => Logger.error(err)); return data as T & HashedValue; } \ No newline at end of file diff --git a/src/utils/redis.ts b/src/utils/redis.ts index 06a89d7..6ebdeb4 100644 --- a/src/utils/redis.ts +++ b/src/utils/redis.ts @@ -33,7 +33,7 @@ if (config.redis?.enabled) { const get = client.get.bind(client); exportClient.get = (key) => new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(), config.redis.getTimeout); + const timeout = config.redis.getTimeout ? setTimeout(() => reject(), config.redis.getTimeout) : null; get(key).then((reply) => { clearTimeout(timeout); resolve(reply); @@ -48,7 +48,10 @@ if (config.redis?.enabled) { .catch((err) => reject(err)) ); client.on("error", function(error) { - Logger.error(error); + Logger.error(`Redis Error: ${error}`); + }); + client.on("reconnect", () => { + Logger.info("Redis: trying to reconnect"); }); } diff --git a/test/cases/getHashCache.ts b/test/cases/getHashCache.ts new file mode 100644 index 0000000..40434e6 --- /dev/null +++ b/test/cases/getHashCache.ts @@ -0,0 +1,31 @@ +import { config } from "../../src/config"; +import { getHashCache } from "../../src/utils/getHashCache"; +import { shaHashKey } from "../../src/utils/redisKeys"; +import { getHash } from "../../src/utils/getHash"; +import redis from "../../src/utils/redis"; +import crypto from "crypto"; +import assert from "assert"; +import { setTimeout } from "timers/promises"; + +const genRandom = (bytes=8) => crypto.pseudoRandomBytes(bytes).toString("hex"); + +const rand1Hash = genRandom(24); +const rand1Hash_Key = getHash(rand1Hash, 1); +const rand1Hash_Result = getHash(rand1Hash); + +describe("getHashCache test", function() { + before(function() { + if (!config.redis?.enabled) this.skip(); + }); + it("Should set hashKey and be able to retreive", (done) => { + const redisKey = shaHashKey(rand1Hash_Key); + getHashCache(rand1Hash) + .then(() => setTimeout(50)) // add timeout for redis to complete async + .then(() => redis.get(redisKey)) + .then(result => { + assert.strictEqual(result, rand1Hash_Result); + done(); + }) + .catch(err => done(err === undefined ? "no set value" : err)); + }).timeout(5000); +}); \ No newline at end of file diff --git a/test/cases/getLockReason.ts b/test/cases/getLockReason.ts index 1bb0dfd..9bbb35b 100644 --- a/test/cases/getLockReason.ts +++ b/test/cases/getLockReason.ts @@ -31,7 +31,7 @@ describe("getLockReason", () => { }); after(async () => { - const deleteUserNameQuery = 'DELETE FROM "userNames" WHERE "userID" = ? AND "userName" = ? LIMIT 1'; + const deleteUserNameQuery = 'DELETE FROM "userNames" WHERE "userID" = ? AND "userName" = ?'; await db.prepare("run", deleteUserNameQuery, [vipUserID1, vipUserName1]); await db.prepare("run", deleteUserNameQuery, [vipUserID2, vipUserName2]); }); diff --git a/test/cases/postSkipSegments.ts b/test/cases/postSkipSegments.ts index 422ea88..1369282 100644 --- a/test/cases/postSkipSegments.ts +++ b/test/cases/postSkipSegments.ts @@ -93,7 +93,6 @@ describe("postSkipSegments", () => { db.prepare("run", insertWarningQuery, [warnUser01Hash, warnVip01Hash, 1, reason01, (now - 3601000)]); // User 2 db.prepare("run", insertWarningQuery, [warnUser02Hash, warnVip01Hash, 1, reason02, now]); - db.prepare("run", insertWarningQuery, [warnUser02Hash, warnVip01Hash, 1, reason02, now]); db.prepare("run", insertWarningQuery, [warnUser02Hash, warnVip01Hash, 1, reason02, (now - (warningExpireTime + 1000))]); db.prepare("run", insertWarningQuery, [warnUser02Hash, warnVip01Hash, 1, reason02, (now - (warningExpireTime + 2000))]); // User 3 diff --git a/test/cases/redisTest.ts b/test/cases/redisTest.ts index a6d21bb..d2f7be4 100644 --- a/test/cases/redisTest.ts +++ b/test/cases/redisTest.ts @@ -8,6 +8,8 @@ const genRandom = (bytes=8) => crypto.pseudoRandomBytes(bytes).toString("hex"); const randKey1 = genRandom(); const randValue1 = genRandom(); const randKey2 = genRandom(16); +const randKey3 = genRandom(); +const randValue3 = genRandom(); describe("redis test", function() { before(async function() { @@ -19,13 +21,41 @@ describe("redis test", function() { .then(res => { assert.strictEqual(res, randValue1); done(); - }).catch(err => assert.fail(err)); + }).catch(err => done(err)); }); it("Should not be able to get not stored value", (done) => { redis.get(randKey2) .then(res => { - if (res) assert.fail("Value should not be found"); + if (res) done("Value should not be found"); done(); - }).catch(err => assert.fail(err)); + }).catch(err => done(err)); + }); + it("Should be able to delete stored value", (done) => { + redis.del(randKey1) + .then(() => { + redis.get(randKey1) + .then(res => { + assert.strictEqual(res, null); + done(); + }).catch(err => done(err)); + }).catch(err => done(err)); + }); + it("Should be able to set expiring value", (done) => { + redis.setEx(randKey3, 8400, randValue3) + .then(() => { + redis.get(randKey3) + .then(res => { + assert.strictEqual(res, randValue3); + done(); + }).catch(err => done(err)); + }).catch(err => done(err)); + }); + it("Should continue when undefined value is fetched", (done) => { + const undefkey = `undefined.${genRandom()}`; + redis.get(undefkey) + .then(result => { + assert.ok(!result); // result should be falsy + done(); + }); }); }); \ No newline at end of file diff --git a/test/cases/tempVip.ts b/test/cases/tempVip.ts index 6f23913..5448598 100644 --- a/test/cases/tempVip.ts +++ b/test/cases/tempVip.ts @@ -6,14 +6,13 @@ import { client } from "../utils/httpClient"; import { db, privateDB } from "../../src/databases/databases"; import redis from "../../src/utils/redis"; import assert from "assert"; -import { Logger } from "../../src/utils/logger"; // helpers const getSegment = (UUID: string) => db.prepare("get", `SELECT "votes", "locked", "category" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID]); -const permVIP1 = "tempVipPermOne"; +const permVIP1 = "tempVip_permaVIPOne"; const publicPermVIP1 = getHash(permVIP1) as HashedUserID; -const permVIP2 = "tempVipPermOne"; +const permVIP2 = "tempVip_permaVIPTwo"; const publicPermVIP2 = getHash(permVIP2) as HashedUserID; const tempVIPOne = "tempVipTempOne"; @@ -51,15 +50,8 @@ const postVoteCategory = (userID: string, UUID: string, category: string) => cli category } }); -const checkUserVIP = async (publicID: HashedUserID) => { - try { - const reply = await redis.get(tempVIPKey(publicID)); - return reply; - } catch (e) { - Logger.error(e as string); - return false; - } -}; +const checkUserVIP = async (publicID: HashedUserID): Promise => + await redis.get(tempVIPKey(publicID)); describe("tempVIP test", function() { before(async function() { @@ -152,7 +144,7 @@ describe("tempVIP test", function() { .catch(err => done(err)); }); it("Should be able to remove tempVIP prematurely", (done) => { - addTempVIP("false", permVIP1, publicTempVIPOne, null) + addTempVIP("false", permVIP1, publicTempVIPOne) .then(async res => { assert.strictEqual(res.status, 200); const vip = await checkUserVIP(publicTempVIPOne); diff --git a/test/cases/voteOnSponsorTime.ts b/test/cases/voteOnSponsorTime.ts index d189eff..44ad178 100644 --- a/test/cases/voteOnSponsorTime.ts +++ b/test/cases/voteOnSponsorTime.ts @@ -78,7 +78,6 @@ describe("voteOnSponsorTime", () => { await db.prepare("run", insertWarningQuery, [warnUser01Hash, (now - 2000), warnVip01Hash, 1]); await db.prepare("run", insertWarningQuery, [warnUser01Hash, (now - 3601000), warnVip01Hash, 1]); await db.prepare("run", insertWarningQuery, [warnUser02Hash, now, warnVip01Hash, 1]); - await db.prepare("run", insertWarningQuery, [warnUser02Hash, now, warnVip01Hash, 1]); await db.prepare("run", insertWarningQuery, [warnUser02Hash, (now - (warningExpireTime + 1000)), warnVip01Hash, 1]); await db.prepare("run", insertWarningQuery, [warnUser02Hash, (now - (warningExpireTime + 2000)), warnVip01Hash, 1]); From bd7dfc63ff19dd4eb44dc38f1c546ea637a3e4b8 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 7 Sep 2022 20:01:11 -0400 Subject: [PATCH 141/168] Add eslint rules for dealing with promises --- .eslintrc.js | 14 ++++ src/app.ts | 94 ++++++++++++------------ src/cronjob/downvoteSegmentArchiveJob.ts | 2 +- src/databases/Postgres.ts | 2 +- src/index.ts | 2 +- src/middleware/requestRateLimit.ts | 1 + src/routes/dumpDatabase.ts | 1 + src/routes/getTopCategoryUsers.ts | 2 +- src/routes/getTopUsers.ts | 2 +- src/routes/postSkipSegments.ts | 6 +- src/routes/verifyToken.ts | 2 +- src/routes/voteOnSponsorTime.ts | 4 +- src/utils/queryCacher.ts | 2 +- src/utils/redis.ts | 4 +- src/utils/youtubeApi.ts | 4 +- 15 files changed, 79 insertions(+), 63 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 55ec41e..c290178 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -29,4 +29,18 @@ module.exports = { "semi": "warn", "no-console": "warn" }, + overrides: [ + { + files: ["src/**/*.ts"], + + parserOptions: { + project: ["./tsconfig.json"], + }, + + rules: { + "@typescript-eslint/no-misused-promises": "warn", + "@typescript-eslint/no-floating-promises" : "warn" + } + }, + ], }; diff --git a/src/app.ts b/src/app.ts index b90daa9..e5502c4 100644 --- a/src/app.ts +++ b/src/app.ts @@ -85,18 +85,18 @@ function setupRoutes(router: Router) { } //add the get function - router.get("/api/getVideoSponsorTimes", oldGetVideoSponsorTimes); + router.get("/api/getVideoSponsorTimes", void oldGetVideoSponsorTimes); //add the oldpost function - router.get("/api/postVideoSponsorTimes", oldSubmitSponsorTimes); - router.post("/api/postVideoSponsorTimes", oldSubmitSponsorTimes); + router.get("/api/postVideoSponsorTimes", void oldSubmitSponsorTimes); + router.post("/api/postVideoSponsorTimes", void oldSubmitSponsorTimes); //add the skip segments functions - router.get("/api/skipSegments", getSkipSegments); - router.post("/api/skipSegments", postSkipSegments); + router.get("/api/skipSegments", void getSkipSegments); + router.post("/api/skipSegments", void postSkipSegments); // add the privacy protecting skip segments functions - router.get("/api/skipSegments/:prefix", getSkipSegmentsByHash); + router.get("/api/skipSegments/:prefix", void getSkipSegmentsByHash); //voting endpoint router.get("/api/voteOnSponsorTime", ...voteEndpoints); @@ -107,106 +107,106 @@ function setupRoutes(router: Router) { router.post("/api/viewedVideoSponsorTime", ...viewEndpoints); //To set your username for the stats view - router.post("/api/setUsername", setUsername); + router.post("/api/setUsername", void setUsername); //get what username this user has - router.get("/api/getUsername", getUsername); + router.get("/api/getUsername", void getUsername); //Endpoint used to hide a certain user's data - router.post("/api/shadowBanUser", shadowBanUser); + router.post("/api/shadowBanUser", void shadowBanUser); //Endpoint used to make a user a VIP user with special privileges - router.post("/api/addUserAsVIP", addUserAsVIP); + router.post("/api/addUserAsVIP", void addUserAsVIP); //Endpoint to add a user as a temporary VIP - router.post("/api/addUserAsTempVIP", addUserAsTempVIP); + router.post("/api/addUserAsTempVIP", void addUserAsTempVIP); //Gets all the views added up for one userID //Useful to see how much one user has contributed - router.get("/api/getViewsForUser", getViewsForUser); + router.get("/api/getViewsForUser", void getViewsForUser); //Gets all the saved time added up (views * sponsor length) for one userID //Useful to see how much one user has contributed //In minutes - router.get("/api/getSavedTimeForUser", getSavedTimeForUser); + router.get("/api/getSavedTimeForUser", void getSavedTimeForUser); - router.get("/api/getTopUsers", getTopUsers); - router.get("/api/getTopCategoryUsers", getTopCategoryUsers); + router.get("/api/getTopUsers", void getTopUsers); + router.get("/api/getTopCategoryUsers", void getTopCategoryUsers); //send out totals //send the total submissions, total views and total minutes saved - router.get("/api/getTotalStats", getTotalStats); + router.get("/api/getTotalStats", void getTotalStats); - router.get("/api/getUserInfo", getUserInfo); - router.get("/api/userInfo", getUserInfo); + router.get("/api/getUserInfo", void getUserInfo); + router.get("/api/userInfo", void getUserInfo); //send out a formatted time saved total - router.get("/api/getDaysSavedFormatted", getDaysSavedFormatted); + router.get("/api/getDaysSavedFormatted", void getDaysSavedFormatted); //submit video to lock categories - router.post("/api/noSegments", postLockCategories); - router.post("/api/lockCategories", postLockCategories); + router.post("/api/noSegments", void postLockCategories); + router.post("/api/lockCategories", void postLockCategories); - router.delete("/api/noSegments", deleteLockCategoriesEndpoint); - router.delete("/api/lockCategories", deleteLockCategoriesEndpoint); + router.delete("/api/noSegments", void deleteLockCategoriesEndpoint); + router.delete("/api/lockCategories", void deleteLockCategoriesEndpoint); //get if user is a vip - router.get("/api/isUserVIP", getIsUserVIP); + router.get("/api/isUserVIP", void getIsUserVIP); //sent user a warning - router.post("/api/warnUser", postWarning); + router.post("/api/warnUser", void postWarning); //get if user is a vip - router.post("/api/segmentShift", postSegmentShift); + router.post("/api/segmentShift", void postSegmentShift); //get segment info - router.get("/api/segmentInfo", getSegmentInfo); + router.get("/api/segmentInfo", void getSegmentInfo); //clear cache as VIP - router.post("/api/clearCache", postClearCache); + router.post("/api/clearCache", void postClearCache); //purge all segments for VIP - router.post("/api/purgeAllSegments", postPurgeAllSegments); + router.post("/api/purgeAllSegments", void postPurgeAllSegments); - router.post("/api/unlistedVideo", addUnlistedVideo); + router.post("/api/unlistedVideo", void addUnlistedVideo); // get userID from username - router.get("/api/userID", getUserID); + router.get("/api/userID", void getUserID); // get lock categores from userID - router.get("/api/lockCategories", getLockCategories); + router.get("/api/lockCategories", void getLockCategories); // get privacy protecting lock categories functions - router.get("/api/lockCategories/:prefix", getLockCategoriesByHash); + router.get("/api/lockCategories/:prefix", void getLockCategoriesByHash); // get all segments that match a search - router.get("/api/searchSegments", getSearchSegments); + router.get("/api/searchSegments", void getSearchSegments); // autocomplete chapter names - router.get("/api/chapterNames", getChapterNames); + router.get("/api/chapterNames", void getChapterNames); // get status - router.get("/api/status/:value", getStatus); - router.get("/api/status", getStatus); + router.get("/api/status/:value", void getStatus); + router.get("/api/status", void getStatus); - router.get("/api/youtubeApiProxy", youtubeApiProxy); + router.get("/api/youtubeApiProxy", void youtubeApiProxy); // get user category stats - router.get("/api/userStats", getUserStats); + router.get("/api/userStats", void getUserStats); - router.get("/api/lockReason", getLockReason); + router.get("/api/lockReason", void getLockReason); - router.post("/api/feature", addFeature); + router.post("/api/feature", void addFeature); - router.get("/api/generateToken/:type", generateTokenRequest); - router.get("/api/verifyToken", verifyTokenRequest); + router.get("/api/generateToken/:type", void generateTokenRequest); + router.get("/api/verifyToken", void verifyTokenRequest); if (config.postgres?.enabled) { - router.get("/database", (req, res) => dumpDatabase(req, res, true)); - router.get("/database.json", (req, res) => dumpDatabase(req, res, false)); - router.get("/database/*", downloadFile); + router.get("/database", (req, res) => void dumpDatabase(req, res, true)); + router.get("/database.json", (req, res) => void dumpDatabase(req, res, false)); + router.get("/database/*", void downloadFile); router.use("/download", express.static(appExportPath)); } else { router.get("/database.db", function (req: Request, res: Response) { res.sendFile("./databases/sponsorTimes.db", { root: "./" }); }); } -} +} \ No newline at end of file diff --git a/src/cronjob/downvoteSegmentArchiveJob.ts b/src/cronjob/downvoteSegmentArchiveJob.ts index 74d72ac..5ff2282 100644 --- a/src/cronjob/downvoteSegmentArchiveJob.ts +++ b/src/cronjob/downvoteSegmentArchiveJob.ts @@ -57,7 +57,7 @@ export const archiveDownvoteSegment = async (dayLimit: number, voteLimit: number const DownvoteSegmentArchiveJob = new CronJob( jobConfig?.schedule || "0 0 * * * 0", - () => archiveDownvoteSegment(jobConfig?.timeThresholdInDays, jobConfig?.voteThreshold) + () => void archiveDownvoteSegment(jobConfig?.timeThresholdInDays, jobConfig?.voteThreshold) ); if (serverConfig?.crons?.enabled && jobConfig && !jobConfig.schedule) { diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index ff97c6f..07879cb 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -177,7 +177,7 @@ export class Postgres implements IDatabase { ); } - client.end(); + client.end().catch(err => Logger.error(`closing db (postgres): ${err}`)); } private async upgradeDB(fileNamePrefix: string, schemaFolder: string) { diff --git a/src/index.ts b/src/index.ts index e538741..e34882a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,4 +27,4 @@ async function init() { }).setTimeout(15000); } -init(); +init().catch((err) => Logger.error(err)); \ No newline at end of file diff --git a/src/middleware/requestRateLimit.ts b/src/middleware/requestRateLimit.ts index 853738e..f7e40cc 100644 --- a/src/middleware/requestRateLimit.ts +++ b/src/middleware/requestRateLimit.ts @@ -22,6 +22,7 @@ export function rateLimitMiddleware(limitConfig: RateLimitConfig, getUserID?: (r keyGenerator: (req) => { return getHash(getIP(req), 1); }, + // eslint-disable-next-line @typescript-eslint/no-misused-promises handler: async (req, res, next) => { if (getUserID === undefined || !await isUserVIP(await getHashCache(getUserID(req)))) { return res.status(limitConfig.statusCode).send(limitConfig.message); diff --git a/src/routes/dumpDatabase.ts b/src/routes/dumpDatabase.ts index ca579d6..c3dc2ba 100644 --- a/src/routes/dumpDatabase.ts +++ b/src/routes/dumpDatabase.ts @@ -75,6 +75,7 @@ function removeOutdatedDumps(exportPath: string): Promise { }, {}); // read files in export directory + // eslint-disable-next-line @typescript-eslint/no-misused-promises fs.readdir(exportPath, async (err: any, files: string[]) => { if (err) Logger.error(err); if (err) return resolve(); diff --git a/src/routes/getTopCategoryUsers.ts b/src/routes/getTopCategoryUsers.ts index b9558dd..305bcf5 100644 --- a/src/routes/getTopCategoryUsers.ts +++ b/src/routes/getTopCategoryUsers.ts @@ -4,7 +4,7 @@ import { config } from "../config"; import { Request, Response } from "express"; const MILLISECONDS_IN_MINUTE = 60000; -const getTopCategoryUsersWithCache = createMemoryCache(generateTopCategoryUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); +const getTopCategoryUsersWithCache = createMemoryCache(void generateTopCategoryUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); const maxRewardTimePerSegmentInSeconds = config.maxRewardTimePerSegmentInSeconds ?? 86400; interface DBSegment { diff --git a/src/routes/getTopUsers.ts b/src/routes/getTopUsers.ts index 34d6bc5..d600f19 100644 --- a/src/routes/getTopUsers.ts +++ b/src/routes/getTopUsers.ts @@ -4,7 +4,7 @@ import { config } from "../config"; import { Request, Response } from "express"; const MILLISECONDS_IN_MINUTE = 60000; -const getTopUsersWithCache = createMemoryCache(generateTopUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); +const getTopUsersWithCache = createMemoryCache(void generateTopUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); const maxRewardTimePerSegmentInSeconds = config.maxRewardTimePerSegmentInSeconds ?? 86400; async function generateTopUsersStats(sortBy: string, categoryStatsEnabled = false) { diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index bca2062..755b5b7 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -76,7 +76,7 @@ async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID: sendWebhookNotification(userID, videoID, UUID, userSubmissionCountRow.submissionCount, data, { submissionStart: startTime, submissionEnd: endTime, - }, segmentInfo); + }, segmentInfo).catch(Logger.error); // If it is a first time submission // Then send a notification to discord @@ -395,7 +395,7 @@ async function updateDataIfVideoDurationChange(videoID: VideoID, service: Servic await db.prepare("run", `UPDATE "sponsorTimes" SET "hidden" = 1 WHERE "UUID" = ?`, [submission.UUID]); } lockedCategoryList = []; - deleteLockCategories(videoID, null, null, service); + deleteLockCategories(videoID, null, null, service).catch(Logger.error); } return { @@ -614,7 +614,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise(fetchFromDB: (values: U[]) => Pr if (valuesToBeFetched.length > 0) { data = await fetchFromDB(valuesToBeFetched); - new Promise(() => { + void new Promise(() => { const newResults: Record = {}; for (const item of data) { const splitValue = (item as unknown as Record)[splitKey]; diff --git a/src/utils/redis.ts b/src/utils/redis.ts index 90a56d1..8e8c6d0 100644 --- a/src/utils/redis.ts +++ b/src/utils/redis.ts @@ -28,7 +28,7 @@ let exportClient: RedisSB = { if (config.redis?.enabled) { Logger.info("Connected to redis"); const client = createClient(config.redis); - client.connect(); + void client.connect(); // void as we don't care about the promise exportClient = client as RedisSB; const get = client.get.bind(client); @@ -40,7 +40,7 @@ if (config.redis?.enabled) { }).catch((err) => reject(err)); }); exportClient.increment = (key) => new Promise((resolve, reject) => - client.multi() + void client.multi() .incr(key) .expire(key, 60) .exec() diff --git a/src/utils/youtubeApi.ts b/src/utils/youtubeApi.ts index 5f3dfe7..6580f5e 100644 --- a/src/utils/youtubeApi.ts +++ b/src/utils/youtubeApi.ts @@ -39,8 +39,8 @@ export class YouTubeAPI { } const apiResult = data as APIVideoData; DiskCache.set(cacheKey, apiResult) - .catch((err: any) => Logger.warn(err)) - .then(() => Logger.debug(`YouTube API: video information cache set for: ${videoID}`)); + .then(() => Logger.debug(`YouTube API: video information cache set for: ${videoID}`)) + .catch((err: any) => Logger.warn(err)); return { err: false, data: apiResult }; } else { From ae95f7e3eaadfe984a57ae8ca305af16bf6e1c1f Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 7 Sep 2022 22:10:48 -0400 Subject: [PATCH 142/168] Only give up on replica for last retry --- src/databases/Postgres.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 07879cb..670c591 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -103,6 +103,8 @@ export class Postgres implements IDatabase { let tries = 0; let lastPool: Pool = null; + const maxTries = () => (lastPool === this.pool + ? this.config.postgres.maxTries : this.config.postgresReadOnly.maxTries); do { tries++; @@ -133,14 +135,13 @@ export class Postgres implements IDatabase { if (lastPool === this.pool) { // Only applies if it is get or all request options.forceReplica = true; - } else if (lastPool === this.poolRead) { + } else if (lastPool === this.poolRead && maxTries() - tries <= 1) { options.useReplica = false; } Logger.error(`prepare (postgres) try ${tries}: ${err}`); } - } while (this.isReadQuery(type) && tries < (lastPool === this.pool - ? this.config.postgres.maxTries : this.config.postgresReadOnly.maxTries)); + } while (this.isReadQuery(type) && tries < maxTries()); throw new Error(`prepare (postgres): ${type} ${query} failed after ${tries} tries`); } From c1e5f0e117e7fad871d744a97c0b5019bab86a17 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 7 Sep 2022 22:15:44 -0400 Subject: [PATCH 143/168] Fix void being used in wrong context --- src/app.ts | 96 ++++++++++++++++--------------- src/routes/getTopCategoryUsers.ts | 3 +- src/routes/getTopUsers.ts | 3 +- 3 files changed, 53 insertions(+), 49 deletions(-) diff --git a/src/app.ts b/src/app.ts index e5502c4..f68507e 100644 --- a/src/app.ts +++ b/src/app.ts @@ -75,6 +75,7 @@ export function createServer(callback: () => void): Server { return app.listen(config.port, callback); } +/* eslint-disable @typescript-eslint/no-misused-promises */ function setupRoutes(router: Router) { // Rate limit endpoint lists const voteEndpoints: RequestHandler[] = [voteOnSponsorTime]; @@ -85,18 +86,18 @@ function setupRoutes(router: Router) { } //add the get function - router.get("/api/getVideoSponsorTimes", void oldGetVideoSponsorTimes); + router.get("/api/getVideoSponsorTimes", oldGetVideoSponsorTimes); //add the oldpost function - router.get("/api/postVideoSponsorTimes", void oldSubmitSponsorTimes); - router.post("/api/postVideoSponsorTimes", void oldSubmitSponsorTimes); + router.get("/api/postVideoSponsorTimes", oldSubmitSponsorTimes); + router.post("/api/postVideoSponsorTimes", oldSubmitSponsorTimes); //add the skip segments functions - router.get("/api/skipSegments", void getSkipSegments); - router.post("/api/skipSegments", void postSkipSegments); + router.get("/api/skipSegments", getSkipSegments); + router.post("/api/skipSegments", postSkipSegments); // add the privacy protecting skip segments functions - router.get("/api/skipSegments/:prefix", void getSkipSegmentsByHash); + router.get("/api/skipSegments/:prefix", getSkipSegmentsByHash); //voting endpoint router.get("/api/voteOnSponsorTime", ...voteEndpoints); @@ -107,106 +108,107 @@ function setupRoutes(router: Router) { router.post("/api/viewedVideoSponsorTime", ...viewEndpoints); //To set your username for the stats view - router.post("/api/setUsername", void setUsername); + router.post("/api/setUsername", setUsername); //get what username this user has - router.get("/api/getUsername", void getUsername); + router.get("/api/getUsername", getUsername); //Endpoint used to hide a certain user's data - router.post("/api/shadowBanUser", void shadowBanUser); + router.post("/api/shadowBanUser", shadowBanUser); //Endpoint used to make a user a VIP user with special privileges - router.post("/api/addUserAsVIP", void addUserAsVIP); + router.post("/api/addUserAsVIP", addUserAsVIP); //Endpoint to add a user as a temporary VIP - router.post("/api/addUserAsTempVIP", void addUserAsTempVIP); + router.post("/api/addUserAsTempVIP", addUserAsTempVIP); //Gets all the views added up for one userID //Useful to see how much one user has contributed - router.get("/api/getViewsForUser", void getViewsForUser); + router.get("/api/getViewsForUser", getViewsForUser); //Gets all the saved time added up (views * sponsor length) for one userID //Useful to see how much one user has contributed //In minutes - router.get("/api/getSavedTimeForUser", void getSavedTimeForUser); + router.get("/api/getSavedTimeForUser", getSavedTimeForUser); - router.get("/api/getTopUsers", void getTopUsers); - router.get("/api/getTopCategoryUsers", void getTopCategoryUsers); + router.get("/api/getTopUsers", getTopUsers); + router.get("/api/getTopCategoryUsers", getTopCategoryUsers); //send out totals //send the total submissions, total views and total minutes saved - router.get("/api/getTotalStats", void getTotalStats); + router.get("/api/getTotalStats", getTotalStats); - router.get("/api/getUserInfo", void getUserInfo); - router.get("/api/userInfo", void getUserInfo); + router.get("/api/getUserInfo", getUserInfo); + router.get("/api/userInfo", getUserInfo); //send out a formatted time saved total - router.get("/api/getDaysSavedFormatted", void getDaysSavedFormatted); + router.get("/api/getDaysSavedFormatted", getDaysSavedFormatted); //submit video to lock categories - router.post("/api/noSegments", void postLockCategories); - router.post("/api/lockCategories", void postLockCategories); + router.post("/api/noSegments", postLockCategories); + router.post("/api/lockCategories", postLockCategories); - router.delete("/api/noSegments", void deleteLockCategoriesEndpoint); - router.delete("/api/lockCategories", void deleteLockCategoriesEndpoint); + router.delete("/api/noSegments", deleteLockCategoriesEndpoint); + router.delete("/api/lockCategories", deleteLockCategoriesEndpoint); //get if user is a vip - router.get("/api/isUserVIP", void getIsUserVIP); + router.get("/api/isUserVIP", getIsUserVIP); //sent user a warning - router.post("/api/warnUser", void postWarning); + router.post("/api/warnUser", postWarning); //get if user is a vip - router.post("/api/segmentShift", void postSegmentShift); + router.post("/api/segmentShift", postSegmentShift); //get segment info - router.get("/api/segmentInfo", void getSegmentInfo); + router.get("/api/segmentInfo", getSegmentInfo); //clear cache as VIP - router.post("/api/clearCache", void postClearCache); + router.post("/api/clearCache", postClearCache); //purge all segments for VIP - router.post("/api/purgeAllSegments", void postPurgeAllSegments); + router.post("/api/purgeAllSegments", postPurgeAllSegments); - router.post("/api/unlistedVideo", void addUnlistedVideo); + router.post("/api/unlistedVideo", addUnlistedVideo); // get userID from username - router.get("/api/userID", void getUserID); + router.get("/api/userID", getUserID); // get lock categores from userID - router.get("/api/lockCategories", void getLockCategories); + router.get("/api/lockCategories", getLockCategories); // get privacy protecting lock categories functions - router.get("/api/lockCategories/:prefix", void getLockCategoriesByHash); + router.get("/api/lockCategories/:prefix", getLockCategoriesByHash); // get all segments that match a search - router.get("/api/searchSegments", void getSearchSegments); + router.get("/api/searchSegments", getSearchSegments); // autocomplete chapter names - router.get("/api/chapterNames", void getChapterNames); + router.get("/api/chapterNames", getChapterNames); // get status - router.get("/api/status/:value", void getStatus); - router.get("/api/status", void getStatus); + router.get("/api/status/:value", getStatus); + router.get("/api/status", getStatus); - router.get("/api/youtubeApiProxy", void youtubeApiProxy); + router.get("/api/youtubeApiProxy", youtubeApiProxy); // get user category stats - router.get("/api/userStats", void getUserStats); + router.get("/api/userStats", getUserStats); - router.get("/api/lockReason", void getLockReason); + router.get("/api/lockReason", getLockReason); - router.post("/api/feature", void addFeature); + router.post("/api/feature", addFeature); - router.get("/api/generateToken/:type", void generateTokenRequest); - router.get("/api/verifyToken", void verifyTokenRequest); + router.get("/api/generateToken/:type", generateTokenRequest); + router.get("/api/verifyToken", verifyTokenRequest); if (config.postgres?.enabled) { - router.get("/database", (req, res) => void dumpDatabase(req, res, true)); - router.get("/database.json", (req, res) => void dumpDatabase(req, res, false)); - router.get("/database/*", void downloadFile); + router.get("/database", (req, res) => dumpDatabase(req, res, true)); + router.get("/database.json", (req, res) => dumpDatabase(req, res, false)); + router.get("/database/*", downloadFile); router.use("/download", express.static(appExportPath)); } else { router.get("/database.db", function (req: Request, res: Response) { res.sendFile("./databases/sponsorTimes.db", { root: "./" }); }); } -} \ No newline at end of file +} +/* eslint-enable @typescript-eslint/no-misused-promises */ \ No newline at end of file diff --git a/src/routes/getTopCategoryUsers.ts b/src/routes/getTopCategoryUsers.ts index 305bcf5..1197e6a 100644 --- a/src/routes/getTopCategoryUsers.ts +++ b/src/routes/getTopCategoryUsers.ts @@ -4,7 +4,8 @@ import { config } from "../config"; import { Request, Response } from "express"; const MILLISECONDS_IN_MINUTE = 60000; -const getTopCategoryUsersWithCache = createMemoryCache(void generateTopCategoryUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); +// eslint-disable-next-line @typescript-eslint/no-misused-promises +const getTopCategoryUsersWithCache = createMemoryCache(generateTopCategoryUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); const maxRewardTimePerSegmentInSeconds = config.maxRewardTimePerSegmentInSeconds ?? 86400; interface DBSegment { diff --git a/src/routes/getTopUsers.ts b/src/routes/getTopUsers.ts index d600f19..5a2b86c 100644 --- a/src/routes/getTopUsers.ts +++ b/src/routes/getTopUsers.ts @@ -4,7 +4,8 @@ import { config } from "../config"; import { Request, Response } from "express"; const MILLISECONDS_IN_MINUTE = 60000; -const getTopUsersWithCache = createMemoryCache(void generateTopUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); +// eslint-disable-next-line @typescript-eslint/no-misused-promises +const getTopUsersWithCache = createMemoryCache(generateTopUsersStats, config.getTopUsersCacheTimeMinutes * MILLISECONDS_IN_MINUTE); const maxRewardTimePerSegmentInSeconds = config.maxRewardTimePerSegmentInSeconds ?? 86400; async function generateTopUsersStats(sortBy: string, categoryStatsEnabled = false) { From 072324f0ab51b7dcfe644a8eb1194a41d6afa9c3 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 7 Sep 2022 22:35:32 -0400 Subject: [PATCH 144/168] Add permission request to image build action --- .github/workflows/docker-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 024df87..ce37e1e 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -19,6 +19,8 @@ on: jobs: build_container: runs-on: ubuntu-latest + permissions: + packages: write steps: - name: Checkout uses: actions/checkout@v3 From 9be9d05dbebbad4dc7231fe0d0cccc6b5626fac1 Mon Sep 17 00:00:00 2001 From: Michael C Date: Wed, 7 Sep 2022 23:43:14 -0400 Subject: [PATCH 145/168] add redis process time and add timeout clause --- src/routes/getStatus.ts | 27 +++++++++++++++++++++------ test/cases/getStatus.ts | 12 ++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/routes/getStatus.ts b/src/routes/getStatus.ts index 7f764f5..a3dd206 100644 --- a/src/routes/getStatus.ts +++ b/src/routes/getStatus.ts @@ -3,25 +3,40 @@ import { Logger } from "../utils/logger"; import { Request, Response } from "express"; import os from "os"; import redis from "../utils/redis"; +import { setTimeout } from "timers/promises"; export async function getStatus(req: Request, res: Response): Promise { const startTime = Date.now(); let value = req.params.value as string[] | string; value = Array.isArray(value) ? value[0] : value; + let processTime, redisProcessTime = -1; + const timeoutPromise = async (): Promise => { + await setTimeout(5000); + return "timeout"; + }; try { - const dbVersion = (await db.prepare("get", "SELECT key, value FROM config where key = ?", ["version"])).value; + const dbVersion = await Promise.race([db.prepare("get", "SELECT key, value FROM config where key = ?", ["version"]), timeoutPromise()]) + .then(e => { + if (e === "timeout") return e; + processTime = Date.now() - startTime; + return e.value; + }); let statusRequests: unknown = 0; - try { - const numberRequests = await redis.increment("statusRequest"); - statusRequests = numberRequests?.[0]; - } catch (error) { } // eslint-disable-line no-empty + const numberRequests = await Promise.race([redis.increment("statusRequest"), timeoutPromise()]) + .then(e => { + if (e === "timeout") return [-1]; + redisProcessTime = Date.now() - startTime; + return e; + }); + statusRequests = numberRequests?.[0]; const statusValues: Record = { uptime: process.uptime(), commit: (global as any).HEADCOMMIT || "unknown", db: Number(dbVersion), startTime, - processTime: Date.now() - startTime, + processTime, + redisProcessTime, loadavg: os.loadavg().slice(1), // only return 5 & 15 minute load average statusRequests, hostname: os.hostname() diff --git a/test/cases/getStatus.ts b/test/cases/getStatus.ts index abf22b3..7f4ffaf 100644 --- a/test/cases/getStatus.ts +++ b/test/cases/getStatus.ts @@ -110,4 +110,16 @@ describe("getStatus", () => { }) .catch(err => done(err)); }); + + it("Should be able to get redis latency", function (done) { + if (!config.redis?.enabled) this.skip(); + client.get(endpoint) + .then(res => { + assert.strictEqual(res.status, 200); + const data = res.data; + assert.ok(data.redisProcessTime >= 0); + done(); + }) + .catch(err => done(err)); + }); }); From 1e66a2e57a922a8880c8be1fcbedd6390c7dbf59 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 8 Sep 2022 15:02:56 -0400 Subject: [PATCH 146/168] Add option to disable fallback after failure --- src/config.ts | 3 ++- src/databases/Postgres.ts | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/config.ts b/src/config.ts index 96f65de..39debed 100644 --- a/src/config.ts +++ b/src/config.ts @@ -88,7 +88,8 @@ addDefaults(config, { readTimeout: 250, max: 10, idleTimeoutMillis: 10000, - maxTries: 3 + maxTries: 3, + fallbackOnFail: true }, dumpDatabase: { enabled: false, diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 670c591..715ecb7 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -148,8 +148,10 @@ export class Postgres implements IDatabase { private getPool(type: string, options: QueryOption): Pool { const readAvailable = this.poolRead && options.useReplica && this.isReadQuery(type); - const ignroreReadDueToFailure = this.lastPoolReadFail > Date.now() - 1000 * 30; - const readDueToFailure = this.lastPoolFail > Date.now() - 1000 * 30; + const ignroreReadDueToFailure = this.config.postgresReadOnly.fallbackOnFail + && this.lastPoolReadFail > Date.now() - 1000 * 30; + const readDueToFailure = this.config.postgresReadOnly.fallbackOnFail + && this.lastPoolFail > Date.now() - 1000 * 30; if (readAvailable && !ignroreReadDueToFailure && (options.forceReplica || readDueToFailure || Math.random() > 1 / (this.config.postgresReadOnly.weight + 1))) { return this.poolRead; From e84957a2c81843210231c8dcc1b4308db8b6e3b8 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 8 Sep 2022 15:04:33 -0400 Subject: [PATCH 147/168] commit missing file --- src/types/config.model.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 9a72713..4e89a02 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -15,6 +15,7 @@ export interface CustomPostgresConfig extends PoolConfig { export interface CustomPostgresReadOnlyConfig extends CustomPostgresConfig { weight: number; readTimeout: number; + fallbackOnFail: boolean; } export interface SBSConfig { From 00dae6d6a18a7f5bc29f948501f27746d5c63683 Mon Sep 17 00:00:00 2001 From: Michael C Date: Thu, 8 Sep 2022 16:34:39 -0400 Subject: [PATCH 148/168] switch to reused promiseOrTimeout --- src/routes/getStatus.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/routes/getStatus.ts b/src/routes/getStatus.ts index a3dd206..1295790 100644 --- a/src/routes/getStatus.ts +++ b/src/routes/getStatus.ts @@ -3,30 +3,30 @@ import { Logger } from "../utils/logger"; import { Request, Response } from "express"; import os from "os"; import redis from "../utils/redis"; -import { setTimeout } from "timers/promises"; +import { promiseOrTimeout } from "../utils/promise"; export async function getStatus(req: Request, res: Response): Promise { const startTime = Date.now(); let value = req.params.value as string[] | string; value = Array.isArray(value) ? value[0] : value; let processTime, redisProcessTime = -1; - const timeoutPromise = async (): Promise => { - await setTimeout(5000); - return "timeout"; - }; try { - const dbVersion = await Promise.race([db.prepare("get", "SELECT key, value FROM config where key = ?", ["version"]), timeoutPromise()]) + const dbVersion = await promiseOrTimeout(db.prepare("get", "SELECT key, value FROM config where key = ?", ["version"]), 5000) .then(e => { - if (e === "timeout") return e; processTime = Date.now() - startTime; return e.value; + }) + .catch(e => { + Logger.error(`status: SQL query timed out: ${e}`); }); let statusRequests: unknown = 0; - const numberRequests = await Promise.race([redis.increment("statusRequest"), timeoutPromise()]) + const numberRequests = await promiseOrTimeout(redis.increment("statusRequest"), 5000) .then(e => { - if (e === "timeout") return [-1]; redisProcessTime = Date.now() - startTime; return e; + }).catch(e => { + Logger.error(`status: redis increment timed out ${e}`); + return [-1]; }); statusRequests = numberRequests?.[0]; From 0e3eeece0167b5a99f0649c53da31d732c7dbfff Mon Sep 17 00:00:00 2001 From: Michael C Date: Thu, 8 Sep 2022 16:38:05 -0400 Subject: [PATCH 149/168] return -1 for unknown dbVersion --- src/routes/getStatus.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/getStatus.ts b/src/routes/getStatus.ts index 1295790..b817450 100644 --- a/src/routes/getStatus.ts +++ b/src/routes/getStatus.ts @@ -18,6 +18,7 @@ export async function getStatus(req: Request, res: Response): Promise }) .catch(e => { Logger.error(`status: SQL query timed out: ${e}`); + return -1; }); let statusRequests: unknown = 0; const numberRequests = await promiseOrTimeout(redis.increment("statusRequest"), 5000) From 94ca2914600111aad8a1caabb835e08ba87229aa Mon Sep 17 00:00:00 2001 From: Ajay Date: Sun, 11 Sep 2022 12:22:57 -0400 Subject: [PATCH 150/168] Don't cut off early if last retry --- src/databases/Postgres.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 715ecb7..389257a 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -113,7 +113,7 @@ export class Postgres implements IDatabase { pendingQueries.push(savePromiseState(lastPool.query({ text: query, values: params }))); const currentPromises = [...pendingQueries]; - if (options.useReplica) currentPromises.push(savePromiseState(timeoutPomise(this.config.postgresReadOnly.readTimeout))); + if (options.useReplica && maxTries() - tries > 1) currentPromises.push(savePromiseState(timeoutPomise(this.config.postgresReadOnly.readTimeout))); const queryResult = await nextFulfilment(currentPromises); switch (type) { From 2ecf4b3a9ba8c0d7c90dec91f7623ffb29d75d23 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 12 Sep 2022 10:59:26 -0400 Subject: [PATCH 151/168] Fix category votes to use category support to judge when not allowed --- src/routes/voteOnSponsorTime.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/voteOnSponsorTime.ts b/src/routes/voteOnSponsorTime.ts index 6205c6b..5b00aac 100644 --- a/src/routes/voteOnSponsorTime.ts +++ b/src/routes/voteOnSponsorTime.ts @@ -221,8 +221,8 @@ async function categoryVote(UUID: SegmentUUID, userID: UserID, isVIP: boolean, i const segmentInfo = (await db.prepare("get", `SELECT "category", "actionType", "videoID", "hashedVideoID", "service", "userID", "locked" FROM "sponsorTimes" WHERE "UUID" = ?`, [UUID], { useReplica: true })) as {category: Category, actionType: ActionType, videoID: VideoID, hashedVideoID: VideoIDHash, service: Service, userID: UserID, locked: number}; - if (segmentInfo.actionType !== ActionType.Skip || category === "poi_highlight" || category === "chapter") { - return { status: 400, message: "Not allowed to change category of non skip segments" }; + if (!config.categorySupport[category]?.includes(segmentInfo.actionType) || segmentInfo.actionType === ActionType.Full) { + return { status: 400, message: `Not allowed to change to ${category} when for segment of type ${segmentInfo.actionType}`}; } if (!config.categoryList.includes(category)) { return { status: 400, message: "Category doesn't exist." }; From 3c0903326714b04aae2fc3915ea8a542817e72e0 Mon Sep 17 00:00:00 2001 From: Michael C Date: Wed, 14 Sep 2022 00:33:52 -0400 Subject: [PATCH 152/168] add innerTube API, types and tests --- src/types/innerTubeApi.model.ts | 23 +++++++++++++++ src/utils/innerTubeAPI.ts | 29 +++++++++++++++++++ test/cases/innerTubeApi.ts | 51 +++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 src/types/innerTubeApi.model.ts create mode 100644 src/utils/innerTubeAPI.ts create mode 100644 test/cases/innerTubeApi.ts diff --git a/src/types/innerTubeApi.model.ts b/src/types/innerTubeApi.model.ts new file mode 100644 index 0000000..3fad700 --- /dev/null +++ b/src/types/innerTubeApi.model.ts @@ -0,0 +1,23 @@ +export interface innerTubeVideoDetails { + "videoId": string, + "title": string, + "lengthSeconds": string, // yes, don't ask. + "channelId": string, + "isOwnerViewing": boolean, + "shortDescription": string, + "isCrawlable": boolean, + "thumbnail": { + "thumbnails": [{ + "url": string, + "width": number, + "height": number + } + ] + }, + "allowRatings": boolean, + "viewCount": string, // yes, don't ask + "author": string, + "isPrivate": boolean, + "isUnpluggedCorpus": boolean, + "isLiveContent": boolean +} \ No newline at end of file diff --git a/src/utils/innerTubeAPI.ts b/src/utils/innerTubeAPI.ts new file mode 100644 index 0000000..4ff5797 --- /dev/null +++ b/src/utils/innerTubeAPI.ts @@ -0,0 +1,29 @@ +import axios from "axios"; +import { innerTubeVideoDetails } from "../types/innerTubeApi.model"; + +export async function getPlayerData(videoID: string): Promise { + // start subrequest + const url = "https://www.youtube.com/youtubei/v1/player"; + const data = { + context: { + client: { + clientName: "WEB", + clientVersion: "2.20211129.09.00" + } + }, + videoId: videoID + }; + const result = await axios.post(url, data, { + timeout: 3500 + }); + if (result.status === 200) { + return result.data.videoDetails; + } else { + return Promise.reject(result.status); + } +} + +export const getLength = (videoID: string): Promise => + getPlayerData(videoID) + .then(pData => Number(pData.lengthSeconds)) + .catch(err => err); \ No newline at end of file diff --git a/test/cases/innerTubeApi.ts b/test/cases/innerTubeApi.ts new file mode 100644 index 0000000..6ef3eb1 --- /dev/null +++ b/test/cases/innerTubeApi.ts @@ -0,0 +1,51 @@ +import { config } from "../../src/config"; +import assert from "assert"; +// import { innerTubeVideoDetails } from "../../src/types/innerTubeApi.model"; +import { YouTubeAPI } from "../../src/utils/youtubeApi"; +import * as innerTube from "../../src/utils/innerTubeAPI"; +import { partialDeepEquals } from "../utils/partialDeepEquals"; + + +const videoID = "dQw4w9WgXcQ"; +const expected = { // partial type of innerTubeVideoDetails + videoId: videoID, + title: "Rick Astley - Never Gonna Give You Up (Official Music Video)", + lengthSeconds: "212", + channelId: "UCuAXFkgsw1L7xaCfnd5JJOw", + isOwnerViewing: false, + isCrawlable: true, + allowRatings: true, + author: "Rick Astley", + isPrivate: false, + isUnpluggedCorpus: false, + isLiveContent: false +}; +const currentViews = 1284257550; + +describe("innertube API test", function() { + it("should be able to get innerTube details", async () => { + const result = await innerTube.getPlayerData(videoID); + assert.ok(partialDeepEquals(result, expected)); + }); + it("Should have more views than current", async () => { + const result = await innerTube.getPlayerData(videoID); + assert.ok(Number(result.viewCount) >= currentViews); + }); + it("Should have the same video duration from both endpoints", async () => { + const playerData = await innerTube.getPlayerData(videoID); + const length = await innerTube.getLength(videoID); + assert.equal(Number(playerData.lengthSeconds), length); + }); + it("Should have equivalent response from NewLeaf", async function () { + if (!config.newLeafURLs || config.newLeafURLs.length <= 0 || config.newLeafURLs[0] == "placeholder") this.skip(); + const itResponse = await innerTube.getPlayerData(videoID); + const newLeafResponse = await YouTubeAPI.listVideos(videoID, true); + // validate videoID + assert.strictEqual(itResponse.videoId, videoID); + assert.strictEqual(newLeafResponse.data?.videoId, videoID); + // validate description + assert.strictEqual(itResponse.shortDescription, newLeafResponse.data?.description); + // validate authorId + assert.strictEqual(itResponse.channelId, newLeafResponse.data?.authorId); + }); +}); \ No newline at end of file From 62a9b0eddd4c21fd36fb15806512cfcb9d9f7742 Mon Sep 17 00:00:00 2001 From: Michael C Date: Thu, 15 Sep 2022 17:02:33 -0400 Subject: [PATCH 153/168] add innerTube as primary videoInfo endpoint - drop videoInfo.genreUrl since it's always empty - bump ts target to ES2021 for Promise.any - fix mocks to return err: false - get maxResThumbnail from static endpoint --- DatabaseSchema.md | 1 - databases/_upgrade_sponsorTimes_34.sql | 7 +++ src/routes/addUserAsTempVIP.ts | 14 ++---- src/routes/postSkipSegments.ts | 70 +++++++++----------------- src/routes/voteOnSponsorTime.ts | 27 +++++----- src/types/innerTubeApi.model.ts | 3 +- src/utils/getVideoDetails.ts | 59 ++++++++++++++++++++++ src/utils/innerTubeAPI.ts | 7 +-- src/utils/isUserTempVIP.ts | 12 ++--- src/utils/youtubeApi.ts | 5 +- test/cases/getChapterNames.ts | 6 +-- test/cases/innerTubeApi.ts | 15 +++--- test/cases/postSkipSegments.ts | 1 - test/youtubeMock.ts | 10 ++-- tsconfig.json | 2 +- 15 files changed, 130 insertions(+), 109 deletions(-) create mode 100644 databases/_upgrade_sponsorTimes_34.sql create mode 100644 src/utils/getVideoDetails.ts diff --git a/DatabaseSchema.md b/DatabaseSchema.md index ab11b53..535e667 100644 --- a/DatabaseSchema.md +++ b/DatabaseSchema.md @@ -126,7 +126,6 @@ | channelID | TEXT | not null | | title | TEXT | not null | | published | REAL | not null | -| genreUrl | TEXT | not null | | index | field | | -- | :--: | diff --git a/databases/_upgrade_sponsorTimes_34.sql b/databases/_upgrade_sponsorTimes_34.sql new file mode 100644 index 0000000..e8d41a4 --- /dev/null +++ b/databases/_upgrade_sponsorTimes_34.sql @@ -0,0 +1,7 @@ +BEGIN TRANSACTION; + +ALTER TABLE "videoInfo" DROP COLUMN "genreUrl"; + +UPDATE "config" SET value = 34 WHERE key = 'version'; + +COMMIT; \ No newline at end of file diff --git a/src/routes/addUserAsTempVIP.ts b/src/routes/addUserAsTempVIP.ts index 3001a0e..774d661 100644 --- a/src/routes/addUserAsTempVIP.ts +++ b/src/routes/addUserAsTempVIP.ts @@ -1,7 +1,5 @@ import { VideoID } from "../types/segments.model"; -import { YouTubeAPI } from "../utils/youtubeApi"; -import { APIVideoInfo } from "../types/youtubeApi.model"; -import { config } from "../config"; +import { getVideoDetails } from "../utils/getVideoDetails"; import { getHashCache } from "../utils/getHashCache"; import { privateDB } from "../databases/databases"; import { Request, Response } from "express"; @@ -20,15 +18,11 @@ interface AddUserAsTempVIPRequest extends Request { } } -function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise { - return (config.newLeafURLs) ? YouTubeAPI.listVideos(videoID, ignoreCache) : null; -} - const getChannelInfo = async (videoID: VideoID): Promise<{id: string | null, name: string | null }> => { - const videoInfo = await getYouTubeVideoInfo(videoID); + const videoInfo = await getVideoDetails(videoID); return { - id: videoInfo?.data?.authorId, - name: videoInfo?.data?.author + id: videoInfo?.authorId, + name: videoInfo?.authorName }; }; diff --git a/src/routes/postSkipSegments.ts b/src/routes/postSkipSegments.ts index 755b5b7..97cd3c7 100644 --- a/src/routes/postSkipSegments.ts +++ b/src/routes/postSkipSegments.ts @@ -1,7 +1,7 @@ import { config } from "../config"; import { Logger } from "../utils/logger"; import { db, privateDB } from "../databases/databases"; -import { getMaxResThumbnail, YouTubeAPI } from "../utils/youtubeApi"; +import { getMaxResThumbnail } from "../utils/youtubeApi"; import { getSubmissionUUID } from "../utils/getSubmissionUUID"; import { getHash } from "../utils/getHash"; import { getHashCache } from "../utils/getHashCache"; @@ -13,7 +13,6 @@ import { ActionType, Category, IncomingSegment, IPAddress, SegmentUUID, Service, import { deleteLockCategories } from "./deleteLockCategories"; import { QueryCacher } from "../utils/queryCacher"; import { getReputation } from "../utils/reputation"; -import { APIVideoData, APIVideoInfo } from "../types/youtubeApi.model"; import { HashedUserID, UserID } from "../types/user.model"; import { isUserVIP } from "../utils/isUserVIP"; import { isUserTempVIP } from "../utils/isUserTempVIP"; @@ -22,6 +21,7 @@ import { getService } from "../utils/getService"; import axios from "axios"; import { vote } from "./voteOnSponsorTime"; import { canSubmit } from "../utils/permissions"; +import { getVideoDetails, videoDetails } from "../utils/getVideoDetails"; type CheckResult = { pass: boolean, @@ -35,7 +35,7 @@ const CHECK_PASS: CheckResult = { errorCode: 0 }; -async function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: APIVideoData, { submissionStart, submissionEnd }: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) { +async function sendWebhookNotification(userID: string, videoID: string, UUID: string, submissionCount: number, youtubeData: videoDetails, { submissionStart, submissionEnd }: { submissionStart: number; submissionEnd: number; }, segmentInfo: any) { const row = await db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ?`, [userID]); const userName = row !== undefined ? row.userName : null; @@ -48,7 +48,7 @@ async function sendWebhookNotification(userID: string, videoID: string, UUID: st "video": { "id": videoID, "title": youtubeData?.title, - "thumbnail": getMaxResThumbnail(youtubeData) || null, + "thumbnail": getMaxResThumbnail(videoID), "url": `https://www.youtube.com/watch?v=${videoID}`, }, "submission": { @@ -64,16 +64,13 @@ async function sendWebhookNotification(userID: string, videoID: string, UUID: st }); } -async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID: string, UUID: string, segmentInfo: any, service: Service) { - if (apiVideoInfo && service == Service.YouTube) { +async function sendWebhooks(apiVideoDetails: videoDetails, userID: string, videoID: string, UUID: string, segmentInfo: any, service: Service) { + if (apiVideoDetails && service == Service.YouTube) { const userSubmissionCountRow = await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ?`, [userID]); - const { data, err } = apiVideoInfo; - if (err) return; - const startTime = parseFloat(segmentInfo.segment[0]); const endTime = parseFloat(segmentInfo.segment[1]); - sendWebhookNotification(userID, videoID, UUID, userSubmissionCountRow.submissionCount, data, { + sendWebhookNotification(userID, videoID, UUID, userSubmissionCountRow.submissionCount, apiVideoDetails, { submissionStart: startTime, submissionEnd: endTime, }, segmentInfo).catch(Logger.error); @@ -84,7 +81,7 @@ async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID: axios.post(config.discordFirstTimeSubmissionsWebhookURL, { embeds: [{ - title: data?.title, + title: apiVideoDetails.title, url: `https://www.youtube.com/watch?v=${videoID}&t=${(parseInt(startTime.toFixed(0)) - 2)}s#requiredSegment=${UUID}`, description: `Submission ID: ${UUID}\ \n\nTimestamp: \ @@ -95,7 +92,7 @@ async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID: name: userID, }, thumbnail: { - url: getMaxResThumbnail(data) || "", + url: getMaxResThumbnail(videoID), }, }], }) @@ -120,18 +117,10 @@ async function sendWebhooks(apiVideoInfo: APIVideoInfo, userID: string, videoID: // Looks like this was broken for no defined youtube key - fixed but IMO we shouldn't return // false for a pass - it was confusing and lead to this bug - any use of this function in // the future could have the same problem. -async function autoModerateSubmission(apiVideoInfo: APIVideoInfo, +async function autoModerateSubmission(apiVideoDetails: videoDetails, submission: { videoID: VideoID; userID: UserID; segments: IncomingSegment[], service: Service, videoDuration: number }) { - - const apiVideoDuration = (apiVideoInfo: APIVideoInfo) => { - if (!apiVideoInfo) return undefined; - const { err, data } = apiVideoInfo; - // return undefined if API error - if (err) return undefined; - return data?.lengthSeconds; - }; // get duration from API - const apiDuration = apiVideoDuration(apiVideoInfo); + const apiDuration = apiVideoDetails.duration; // if API fail or returns 0, get duration from client const duration = apiDuration || submission.videoDuration; // return false on undefined or 0 @@ -165,14 +154,6 @@ async function autoModerateSubmission(apiVideoInfo: APIVideoInfo, return false; } -function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise { - if (config.newLeafURLs !== null) { - return YouTubeAPI.listVideos(videoID, ignoreCache); - } else { - return null; - } -} - async function checkUserActiveWarning(userID: string): Promise { const MILLISECONDS_IN_HOUR = 3600000; const now = Date.now(); @@ -345,10 +326,10 @@ async function checkEachSegmentValid(rawIP: IPAddress, paramUserID: UserID, user return CHECK_PASS; } -async function checkByAutoModerator(videoID: any, userID: any, segments: Array, service:string, apiVideoInfo: APIVideoInfo, videoDuration: number): Promise { +async function checkByAutoModerator(videoID: any, userID: any, segments: Array, service:string, apiVideoDetails: videoDetails, videoDuration: number): Promise { // Auto moderator check if (service == Service.YouTube) { - const autoModerateResult = await autoModerateSubmission(apiVideoInfo, { userID, videoID, segments, service, videoDuration }); + const autoModerateResult = await autoModerateSubmission(apiVideoDetails, { userID, videoID, segments, service, videoDuration }); if (autoModerateResult) { return { pass: false, @@ -377,12 +358,13 @@ async function updateDataIfVideoDurationChange(videoID: VideoID, service: Servic const videoDurationChanged = (videoDuration: number) => videoDuration != 0 && previousSubmissions.length > 0 && !previousSubmissions.some((e) => Math.abs(videoDuration - e.videoDuration) < 2); - let apiVideoInfo: APIVideoInfo = null; + let apiVideoDetails: videoDetails = null; if (service == Service.YouTube) { // Don't use cache if we don't know the video duration, or the client claims that it has changed - apiVideoInfo = await getYouTubeVideoInfo(videoID, !videoDurationParam || previousSubmissions.length === 0 || videoDurationChanged(videoDurationParam)); + const ignoreCache = !videoDurationParam || previousSubmissions.length === 0 || videoDurationChanged(videoDurationParam); + apiVideoDetails = await getVideoDetails(videoID, ignoreCache); } - const apiVideoDuration = apiVideoInfo?.data?.lengthSeconds as VideoDuration; + const apiVideoDuration = apiVideoDetails?.duration as VideoDuration; if (!videoDurationParam || (apiVideoDuration && Math.abs(videoDurationParam - apiVideoDuration) > 2)) { // If api duration is far off, take that one instead (it is only precise to seconds, not millis) videoDuration = apiVideoDuration || 0 as VideoDuration; @@ -400,7 +382,7 @@ async function updateDataIfVideoDurationChange(videoID: VideoID, service: Servic return { videoDuration, - apiVideoInfo, + apiVideoDetails, lockedCategoryList }; } @@ -501,10 +483,6 @@ export async function postSkipSegments(req: Request, res: Response): Promise { - return config.newLeafURLs ? YouTubeAPI.listVideos(videoID, ignoreCache) : null; -} - const videoDurationChanged = (segmentDuration: number, APIDuration: number) => (APIDuration > 0 && Math.abs(segmentDuration - APIDuration) > 2); async function updateSegmentVideoDuration(UUID: SegmentUUID) { const { videoDuration, videoID, service } = await db.prepare("get", `select "videoDuration", "videoID", "service" from "sponsorTimes" where "UUID" = ?`, [UUID]); - let apiVideoInfo: APIVideoInfo = null; + let apiVideoDetails: videoDetails = null; if (service == Service.YouTube) { // don't use cache since we have no information about the video length - apiVideoInfo = await getYouTubeVideoInfo(videoID); + apiVideoDetails = await getVideoDetails(videoID); } - const apiVideoDuration = apiVideoInfo?.data?.lengthSeconds as VideoDuration; + const apiVideoDuration = apiVideoDetails?.duration as VideoDuration; if (videoDurationChanged(videoDuration, apiVideoDuration)) { Logger.info(`Video duration changed for ${videoID} from ${videoDuration} to ${apiVideoDuration}`); await db.prepare("run", `UPDATE "sponsorTimes" SET "videoDuration" = ? WHERE "UUID" = ?`, [apiVideoDuration, UUID]); @@ -74,12 +70,12 @@ async function updateSegmentVideoDuration(UUID: SegmentUUID) { async function checkVideoDuration(UUID: SegmentUUID) { const { videoID, service } = await db.prepare("get", `select "videoID", "service" from "sponsorTimes" where "UUID" = ?`, [UUID]); - let apiVideoInfo: APIVideoInfo = null; + let apiVideoDetails: videoDetails = null; if (service == Service.YouTube) { // don't use cache since we have no information about the video length - apiVideoInfo = await getYouTubeVideoInfo(videoID, true); + apiVideoDetails = await getVideoDetails(videoID, true); } - const apiVideoDuration = apiVideoInfo?.data?.lengthSeconds as VideoDuration; + const apiVideoDuration = apiVideoDetails?.duration as VideoDuration; // if no videoDuration return early if (isNaN(apiVideoDuration)) return; // fetch latest submission @@ -129,7 +125,8 @@ async function sendWebhooks(voteData: VoteData) { } if (config.newLeafURLs !== null) { - const { err, data } = await YouTubeAPI.listVideos(submissionInfoRow.videoID); + const videoID = submissionInfoRow.videoID; + const { err, data } = await YouTubeAPI.listVideos(videoID); if (err) return; const isUpvote = voteData.incrementAmount > 0; @@ -141,8 +138,8 @@ async function sendWebhooks(voteData: VoteData) { "video": { "id": submissionInfoRow.videoID, "title": data?.title, - "url": `https://www.youtube.com/watch?v=${submissionInfoRow.videoID}`, - "thumbnail": getMaxResThumbnail(data) || null, + "url": `https://www.youtube.com/watch?v=${videoID}`, + "thumbnail": getMaxResThumbnail(videoID), }, "submission": { "UUID": voteData.UUID, @@ -187,7 +184,7 @@ async function sendWebhooks(voteData: VoteData) { `${getVoteAuthor(userSubmissionCountRow.submissionCount, voteData.isTempVIP, voteData.isVIP, voteData.isOwnSubmission)}${voteData.row.locked ? " (Locked)" : ""}`, }, "thumbnail": { - "url": getMaxResThumbnail(data) || "", + "url": getMaxResThumbnail(videoID), }, }], }) diff --git a/src/types/innerTubeApi.model.ts b/src/types/innerTubeApi.model.ts index 3fad700..28e46c8 100644 --- a/src/types/innerTubeApi.model.ts +++ b/src/types/innerTubeApi.model.ts @@ -19,5 +19,6 @@ export interface innerTubeVideoDetails { "author": string, "isPrivate": boolean, "isUnpluggedCorpus": boolean, - "isLiveContent": boolean + "isLiveContent": boolean, + "publishDate": string } \ No newline at end of file diff --git a/src/utils/getVideoDetails.ts b/src/utils/getVideoDetails.ts new file mode 100644 index 0000000..aa5636a --- /dev/null +++ b/src/utils/getVideoDetails.ts @@ -0,0 +1,59 @@ +import { config } from "../config"; +import { innerTubeVideoDetails } from "../types/innerTubeApi.model"; +import { APIVideoData } from "../types/youtubeApi.model"; +import { YouTubeAPI } from "../utils/youtubeApi"; +import { getPlayerData } from "../utils/innerTubeAPI"; + +export interface videoDetails { + videoId: string, + duration: number, + authorId: string, + authorName: string, + title: string, + published: number, + thumbnails: { + url: string, + width: number, + height: number, + }[] +} + +const convertFromInnerTube = (input: innerTubeVideoDetails): videoDetails => ({ + videoId: input.videoId, + duration: Number(input.lengthSeconds), + authorId: input.channelId, + authorName: input.author, + title: input.title, + published: new Date(input.publishDate).getTime()/1000, + thumbnails: input.thumbnail.thumbnails +}); + +const convertFromNewLeaf = (input: APIVideoData): videoDetails => ({ + videoId: input.videoId, + duration: input.lengthSeconds, + authorId: input.authorId, + authorName: input.author, + title: input.title, + published: input.published, + thumbnails: input.videoThumbnails +}); + +async function newLeafWrapper(videoId: string, ignoreCache: boolean) { + const result = await YouTubeAPI.listVideos(videoId, ignoreCache); + return result?.data ?? Promise.reject(); +} + +export function getVideoDetails(videoId: string, ignoreCache = false): Promise { + if (!config.newLeafURLs) { + return getPlayerData(videoId) + .then(data => convertFromInnerTube(data)); + } + return Promise.any([ + newLeafWrapper(videoId, ignoreCache) + .then(videoData => convertFromNewLeaf(videoData)), + getPlayerData(videoId) + .then(data => convertFromInnerTube(data)) + ]).catch(() => { + return null; + }); +} \ No newline at end of file diff --git a/src/utils/innerTubeAPI.ts b/src/utils/innerTubeAPI.ts index 4ff5797..c8f8696 100644 --- a/src/utils/innerTubeAPI.ts +++ b/src/utils/innerTubeAPI.ts @@ -21,9 +21,4 @@ export async function getPlayerData(videoID: string): Promise => - getPlayerData(videoID) - .then(pData => Number(pData.lengthSeconds)) - .catch(err => err); \ No newline at end of file +} \ No newline at end of file diff --git a/src/utils/isUserTempVIP.ts b/src/utils/isUserTempVIP.ts index a2758bc..44bc649 100644 --- a/src/utils/isUserTempVIP.ts +++ b/src/utils/isUserTempVIP.ts @@ -1,19 +1,13 @@ import redis from "../utils/redis"; import { tempVIPKey } from "../utils/redisKeys"; import { HashedUserID } from "../types/user.model"; -import { YouTubeAPI } from "../utils/youtubeApi"; -import { APIVideoInfo } from "../types/youtubeApi.model"; import { VideoID } from "../types/segments.model"; -import { config } from "../config"; import { Logger } from "./logger"; - -function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise { - return config.newLeafURLs ? YouTubeAPI.listVideos(videoID, ignoreCache) : null; -} +import { getVideoDetails } from "./getVideoDetails"; export const isUserTempVIP = async (hashedUserID: HashedUserID, videoID: VideoID): Promise => { - const apiVideoInfo = await getYouTubeVideoInfo(videoID); - const channelID = apiVideoInfo?.data?.authorId; + const apiVideoDetails = await getVideoDetails(videoID); + const channelID = apiVideoDetails?.authorId; try { const reply = await redis.get(tempVIPKey(hashedUserID)); return reply && reply == channelID; diff --git a/src/utils/youtubeApi.ts b/src/utils/youtubeApi.ts index 6580f5e..a227d71 100644 --- a/src/utils/youtubeApi.ts +++ b/src/utils/youtubeApi.ts @@ -52,6 +52,5 @@ export class YouTubeAPI { } } -export function getMaxResThumbnail(apiInfo: APIVideoData): string | void { - return apiInfo?.videoThumbnails?.find((elem) => elem.quality === "maxres")?.second__originalUrl; -} \ No newline at end of file +export const getMaxResThumbnail = (videoID: string): string => + `https://i.ytimg.com/vi/${videoID}/maxresdefault.jpg`; \ No newline at end of file diff --git a/test/cases/getChapterNames.ts b/test/cases/getChapterNames.ts index 0905b47..c73c923 100644 --- a/test/cases/getChapterNames.ts +++ b/test/cases/getChapterNames.ts @@ -19,9 +19,9 @@ if (db instanceof Postgres) { await db.prepare("run", query, [chapterNamesVid1, 70, 75, 2, 0, "chapterNamesVid-2", "testman", 0, 50, "chapter", "chapter", "YouTube", 0, 0, 0, "A different one"]); await db.prepare("run", query, [chapterNamesVid1, 71, 76, 2, 0, "chapterNamesVid-3", "testman", 0, 50, "chapter", "chapter", "YouTube", 0, 0, 0, "Something else"]); - await db.prepare("run", `INSERT INTO "videoInfo" ("videoID", "channelID", "title", "published", "genreUrl") - SELECT ?, ?, ?, ?, ?`, [ - chapterNamesVid1, chapterChannelID, "", 0, "" + await db.prepare("run", `INSERT INTO "videoInfo" ("videoID", "channelID", "title", "published") + SELECT ?, ?, ?, ?`, [ + chapterNamesVid1, chapterChannelID, "", 0 ]); }); diff --git a/test/cases/innerTubeApi.ts b/test/cases/innerTubeApi.ts index 6ef3eb1..cc9cb2d 100644 --- a/test/cases/innerTubeApi.ts +++ b/test/cases/innerTubeApi.ts @@ -4,10 +4,10 @@ import assert from "assert"; import { YouTubeAPI } from "../../src/utils/youtubeApi"; import * as innerTube from "../../src/utils/innerTubeAPI"; import { partialDeepEquals } from "../utils/partialDeepEquals"; - +import { getVideoDetails } from "../../src/utils/getVideoDetails"; const videoID = "dQw4w9WgXcQ"; -const expected = { // partial type of innerTubeVideoDetails +const expectedInnerTube = { // partial type of innerTubeVideoDetails videoId: videoID, title: "Rick Astley - Never Gonna Give You Up (Official Music Video)", lengthSeconds: "212", @@ -25,17 +25,12 @@ const currentViews = 1284257550; describe("innertube API test", function() { it("should be able to get innerTube details", async () => { const result = await innerTube.getPlayerData(videoID); - assert.ok(partialDeepEquals(result, expected)); + assert.ok(partialDeepEquals(result, expectedInnerTube)); }); it("Should have more views than current", async () => { const result = await innerTube.getPlayerData(videoID); assert.ok(Number(result.viewCount) >= currentViews); }); - it("Should have the same video duration from both endpoints", async () => { - const playerData = await innerTube.getPlayerData(videoID); - const length = await innerTube.getLength(videoID); - assert.equal(Number(playerData.lengthSeconds), length); - }); it("Should have equivalent response from NewLeaf", async function () { if (!config.newLeafURLs || config.newLeafURLs.length <= 0 || config.newLeafURLs[0] == "placeholder") this.skip(); const itResponse = await innerTube.getPlayerData(videoID); @@ -48,4 +43,8 @@ describe("innertube API test", function() { // validate authorId assert.strictEqual(itResponse.channelId, newLeafResponse.data?.authorId); }); + it("Should return data from generic endpoint", async function () { + const videoDetail = await getVideoDetails(videoID); + assert.ok(videoDetail); + }); }); \ No newline at end of file diff --git a/test/cases/postSkipSegments.ts b/test/cases/postSkipSegments.ts index 1369282..17729dd 100644 --- a/test/cases/postSkipSegments.ts +++ b/test/cases/postSkipSegments.ts @@ -141,7 +141,6 @@ describe("postSkipSegments", () => { title: "Example Title", channelID: "ExampleChannel", published: 123, - genreUrl: "" }; assert.ok(partialDeepEquals(videoInfo, expectedVideoInfo)); diff --git a/test/youtubeMock.ts b/test/youtubeMock.ts index f0b89d1..bb489af 100644 --- a/test/youtubeMock.ts +++ b/test/youtubeMock.ts @@ -15,7 +15,7 @@ export class YouTubeApiMock { if (obj.id === "noDuration" || obj.id === "full_video_duration_segment") { return { - err: null, + err: false, data: { title: "Example Title", lengthSeconds: 0, @@ -32,7 +32,7 @@ export class YouTubeApiMock { }; } else if (obj.id === "duration-update") { return { - err: null, + err: false, data: { title: "Example Title", lengthSeconds: 500, @@ -49,7 +49,7 @@ export class YouTubeApiMock { }; } else if (obj.id === "channelid-convert") { return { - err: null, + err: false, data: { title: "Video Lookup Title", author: "ChannelAuthor", @@ -58,14 +58,14 @@ export class YouTubeApiMock { }; } else if (obj.id === "duration-changed") { return { - err: null, + err: false, data: { lengthSeconds: 100, } as APIVideoData }; } else { return { - err: null, + err: false, data: { title: "Example Title", authorId: "ExampleChannel", diff --git a/tsconfig.json b/tsconfig.json index c38f013..cb90fb2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "es2016", + "target": "ES2021", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ From f6c68ec29c633807987b368e9f6845d80707929b Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 19 Sep 2022 11:56:55 -0400 Subject: [PATCH 154/168] Add another option to get chapters submitting permission --- src/utils/permissions.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index ebd727a..936a88e 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -12,11 +12,19 @@ interface CanSubmitResult { reason?: string; } +async function lowDownvotes(userID: HashedUserID): Promise { + const result = await db.prepare("get", `SELECT count(*) as "submissionCount", SUM(CASE WHEN "votes" < 0 AND "views" > 5 THEN 1 ELSE 0 END) AS "downvotedSubmissions" FROM "sponsorTimes" WHERE "userID" = ?` + , [userID], { useReplica: true }); + + return result.submissionCount > 100 && result.downvotedSubmissions / result.submissionCount < 0.15; +} + export async function canSubmit(userID: HashedUserID, category: Category): Promise { switch (category) { case "chapter": return { canSubmit: await oneOf([isUserVIP(userID), + lowDownvotes(userID), (async () => (await getReputation(userID)) > config.minReputationToSubmitChapter)(), hasFeature(userID, Feature.ChapterSubmitter) ]) From f6f83fcbe43fb93b0f0391b6f392a4e9895cefa5 Mon Sep 17 00:00:00 2001 From: Ajay Date: Tue, 20 Sep 2022 23:41:36 -0400 Subject: [PATCH 155/168] Add concurrent request limit --- src/config.ts | 6 ++++-- src/databases/Postgres.ts | 32 +++++++++++++++++++++++++++++++- src/types/config.model.ts | 1 + 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/config.ts b/src/config.ts index 39debed..70e1fc3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -76,7 +76,8 @@ addDefaults(config, { port: 5432, max: 10, idleTimeoutMillis: 10000, - maxTries: 3 + maxTries: 3, + maxConcurrentRequests: 3500 }, postgresReadOnly: { enabled: false, @@ -89,7 +90,8 @@ addDefaults(config, { max: 10, idleTimeoutMillis: 10000, maxTries: 3, - fallbackOnFail: true + fallbackOnFail: true, + maxConcurrentRequests: 3500 }, dumpDatabase: { enabled: false, diff --git a/src/databases/Postgres.ts b/src/databases/Postgres.ts index 389257a..01f59b1 100644 --- a/src/databases/Postgres.ts +++ b/src/databases/Postgres.ts @@ -33,6 +33,9 @@ export class Postgres implements IDatabase { private poolRead: Pool; private lastPoolReadFail = 0; + private concurrentRequests = 0; + private concurrentReadRequests = 0; + constructor(private config: DatabaseConfig) {} async init(): Promise { @@ -99,8 +102,23 @@ export class Postgres implements IDatabase { Logger.debug(`prepare (postgres): type: ${type}, query: ${query}, params: ${params}`); - const pendingQueries: PromiseWithState>[] = []; + if (this.config.readOnly) { + if (this.concurrentReadRequests > this.config.postgresReadOnly?.maxConcurrentRequests) { + Logger.error(`prepare (postgres): cancelling read query because too many concurrent requests, query: ${query}`); + throw new Error("Too many concurrent requests"); + } + this.concurrentReadRequests++; + } else { + if (this.concurrentRequests > this.config.postgres.maxConcurrentRequests) { + Logger.error(`prepare (postgres): cancelling query because too many concurrent requests, query: ${query}`); + throw new Error("Too many concurrent requests"); + } + + this.concurrentRequests++; + } + + const pendingQueries: PromiseWithState>[] = []; let tries = 0; let lastPool: Pool = null; const maxTries = () => (lastPool === this.pool @@ -116,6 +134,12 @@ export class Postgres implements IDatabase { if (options.useReplica && maxTries() - tries > 1) currentPromises.push(savePromiseState(timeoutPomise(this.config.postgresReadOnly.readTimeout))); const queryResult = await nextFulfilment(currentPromises); + if (this.config.readOnly) { + this.concurrentReadRequests--; + } else { + this.concurrentRequests--; + } + switch (type) { case "get": { const value = queryResult.rows[0]; @@ -143,6 +167,12 @@ export class Postgres implements IDatabase { } } while (this.isReadQuery(type) && tries < maxTries()); + if (this.config.readOnly) { + this.concurrentReadRequests--; + } else { + this.concurrentRequests--; + } + throw new Error(`prepare (postgres): ${type} ${query} failed after ${tries} tries`); } diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 4e89a02..406643c 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -10,6 +10,7 @@ interface RedisConfig extends redis.RedisClientOptions { export interface CustomPostgresConfig extends PoolConfig { enabled: boolean; maxTries: number; + maxConcurrentRequests: number; } export interface CustomPostgresReadOnlyConfig extends CustomPostgresConfig { From 30bac658ed5ffdc71a2482111479b8f4c6249a85 Mon Sep 17 00:00:00 2001 From: Ajay Date: Wed, 21 Sep 2022 21:52:55 -0400 Subject: [PATCH 156/168] Remove old permission workaround --- src/utils/permissions.ts | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/utils/permissions.ts b/src/utils/permissions.ts index 936a88e..f64000d 100644 --- a/src/utils/permissions.ts +++ b/src/utils/permissions.ts @@ -31,16 +31,7 @@ export async function canSubmit(userID: HashedUserID, category: Category): Promi }; default: return { - canSubmit: await oneOf([isUserVIP(userID), - (async () => (await getReputation(userID)) > config.minReputationToSubmitFiller)(), - hasFeature(userID, Feature.FillerSubmitter), - (async () => (await db.prepare("get", `SELECT count(*) as "submissionCount" FROM "sponsorTimes" WHERE "userID" = ? AND category != 'filler' AND "timeSubmitted" < 1660096797000 LIMIT 1`, [userID], { useReplica: true }))?.submissionCount > 0)() - ]), - reason: "Yes, it has been a week, please be patient. Unfortunately, someone is doing a targeted attack and as a temporary emergency measure, segment submission for new users is disabled. You can request submission access on chat.sponsor.ajay.app/#filler, discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" + canSubmit: true }; } - - return { - canSubmit: true - }; } \ No newline at end of file From 7007ab05e1f8be2d994cdd267d023b0c72231d89 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 22 Sep 2022 11:12:47 -0400 Subject: [PATCH 157/168] Handle errors from redis store in request rate limit --- src/middleware/requestRateLimit.ts | 55 ++++++++++++++++-------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/middleware/requestRateLimit.ts b/src/middleware/requestRateLimit.ts index f7e40cc..27640c7 100644 --- a/src/middleware/requestRateLimit.ts +++ b/src/middleware/requestRateLimit.ts @@ -1,9 +1,9 @@ import { getIP } from "../utils/getIP"; import { getHash } from "../utils/getHash"; import { getHashCache } from "../utils/getHashCache"; -import rateLimit, { RateLimitRequestHandler } from "express-rate-limit"; +import rateLimit from "express-rate-limit"; import { RateLimitConfig } from "../types/config.model"; -import { Request } from "express"; +import { Request, RequestHandler } from "express"; import { isUserVIP } from "../utils/isUserVIP"; import { UserID } from "../types/user.model"; import RedisStore, { RedisReply } from "rate-limit-redis"; @@ -11,27 +11,32 @@ import redis from "../utils/redis"; import { config } from "../config"; import { Logger } from "../utils/logger"; -export function rateLimitMiddleware(limitConfig: RateLimitConfig, getUserID?: (req: Request) => UserID): RateLimitRequestHandler { - return rateLimit({ - windowMs: limitConfig.windowMs, - max: limitConfig.max, - message: limitConfig.message, - statusCode: limitConfig.statusCode, - legacyHeaders: false, - standardHeaders: false, - keyGenerator: (req) => { - return getHash(getIP(req), 1); - }, - // eslint-disable-next-line @typescript-eslint/no-misused-promises - handler: async (req, res, next) => { - if (getUserID === undefined || !await isUserVIP(await getHashCache(getUserID(req)))) { - return res.status(limitConfig.statusCode).send(limitConfig.message); - } else { - return next(); - } - }, - store: config.redis?.enabled ? new RedisStore({ - sendCommand: (...args: string[]) => redis.sendCommand(args).catch((err) => Logger.error(err)) as Promise, - }) : null, - }); +export function rateLimitMiddleware(limitConfig: RateLimitConfig, getUserID?: (req: Request) => UserID): RequestHandler { + try { + return rateLimit({ + windowMs: limitConfig.windowMs, + max: limitConfig.max, + message: limitConfig.message, + statusCode: limitConfig.statusCode, + legacyHeaders: false, + standardHeaders: false, + keyGenerator: (req) => { + return getHash(getIP(req), 1); + }, + // eslint-disable-next-line @typescript-eslint/no-misused-promises + handler: async (req, res, next) => { + if (getUserID === undefined || !await isUserVIP(await getHashCache(getUserID(req)))) { + return res.status(limitConfig.statusCode).send(limitConfig.message); + } else { + return next(); + } + }, + store: config.redis?.enabled ? new RedisStore({ + sendCommand: (...args: string[]) => redis.sendCommand(args).catch((err) => Logger.error(err)) as Promise, + }) : null, + }); + } catch (e) { + Logger.error(`Rate limit error: ${e}`); + return (req, res, next) => next(); + } } From 1c1496afbc8aefd0a93b67d11434b49dfb62cf96 Mon Sep 17 00:00:00 2001 From: Ajay Date: Thu, 22 Sep 2022 14:50:48 -0400 Subject: [PATCH 158/168] Change free chapter to use historical rep --- src/routes/getUserInfo.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index 38c171d..c0b1955 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -118,7 +118,7 @@ async function getPermissions(userID: HashedUserID): Promise { return await oneOf([isUserVIP(userID), - (async () => (await getReputation(userID)) > 0)(), + (async () => !!(await db.prepare("get", `SELECT "timeSubmitted" FROM "sponsorTimes" WHERE "reputation" > 0 AND "timeSubmitted" < 1663872563000 AND "userID" = ? LIMIT 1`, [userID], { useReplica: true })))(), (async () => !!(await db.prepare("get", `SELECT "timeSubmitted" FROM "sponsorTimes" WHERE "timeSubmitted" < 1590969600000 AND "userID" = ? LIMIT 1`, [userID], { useReplica: true })))() ]); } From 8fc01ba138763d4fdd33ae2db577a938074c406c Mon Sep 17 00:00:00 2001 From: Michael C Date: Thu, 22 Sep 2022 20:30:10 -0400 Subject: [PATCH 159/168] add fast fails for local and gumroad license keys --- src/routes/verifyToken.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/routes/verifyToken.ts b/src/routes/verifyToken.ts index 55b6faa..ac5b5f7 100644 --- a/src/routes/verifyToken.ts +++ b/src/routes/verifyToken.ts @@ -41,6 +41,12 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response) } } else { // Check Local + const localRegex = new RegExp(/[a-zA-Z0-9]{40}/); + if (!localRegex.test(licenseKey)) { + return res.status(200).send({ + allowed: false + }); + } const result = await privateDB.prepare("get", `SELECT "licenseKey" from "licenseKeys" WHERE "licenseKey" = ?`, [licenseKey]); if (result) { return res.status(200).send({ @@ -48,6 +54,12 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response) }); } else { // Gumroad + const gumRoadRegex = new RegExp(/[A-Z0-9-]{35}/); + if (gumRoadRegex.test(licenseKey)) { // check against regex + return res.status(200).send({ + allowed: false + }); + } return res.status(200).send({ allowed: await checkAllGumroadProducts(licenseKey) }); From 551e1031581692e664e853fbc197e6eee25bbac5 Mon Sep 17 00:00:00 2001 From: Michael C Date: Sat, 24 Sep 2022 20:13:35 -0400 Subject: [PATCH 160/168] add tregex for both patreon and gumroad --- src/routes/verifyToken.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/routes/verifyToken.ts b/src/routes/verifyToken.ts index ac5b5f7..59910ef 100644 --- a/src/routes/verifyToken.ts +++ b/src/routes/verifyToken.ts @@ -18,6 +18,12 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response) if (!licenseKey) { return res.status(400).send("Invalid request"); } + const licenseRegex = new RegExp(/[a-zA-Z0-9]{40}|[A-Z0-9-]{35}/); + if (!licenseRegex.test(licenseKey)) { + return res.status(200).send({ + allowed: false + }); + } const tokens = (await privateDB.prepare("get", `SELECT "accessToken", "refreshToken", "expiresIn" from "oauthLicenseKeys" WHERE "licenseKey" = ?` , [licenseKey])) as {accessToken: string, refreshToken: string, expiresIn: number}; @@ -41,12 +47,6 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response) } } else { // Check Local - const localRegex = new RegExp(/[a-zA-Z0-9]{40}/); - if (!localRegex.test(licenseKey)) { - return res.status(200).send({ - allowed: false - }); - } const result = await privateDB.prepare("get", `SELECT "licenseKey" from "licenseKeys" WHERE "licenseKey" = ?`, [licenseKey]); if (result) { return res.status(200).send({ @@ -54,12 +54,6 @@ export async function verifyTokenRequest(req: VerifyTokenRequest, res: Response) }); } else { // Gumroad - const gumRoadRegex = new RegExp(/[A-Z0-9-]{35}/); - if (gumRoadRegex.test(licenseKey)) { // check against regex - return res.status(200).send({ - allowed: false - }); - } return res.status(200).send({ allowed: await checkAllGumroadProducts(licenseKey) }); From 28448f99d939f4df21add97525dc7bf5f079a738 Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 26 Sep 2022 15:17:38 -0400 Subject: [PATCH 161/168] Lock s3cmd-sync version --- .github/workflows/generate-sqlite-base.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/generate-sqlite-base.yml b/.github/workflows/generate-sqlite-base.yml index 97c5cb3..6a4bf3f 100644 --- a/.github/workflows/generate-sqlite-base.yml +++ b/.github/workflows/generate-sqlite-base.yml @@ -26,7 +26,7 @@ jobs: with: name: SponsorTimesDB.db path: databases/sponsorTimes.db - - uses: mchangrh/s3cmd-sync@v1 + - uses: mchangrh/s3cmd-sync@f4f36b9705bdd9af7ac91964136989ac17e3b513 with: args: --acl-public env: From ea05284b1dc5ddf8fcb54ac9dfa58249d1a0d8eb Mon Sep 17 00:00:00 2001 From: Ajay Date: Mon, 26 Sep 2022 15:23:01 -0400 Subject: [PATCH 162/168] Allow manual dispatch of generate sqlite base --- .github/workflows/generate-sqlite-base.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/generate-sqlite-base.yml b/.github/workflows/generate-sqlite-base.yml index 6a4bf3f..c320e58 100644 --- a/.github/workflows/generate-sqlite-base.yml +++ b/.github/workflows/generate-sqlite-base.yml @@ -6,6 +6,7 @@ on: - master paths: - databases/** + workflow_dispatch: jobs: make-base-db: From 07926ada57bce56bdedd021b4efbdc6581ce6f07 Mon Sep 17 00:00:00 2001 From: Michael C Date: Mon, 26 Sep 2022 15:44:04 -0400 Subject: [PATCH 163/168] switch test videoID, remove useless import --- test/cases/innerTubeApi.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/cases/innerTubeApi.ts b/test/cases/innerTubeApi.ts index cc9cb2d..e20c4a6 100644 --- a/test/cases/innerTubeApi.ts +++ b/test/cases/innerTubeApi.ts @@ -1,26 +1,25 @@ import { config } from "../../src/config"; import assert from "assert"; -// import { innerTubeVideoDetails } from "../../src/types/innerTubeApi.model"; import { YouTubeAPI } from "../../src/utils/youtubeApi"; import * as innerTube from "../../src/utils/innerTubeAPI"; import { partialDeepEquals } from "../utils/partialDeepEquals"; import { getVideoDetails } from "../../src/utils/getVideoDetails"; -const videoID = "dQw4w9WgXcQ"; +const videoID = "BaW_jenozKc"; const expectedInnerTube = { // partial type of innerTubeVideoDetails videoId: videoID, - title: "Rick Astley - Never Gonna Give You Up (Official Music Video)", - lengthSeconds: "212", - channelId: "UCuAXFkgsw1L7xaCfnd5JJOw", + title: "youtube-dl test video \"'/\\ä↭𝕐", + lengthSeconds: "10", + channelId: "UCLqxVugv74EIW3VWh2NOa3Q", isOwnerViewing: false, isCrawlable: true, allowRatings: true, - author: "Rick Astley", + author: "Philipp Hagemeister", isPrivate: false, isUnpluggedCorpus: false, isLiveContent: false }; -const currentViews = 1284257550; +const currentViews = 49816; describe("innertube API test", function() { it("should be able to get innerTube details", async () => { From 55aa33aa6e61adf31c2fab6420ff66bfd0f89cb0 Mon Sep 17 00:00:00 2001 From: Michael C Date: Mon, 26 Sep 2022 15:58:33 -0400 Subject: [PATCH 164/168] add DiskCache to getPlayerData --- src/utils/getVideoDetails.ts | 4 ++-- src/utils/innerTubeAPI.ts | 36 +++++++++++++++++++++++++++++++++++- test/cases/innerTubeApi.ts | 6 +++--- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/utils/getVideoDetails.ts b/src/utils/getVideoDetails.ts index aa5636a..251336f 100644 --- a/src/utils/getVideoDetails.ts +++ b/src/utils/getVideoDetails.ts @@ -45,13 +45,13 @@ async function newLeafWrapper(videoId: string, ignoreCache: boolean) { export function getVideoDetails(videoId: string, ignoreCache = false): Promise { if (!config.newLeafURLs) { - return getPlayerData(videoId) + return getPlayerData(videoId, ignoreCache) .then(data => convertFromInnerTube(data)); } return Promise.any([ newLeafWrapper(videoId, ignoreCache) .then(videoData => convertFromNewLeaf(videoData)), - getPlayerData(videoId) + getPlayerData(videoId, ignoreCache) .then(data => convertFromInnerTube(data)) ]).catch(() => { return null; diff --git a/src/utils/innerTubeAPI.ts b/src/utils/innerTubeAPI.ts index c8f8696..c330e92 100644 --- a/src/utils/innerTubeAPI.ts +++ b/src/utils/innerTubeAPI.ts @@ -1,7 +1,9 @@ import axios from "axios"; +import { Logger } from "./logger"; import { innerTubeVideoDetails } from "../types/innerTubeApi.model"; +import DiskCache from "./diskCache"; -export async function getPlayerData(videoID: string): Promise { +async function getFromITube (videoID: string): Promise { // start subrequest const url = "https://www.youtube.com/youtubei/v1/player"; const data = { @@ -21,4 +23,36 @@ export async function getPlayerData(videoID: string): Promise { + if (!videoID || videoID.length !== 11 || videoID.includes(".")) { + return Promise.reject("Invalid video ID"); + } + + const cacheKey = `yt.itube.video.${videoID}`; + if (!ignoreCache) { // try fetching from cache + try { + const data = await DiskCache.get(cacheKey); + if (data) { + Logger.debug(`InnerTube API: cache used for video information: ${videoID}`); + return data as innerTubeVideoDetails; + } + } catch (err) { + return Promise.reject(err); + } + } + try { + const data = await getFromITube(videoID) + .catch(err => { + Logger.warn(`InnerTube API Error for ${videoID}: ${err}`); + return Promise.reject(err); + }); + DiskCache.set(cacheKey, data) + .then(() => Logger.debug(`InnerTube API: video information cache set for: ${videoID}`)) + .catch((err: any) => Logger.warn(err)); + return data; + } catch (err) { + return Promise.reject(err); + } } \ No newline at end of file diff --git a/test/cases/innerTubeApi.ts b/test/cases/innerTubeApi.ts index e20c4a6..0c98642 100644 --- a/test/cases/innerTubeApi.ts +++ b/test/cases/innerTubeApi.ts @@ -23,16 +23,16 @@ const currentViews = 49816; describe("innertube API test", function() { it("should be able to get innerTube details", async () => { - const result = await innerTube.getPlayerData(videoID); + const result = await innerTube.getPlayerData(videoID, true); assert.ok(partialDeepEquals(result, expectedInnerTube)); }); it("Should have more views than current", async () => { - const result = await innerTube.getPlayerData(videoID); + const result = await innerTube.getPlayerData(videoID, true); assert.ok(Number(result.viewCount) >= currentViews); }); it("Should have equivalent response from NewLeaf", async function () { if (!config.newLeafURLs || config.newLeafURLs.length <= 0 || config.newLeafURLs[0] == "placeholder") this.skip(); - const itResponse = await innerTube.getPlayerData(videoID); + const itResponse = await innerTube.getPlayerData(videoID, true); const newLeafResponse = await YouTubeAPI.listVideos(videoID, true); // validate videoID assert.strictEqual(itResponse.videoId, videoID); From 27f7b6d3c7fc2c72634f2608aeb1484d42655ba2 Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Wed, 28 Sep 2022 17:43:04 +0200 Subject: [PATCH 165/168] Add chapter category & action type to getUserStats.ts This also fixes chapters not being counted in the segment counts. --- src/routes/getUserStats.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/routes/getUserStats.ts b/src/routes/getUserStats.ts index 8fca455..38c8764 100644 --- a/src/routes/getUserStats.ts +++ b/src/routes/getUserStats.ts @@ -21,6 +21,7 @@ async function dbGetUserSummary(userID: HashedUserID, fetchCategoryStats: boolea SUM(CASE WHEN "category" = 'poi_highlight' THEN 1 ELSE 0 END) as "categorySumHighlight", SUM(CASE WHEN "category" = 'filler' THEN 1 ELSE 0 END) as "categorySumFiller", SUM(CASE WHEN "category" = 'exclusive_access' THEN 1 ELSE 0 END) as "categorySumExclusiveAccess", + SUM(CASE WHEN "category" = 'chapter' THEN 1 ELSE 0 END) as "categorySumChapter", `; } if (fetchActionTypeStats) { @@ -29,15 +30,16 @@ async function dbGetUserSummary(userID: HashedUserID, fetchCategoryStats: boolea SUM(CASE WHEN "actionType" = 'mute' THEN 1 ELSE 0 END) as "typeSumMute", SUM(CASE WHEN "actionType" = 'full' THEN 1 ELSE 0 END) as "typeSumFull", SUM(CASE WHEN "actionType" = 'poi' THEN 1 ELSE 0 END) as "typeSumPoi", + SUM(CASE WHEN "actionType" = 'chapter' THEN 1 ELSE 0 END) as "typeSumChapter", `; } try { const row = await db.prepare("get", ` - SELECT SUM(((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views") as "minutesSaved", + SELECT SUM(CASE WHEN "actionType" = 'chapter' THEN 0 ELSE ((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views" END) as "minutesSaved", ${additionalQuery} count(*) as "segmentCount" FROM "sponsorTimes" - WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1 AND "actionType" != 'chapter'`, + WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [maxRewardTimePerSegmentInSeconds, maxRewardTimePerSegmentInSeconds, userID]); const source = (row.minutesSaved != null) ? row : {}; const handler = { get: (target: Record, name: string) => target?.[name] || 0 }; @@ -60,6 +62,7 @@ async function dbGetUserSummary(userID: HashedUserID, fetchCategoryStats: boolea poi_highlight: proxy.categorySumHighlight, filler: proxy.categorySumFiller, exclusive_access: proxy.categorySumExclusiveAccess, + chapter: proxy.categorySumChapter, }; } if (fetchActionTypeStats) { @@ -67,7 +70,8 @@ async function dbGetUserSummary(userID: HashedUserID, fetchCategoryStats: boolea skip: proxy.typeSumSkip, mute: proxy.typeSumMute, full: proxy.typeSumFull, - poi: proxy.typeSumPoi + poi: proxy.typeSumPoi, + chapter: proxy.typeSumChapter, }; } return result; From 771fa18731267b6df77c47da851631858855e305 Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Wed, 28 Sep 2022 17:59:04 +0200 Subject: [PATCH 166/168] Modify getUserStats test cases to include chapter counts --- test/cases/getUserStats.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/cases/getUserStats.ts b/test/cases/getUserStats.ts index df096bd..f5d63d3 100644 --- a/test/cases/getUserStats.ts +++ b/test/cases/getUserStats.ts @@ -22,8 +22,7 @@ describe("getUserStats", () => { await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, -2, "skip", "getuserstatsuuid9", getHash("getuserstats_user_02"), 8, 2, "sponsor", 0]); await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "skip", "getuserstatsuuid10", getHash("getuserstats_user_01"), 8, 2, "filler", 0]); await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 0, 0, "full", "getuserstatsuuid11", getHash("getuserstats_user_01"), 8, 2, "exclusive_access", 0]); - - + await db.prepare("run", sponsorTimesQuery, ["getuserstats1", 0, 60, 0, "chapter", "getuserstatsuuid12", getHash("getuserstats_user_01"), 9, 2, "chapter", 0]); }); it("Should be able to get a 400 (No userID parameter)", (done) => { @@ -52,17 +51,19 @@ describe("getUserStats", () => { music_offtopic: 1, poi_highlight: 1, filler: 1, - exclusive_access: 1 + exclusive_access: 1, + chapter: 1, }, actionTypeCount: { mute: 0, skip: 8, full: 1, - poi: 1 + poi: 1, + chapter: 1, }, overallStats: { minutesSaved: 30, - segmentCount: 10 + segmentCount: 11 } }; assert.ok(partialDeepEquals(res.data, expected)); From 601a17c969b2a94ecafdb730c6ea0a4319b33f33 Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Wed, 28 Sep 2022 18:07:39 +0200 Subject: [PATCH 167/168] Ignore chapters for saved time calculations in getUserInfo.ts --- src/routes/getUserInfo.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/getUserInfo.ts b/src/routes/getUserInfo.ts index c0b1955..a5e8ea3 100644 --- a/src/routes/getUserInfo.ts +++ b/src/routes/getUserInfo.ts @@ -14,7 +14,7 @@ const maxRewardTime = config.maxRewardTimePerSegmentInSeconds; async function dbGetSubmittedSegmentSummary(userID: HashedUserID): Promise<{ minutesSaved: number, segmentCount: number }> { try { const row = await db.prepare("get", - `SELECT SUM(((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views") as "minutesSaved", + `SELECT SUM(CASE WHEN "actionType" = 'chapter' THEN 0 ELSE ((CASE WHEN "endTime" - "startTime" > ? THEN ? ELSE "endTime" - "startTime" END) / 60) * "views" END) as "minutesSaved", count(*) as "segmentCount" FROM "sponsorTimes" WHERE "userID" = ? AND "votes" > -2 AND "shadowHidden" != 1`, [maxRewardTime, maxRewardTime, userID], { useReplica: true }); if (row.minutesSaved != null) { @@ -200,4 +200,4 @@ export async function endpoint(req: Request, res: Response): Promise { return res.status(400).send("Invalid values JSON"); } else return res.sendStatus(500); } -} \ No newline at end of file +} From ed251a047a61ccc28afddbae8964455b81312ad6 Mon Sep 17 00:00:00 2001 From: mini-bomba <55105495+mini-bomba@users.noreply.github.com> Date: Wed, 28 Sep 2022 18:18:41 +0200 Subject: [PATCH 168/168] Add a test case for getUserInfo.ts ignoring chapters for time saved calc --- test/cases/getUserInfo.ts | 45 ++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/test/cases/getUserInfo.ts b/test/cases/getUserInfo.ts index 4f8d3ae..d4676c7 100644 --- a/test/cases/getUserInfo.ts +++ b/test/cases/getUserInfo.ts @@ -10,16 +10,17 @@ describe("getUserInfo", () => { const insertUserNameQuery = 'INSERT INTO "userNames" ("userID", "userName") VALUES(?, ?)'; await db.prepare("run", insertUserNameQuery, [getHash("getuserinfo_user_01"), "Username user 01"]); - const sponsorTimesQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "shadowHidden") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; - await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 1, 11, 2, "uuid000001", getHash("getuserinfo_user_01"), 1, 10, "sponsor", 0]); - await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 1, 11, 2, "uuid000002", getHash("getuserinfo_user_01"), 2, 10, "sponsor", 0]); - await db.prepare("run", sponsorTimesQuery, ["getUserInfo1", 1, 11, -1, "uuid000003", getHash("getuserinfo_user_01"), 3, 10, "sponsor", 0]); - await db.prepare("run", sponsorTimesQuery, ["getUserInfo1", 1, 11, -2, "uuid000004", getHash("getuserinfo_user_01"), 4, 10, "sponsor", 1]); - await db.prepare("run", sponsorTimesQuery, ["getUserInfo2", 1, 11, -5, "uuid000005", getHash("getuserinfo_user_01"), 5, 10, "sponsor", 1]); - await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 1, 11, 2, "uuid000007", getHash("getuserinfo_user_02"), 7, 10, "sponsor", 1]); - await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 1, 11, 2, "uuid000008", getHash("getuserinfo_user_02"), 8, 10, "sponsor", 1]); - await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 0, 36000, 2,"uuid000009", getHash("getuserinfo_user_03"), 8, 10, "sponsor", 0]); - await db.prepare("run", sponsorTimesQuery, ["getUserInfo3", 1, 11, 2, "uuid000006", getHash("getuserinfo_user_02"), 6, 10, "sponsor", 0]); + const sponsorTimesQuery = 'INSERT INTO "sponsorTimes" ("videoID", "startTime", "endTime", "votes", "UUID", "userID", "timeSubmitted", views, category, "actionType", "shadowHidden") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; + await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 1, 11, 2, "uuid000001", getHash("getuserinfo_user_01"), 1, 10, "sponsor", "skip", 0]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 1, 11, 2, "uuid000002", getHash("getuserinfo_user_01"), 2, 10, "sponsor", "skip", 0]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo1", 1, 11, -1, "uuid000003", getHash("getuserinfo_user_01"), 3, 10, "sponsor", "skip", 0]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo1", 1, 11, -2, "uuid000004", getHash("getuserinfo_user_01"), 4, 10, "sponsor", "skip", 1]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo2", 1, 11, -5, "uuid000005", getHash("getuserinfo_user_01"), 5, 10, "sponsor", "skip", 1]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 1, 11, 2, "uuid000007", getHash("getuserinfo_user_02"), 7, 10, "sponsor", "skip", 1]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 1, 11, 2, "uuid000008", getHash("getuserinfo_user_02"), 8, 10, "sponsor", "skip", 1]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo0", 0, 36000, 2,"uuid000009", getHash("getuserinfo_user_03"), 8, 10, "sponsor", "skip", 0]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo3", 1, 11, 2, "uuid000006", getHash("getuserinfo_user_02"), 6, 10, "sponsor", "skip", 0]); + await db.prepare("run", sponsorTimesQuery, ["getUserInfo4", 1, 11, 2, "uuid000010", getHash("getuserinfo_user_04"), 9, 10, "chapter", "chapter", 0]); const insertWarningQuery = 'INSERT INTO warnings ("userID", "issueTime", "issuerUserID", "enabled", "reason") VALUES (?, ?, ?, ?, ?)'; @@ -307,4 +308,28 @@ describe("getUserInfo", () => { }) .catch(err => done(err)); }); + + it("Should ignore chapters for saved time calculations", (done) => { + client.get(endpoint, { params: { userID: "getuserinfo_user_04" } }) + .then(res => { + assert.strictEqual(res.status, 200); + const expected = { + userName: "f187933817e7b0211a3f6f7d542a63ca9cc289d6cc8a8a79669d69a313671ccf", + userID: "f187933817e7b0211a3f6f7d542a63ca9cc289d6cc8a8a79669d69a313671ccf", + minutesSaved: 0, + viewCount: 10, + ignoredViewCount: 0, + segmentCount: 1, + ignoredSegmentCount: 0, + reputation: 0, + lastSegmentID: "uuid000010", + vip: false, + warnings: 0, + warningReason: "" + }; + assert.deepStrictEqual(res.data, expected); + done(); + }) + .catch(err => done(err)); + }); });