diff --git a/ci.json b/ci.json index 68262cb..86c106a 100644 --- a/ci.json +++ b/ci.json @@ -17,8 +17,10 @@ "port": 5432 }, "redis": { - "host": "localhost", - "port": 6379 + "socket": { + "host": "localhost", + "port": 6379 + } }, "createDatabaseIfNotExist": true, "schemaFolder": "./databases", diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 69526ec..2b4596d 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -72,6 +72,9 @@ http { 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; diff --git a/package-lock.json b/package-lock.json index 37f5178..ebf1447 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "express-rate-limit": "^6.3.0", "lodash": "^4.17.21", "pg": "^8.7.1", - "redis": "^3.1.2", + "rate-limit-redis": "^3.0.1", + "redis": "^4.0.6", "sync-mysql": "^3.0.1" }, "devDependencies": { @@ -29,7 +30,6 @@ "@types/mocha": "^9.0.0", "@types/node": "^16.11.11", "@types/pg": "^8.6.1", - "@types/redis": "^2.8.32", "@typescript-eslint/eslint-plugin": "^5.5.0", "@typescript-eslint/parser": "^5.5.0", "eslint": "^8.3.0", @@ -127,6 +127,65 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "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", @@ -356,15 +415,6 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "devOptional": true }, - "node_modules/@types/redis": { - "version": "2.8.32", - "resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.32.tgz", - "integrity": "sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/serve-static": { "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", @@ -1204,6 +1254,14 @@ "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", + "integrity": "sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==", + "engines": { + "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", @@ -1434,14 +1492,6 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, - "node_modules/denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", - "engines": { - "node": ">=0.10" - } - }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -2201,6 +2251,14 @@ "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", + "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==", + "engines": { + "node": ">= 4" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -3872,6 +3930,17 @@ "node": ">= 0.6" } }, + "node_modules/rate-limit-redis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-3.0.1.tgz", + "integrity": "sha512-L6yhOUBrAZ8VEMX9DwlM3X6hfm8yq+gBO4LoOW7+JgmNq59zE7QmLz4v5VnwYPvLeSh/e7PDcrzUI3UumJw1iw==", + "engines": { + "node": ">= 14.5.0" + }, + "peerDependencies": { + "express-rate-limit": "^6" + } + }, "node_modules/raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", @@ -3927,28 +3996,18 @@ } }, "node_modules/redis": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz", - "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.0.6.tgz", + "integrity": "sha512-IaPAxgF5dV0jx+A9l6yd6R9/PAChZIoAskDVRzUODeLDNhsMlq7OLLTmu0AwAr0xjrJ1bibW5xdpRwqIQ8Q0Xg==", "dependencies": { - "denque": "^1.5.0", - "redis-commands": "^1.7.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-redis" + "@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-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, "node_modules/redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", @@ -5165,6 +5224,54 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@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", @@ -5379,15 +5486,6 @@ "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", "devOptional": true }, - "@types/redis": { - "version": "2.8.32", - "resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.32.tgz", - "integrity": "sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/serve-static": { "version": "1.13.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", @@ -6016,6 +6114,11 @@ } } }, + "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", @@ -6196,11 +6299,6 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, - "denque": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", - "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" - }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -6790,6 +6888,11 @@ } } }, + "generic-pool": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.8.2.tgz", + "integrity": "sha512-nGToKy6p3PAbYQ7p1UlWl6vSPwfwU6TMSWK7TTu+WUY4ZjyZQGniGGt2oNVvyNSpyZYSB43zMXVLcBm08MTMkg==" + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -8039,6 +8142,12 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, + "rate-limit-redis": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-3.0.1.tgz", + "integrity": "sha512-L6yhOUBrAZ8VEMX9DwlM3X6hfm8yq+gBO4LoOW7+JgmNq59zE7QmLz4v5VnwYPvLeSh/e7PDcrzUI3UumJw1iw==", + "requires": {} + }, "raw-body": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", @@ -8082,21 +8191,18 @@ } }, "redis": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz", - "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.0.6.tgz", + "integrity": "sha512-IaPAxgF5dV0jx+A9l6yd6R9/PAChZIoAskDVRzUODeLDNhsMlq7OLLTmu0AwAr0xjrJ1bibW5xdpRwqIQ8Q0Xg==", "requires": { - "denque": "^1.5.0", - "redis-commands": "^1.7.0", - "redis-errors": "^1.2.0", - "redis-parser": "^3.0.0" + "@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-commands": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", - "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" - }, "redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", diff --git a/package.json b/package.json index d727b11..4ddd8dc 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,8 @@ "express-rate-limit": "^6.3.0", "lodash": "^4.17.21", "pg": "^8.7.1", - "redis": "^3.1.2", + "rate-limit-redis": "^3.0.1", + "redis": "^4.0.6", "sync-mysql": "^3.0.1" }, "devDependencies": { @@ -37,7 +38,6 @@ "@types/mocha": "^9.0.0", "@types/node": "^16.11.11", "@types/pg": "^8.6.1", - "@types/redis": "^2.8.32", "@typescript-eslint/eslint-plugin": "^5.5.0", "@typescript-eslint/parser": "^5.5.0", "eslint": "^8.3.0", diff --git a/src/config.ts b/src/config.ts index f4e6175..d6f59ca 100644 --- a/src/config.ts +++ b/src/config.ts @@ -8,6 +8,7 @@ const configFile = process.env.TEST_POSTGRES ? "ci.json" : "config.json"; export const config: SBSConfig = JSON.parse(fs.readFileSync(configFile).toString("utf8")); +migrate(config); addDefaults(config, { port: 80, behindProxy: "X-Forwarded-For", @@ -109,3 +110,20 @@ function addDefaults(config: SBSConfig, defaults: SBSConfig) { } } } + +function migrate(config: SBSConfig) { + // Redis change + if (config.redis) { + const redisConfig = config.redis as any; + if (redisConfig.host || redisConfig.port) { + config.redis.socket = { + host: redisConfig.host, + port: redisConfig.port + }; + } + + if (redisConfig.enable_offline_queue !== undefined) { + config.disableOfflineQueue = !redisConfig.enable_offline_queue; + } + } +} \ No newline at end of file diff --git a/src/middleware/requestRateLimit.ts b/src/middleware/requestRateLimit.ts index 63b65c9..a188011 100644 --- a/src/middleware/requestRateLimit.ts +++ b/src/middleware/requestRateLimit.ts @@ -6,6 +6,9 @@ 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 redis from "../utils/redis"; +import { config } from "../config"; export function rateLimitMiddleware(limitConfig: RateLimitConfig, getUserID?: (req: Request) => UserID): RateLimitRequestHandler { return rateLimit({ @@ -24,6 +27,9 @@ export function rateLimitMiddleware(limitConfig: RateLimitConfig, getUserID?: (r } else { return next(); } - } + }, + store: config.redis ? new RedisStore({ + sendCommand: (...args: string[]) => redis.sendCommand(args), + }) : null, }); } diff --git a/src/routes/addUserAsTempVIP.ts b/src/routes/addUserAsTempVIP.ts index fb8d482..3001a0e 100644 --- a/src/routes/addUserAsTempVIP.ts +++ b/src/routes/addUserAsTempVIP.ts @@ -9,6 +9,7 @@ import { isUserVIP } from "../utils/isUserVIP"; import { HashedUserID } from "../types/user.model"; import redis from "../utils/redis"; import { tempVIPKey } from "../utils/redisKeys"; +import { Logger } from "../utils/logger"; interface AddUserAsTempVIPRequest extends Request { query: { @@ -65,12 +66,22 @@ export async function addUserAsTempVIP(req: AddUserAsTempVIPRequest, res: Respon if (!channelInfo?.id) { return res.status(404).send(`No channel found for videoID ${channelVideoID}`); } - await redis.setAsyncEx(tempVIPKey(userID), channelInfo?.id, dayInSeconds); - await privateDB.prepare("run", `INSERT INTO "tempVipLog" VALUES (?, ?, ?, ?)`, [adminUserID, userID, + enabled, startTime]); - return res.status(200).send(`Temp VIP added on channel ${channelInfo?.name}`); - } - await redis.delAsync(tempVIPKey(userID)); - await privateDB.prepare("run", `INSERT INTO "tempVipLog" VALUES (?, ?, ?, ?)`, [adminUserID, userID, + enabled, startTime]); - return res.status(200).send(`Temp VIP removed`); + try { + await redis.setEx(tempVIPKey(userID), dayInSeconds, channelInfo?.id); + await privateDB.prepare("run", `INSERT INTO "tempVipLog" VALUES (?, ?, ?, ?)`, [adminUserID, userID, + enabled, startTime]); + return res.status(200).send(`Temp VIP added on channel ${channelInfo?.name}`); + } catch (e) { + Logger.error(e as string); + return res.status(500).send(); + } + } + try { + await redis.del(tempVIPKey(userID)); + await privateDB.prepare("run", `INSERT INTO "tempVipLog" VALUES (?, ?, ?, ?)`, [adminUserID, userID, + enabled, startTime]); + return res.status(200).send(`Temp VIP removed`); + } catch (e) { + Logger.error(e as string); + return res.status(500).send(); + } } \ No newline at end of file diff --git a/src/routes/getStatus.ts b/src/routes/getStatus.ts index bc855c2..770ca25 100644 --- a/src/routes/getStatus.ts +++ b/src/routes/getStatus.ts @@ -10,8 +10,12 @@ export async function getStatus(req: Request, res: Response): Promise value = Array.isArray(value) ? value[0] : value; try { const dbVersion = (await db.prepare("get", "SELECT key, value FROM config where key = ?", ["version"])).value; - const numberRequests = await redis.increment("statusRequest"); - const statusRequests = numberRequests?.replies?.[0]; + let statusRequests: unknown = 0; + try { + const numberRequests = await redis.increment("statusRequest"); + statusRequests = numberRequests?.[0]; + } catch (error) { } // eslint-disable-line no-empty + const statusValues: Record = { uptime: process.uptime(), commit: (global as any).HEADCOMMIT || "unknown", diff --git a/src/types/config.model.ts b/src/types/config.model.ts index 1a5a22f..52ba2f2 100644 --- a/src/types/config.model.ts +++ b/src/types/config.model.ts @@ -41,7 +41,7 @@ export interface SBSConfig { privateMysql?: any; minimumPrefix?: string; maximumPrefix?: string; - redis?: redis.ClientOpts; + redis?: redis.RedisClientOptions; maxRewardTimePerSegmentInSeconds?: number; postgres?: PoolConfig; dumpDatabase?: DumpDatabase; diff --git a/src/utils/getHashCache.ts b/src/utils/getHashCache.ts index 14d30c6..f801545 100644 --- a/src/utils/getHashCache.ts +++ b/src/utils/getHashCache.ts @@ -18,18 +18,19 @@ export async function getHashCache(value: T, times = defaulted async function getFromRedis(key: HashedValue): Promise { const redisKey = shaHashKey(key); - const { err, reply } = await redis.getAsync(redisKey); - if (!err && reply) { - try { + try { + const reply = await redis.get(redisKey); + + if (reply) { Logger.debug(`Got data from redis: ${reply}`); return reply as T & HashedValue; - } catch (e) { - // If all else, continue on hashing } - } - const data = getHash(key, cachedHashTimes); + } catch (e) {} // eslint-disable-line no-empty + + // Otherwise, calculate it + const data = getHash(key, cachedHashTimes); + redis.set(key, data); - redis.setAsync(key, data); return data as T & HashedValue; } \ No newline at end of file diff --git a/src/utils/isUserTempVIP.ts b/src/utils/isUserTempVIP.ts index dc098e0..a2758bc 100644 --- a/src/utils/isUserTempVIP.ts +++ b/src/utils/isUserTempVIP.ts @@ -5,6 +5,7 @@ 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; @@ -13,7 +14,11 @@ function getYouTubeVideoInfo(videoID: VideoID, ignoreCache = false): Promise => { const apiVideoInfo = await getYouTubeVideoInfo(videoID); const channelID = apiVideoInfo?.data?.authorId; - const { err, reply } = await redis.getAsync(tempVIPKey(hashedUserID)); - - return err || !reply ? false : (reply == channelID); + try { + const reply = await redis.get(tempVIPKey(hashedUserID)); + return reply && reply == channelID; + } catch (e) { + Logger.error(e as string); + return false; + } }; \ No newline at end of file diff --git a/src/utils/queryCacher.ts b/src/utils/queryCacher.ts index 57af472..028c4e7 100644 --- a/src/utils/queryCacher.ts +++ b/src/utils/queryCacher.ts @@ -5,21 +5,18 @@ import { Service, VideoID, VideoIDHash } from "../types/segments.model"; import { UserID } from "../types/user.model"; async function get(fetchFromDB: () => Promise, key: string): Promise { - const { err, reply } = await redis.getAsync(key); - - if (!err && reply) { - try { + try { + const reply = await redis.get(key); + if (reply) { Logger.debug(`Got data from redis: ${reply}`); return JSON.parse(reply); - } catch (e) { - // If all else, continue on to fetching from the database } - } + } catch (e) { } //eslint-disable-line no-empty const data = await fetchFromDB(); - redis.setAsync(key, JSON.stringify(data)); + redis.set(key, JSON.stringify(data)); return data; } @@ -30,18 +27,17 @@ async function get(fetchFromDB: () => Promise, key: string): Promise { async function getAndSplit(fetchFromDB: (values: U[]) => Promise>, keyGenerator: (value: U) => string, splitKey: string, values: U[]): Promise> { const cachedValues = await Promise.all(values.map(async (value) => { const key = keyGenerator(value); - const { err, reply } = await redis.getAsync(key); - - if (!err && reply) { - try { + try { + const reply = await redis.get(key); + if (reply) { Logger.debug(`Got data from redis: ${reply}`); return { value, result: JSON.parse(reply) }; - } catch (e) { } //eslint-disable-line no-empty - } + } + } catch (e) { } //eslint-disable-line no-empty return { value, @@ -71,7 +67,7 @@ async function getAndSplit(fetchFromDB: (values: U[]) => Pr } for (const key in newResults) { - redis.setAsync(key, JSON.stringify(newResults[key])); + redis.set(key, JSON.stringify(newResults[key])); } }); } @@ -81,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.delAsync(skipSegmentsKey(videoInfo.videoID, videoInfo.service)); - redis.delAsync(skipSegmentGroupsKey(videoInfo.videoID, videoInfo.service)); - redis.delAsync(skipSegmentsHashKey(videoInfo.hashedVideoID, videoInfo.service)); - if (videoInfo.userID) redis.delAsync(reputationKey(videoInfo.userID)); + 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)); } } function clearRatingCache(videoInfo: { hashedVideoID: VideoIDHash; service: Service;}): void { if (videoInfo) { - redis.delAsync(ratingHashKey(videoInfo.hashedVideoID, videoInfo.service)); + redis.del(ratingHashKey(videoInfo.hashedVideoID, videoInfo.service)); } } diff --git a/src/utils/redis.ts b/src/utils/redis.ts index 7faefe6..3c3123e 100644 --- a/src/utils/redis.ts +++ b/src/utils/redis.ts @@ -1,59 +1,56 @@ import { config } from "../config"; import { Logger } from "./logger"; -import redis, { Callback } from "redis"; +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 { RedisReply } from "rate-limit-redis"; interface RedisSB { - get(key: string, callback?: Callback): void; - getAsync?(key: string): Promise<{err: Error | null, reply: string | null}>; - set(key: string, value: string, callback?: Callback): void; - setAsync?(key: string, value: string): Promise<{err: Error | null, reply: string | null}>; - setAsyncEx?(key: string, value: string, seconds: number): Promise<{err: Error | null, reply: string | null}>; - delAsync?(...keys: [string]): Promise; - close?(flush?: boolean): void; - increment?(key: string): Promise<{err: Error| null, replies: any[] | null}>; + get(key: RedisCommandArgument): Promise; + set(key: RedisCommandArgument, value: RedisCommandArgument): Promise; + setEx(key: RedisCommandArgument, seconds: number, value: RedisCommandArgument): Promise; + del(...keys: [RedisCommandArgument]): Promise; + increment?(key: RedisCommandArgument): Promise; + sendCommand(args: RedisCommandArguments, options?: ClientCommandOptions): Promise; + quit(): Promise; } -let exportObject: RedisSB = { - get: (key, callback?) => callback(null, undefined), - getAsync: () => - new Promise((resolve) => resolve({ err: null, reply: undefined })), - set: (key, value, callback) => callback(null, undefined), - setAsync: () => - new Promise((resolve) => resolve({ err: null, reply: undefined })), - setAsyncEx: () => - new Promise((resolve) => resolve({ err: null, reply: undefined })), - delAsync: () => - new Promise((resolve) => resolve(null)), - increment: () => - new Promise((resolve) => resolve({ err: null, replies: undefined })), +let exportClient: RedisSB = { + get: () => new Promise((resolve, reject) => reject()), + set: () => new Promise((resolve, reject) => reject()), + setEx: () => new Promise((resolve, reject) => reject()), + del: () => new Promise((resolve, reject) => reject()), + increment: () => new Promise((resolve, reject) => reject()), + sendCommand: () => new Promise((resolve, reject) => reject()), + quit: () => new Promise((resolve, reject) => reject()), }; if (config.redis) { Logger.info("Connected to redis"); - const client = redis.createClient(config.redis); - exportObject = client; + const client = createClient(config.redis); + client.connect(); + exportClient = client; const timeoutDuration = 200; - exportObject.getAsync = (key) => new Promise((resolve) => { - const timeout = setTimeout(() => resolve({ err: null, reply: undefined }), timeoutDuration); - client.get(key, (err, reply) => { + const get = client.get.bind(client); + exportClient.get = (key) => new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(), timeoutDuration); + get(key).then((reply) => { clearTimeout(timeout); - resolve({ err, reply }); - }); + resolve(reply); + }).catch((err) => reject(err)); }); - exportObject.setAsync = (key, value) => new Promise((resolve) => client.set(key, value, (err, reply) => resolve({ err, reply }))); - exportObject.setAsyncEx = (key, value, seconds) => new Promise((resolve) => client.setex(key, seconds, value, (err, reply) => resolve({ err, reply }))); - exportObject.delAsync = (...keys) => new Promise((resolve) => client.del(keys, (err) => resolve(err))); - exportObject.close = (flush) => client.end(flush); - exportObject.increment = (key) => new Promise((resolve) => + exportClient.increment = (key) => new Promise((resolve, reject) => client.multi() .incr(key) .expire(key, 60) - .exec((err, replies) => resolve({ err, replies })) + .exec() + .then((reply) => resolve(reply)) + .catch((err) => reject(err)) ); client.on("error", function(error) { Logger.error(error); }); } -export default exportObject; +export default exportClient; diff --git a/test/cases/redisTest.ts b/test/cases/redisTest.ts index 3ab238d..e86f123 100644 --- a/test/cases/redisTest.ts +++ b/test/cases/redisTest.ts @@ -12,21 +12,20 @@ const randKey2 = genRandom(16); describe("redis test", function() { before(async function() { if (!config.redis) this.skip(); - await redis.setAsync(randKey1, randValue1); + await redis.set(randKey1, randValue1); }); it("Should get stored value", (done) => { - redis.getAsync(randKey1) + redis.get(randKey1) .then(res => { - if (res.err) assert.fail(res.err); - assert.strictEqual(res.reply, randValue1); + assert.strictEqual(res, randValue1); done(); - }); + }).catch(err => assert.fail(err)); }); it("Should not be able to get not stored value", (done) => { - redis.getAsync(randKey2) + redis.get(randKey2) .then(res => { - if (res.reply || res.err ) assert.fail("Value should not be found"); + if (res) assert.fail("Value should not be found"); done(); - }); + }).catch(err => assert.fail(err)); }); }); \ No newline at end of file diff --git a/test/cases/tempVip.ts b/test/cases/tempVip.ts index c2fbc10..3051e0c 100644 --- a/test/cases/tempVip.ts +++ b/test/cases/tempVip.ts @@ -6,6 +6,7 @@ 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]); @@ -51,8 +52,13 @@ const postVoteCategory = (userID: string, UUID: string, category: string) => cli } }); const checkUserVIP = async (publicID: HashedUserID) => { - const { reply } = await redis.getAsync(tempVIPKey(publicID)); - return reply; + try { + const reply = await redis.get(tempVIPKey(publicID)); + return reply; + } catch (e) { + Logger.error(e as string); + return false; + } }; describe("tempVIP test", function() { @@ -67,7 +73,7 @@ describe("tempVIP test", function() { await db.prepare("run", 'INSERT INTO "vipUsers" ("userID") VALUES (?)', [publicPermVIP1]); await db.prepare("run", 'INSERT INTO "vipUsers" ("userID") VALUES (?)', [publicPermVIP2]); // clear redis if running consecutive tests - await redis.delAsync(tempVIPKey(publicTempVIPOne)); + await redis.del(tempVIPKey(publicTempVIPOne)); }); it("Should update db version when starting the application", () => { diff --git a/test/test.ts b/test/test.ts index 3d72c34..e13f4ba 100644 --- a/test/test.ts +++ b/test/test.ts @@ -57,7 +57,7 @@ async function init() { mocha.run((failures) => { mockServer.close(); server.close(); - redis.close(true); + redis.quit(); process.exitCode = failures ? 1 : 0; // exit with non-zero status if there were failures }); });