diff --git a/.eslintignore b/.eslintignore index f4013e9d..fa73479d 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,4 @@ dist -*.min.js \ No newline at end of file +*.min.js +# temporarily disable eslint in webfiles as settings aren't right +src/webfiles/* \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 17d5c804..4c8979ed 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,6 +4,9 @@ "commonjs": true, "es6": true }, + "plugins": [ + "@typescript-eslint" + ], "parser": "@typescript-eslint/parser", "globals": { "BigInt": true @@ -12,10 +15,10 @@ "eslint:recommended", "plugin:@typescript-eslint/recommended" ], - "plugins": [ - "@typescript-eslint" - ], "rules": { + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/no-extra-semi": "error", + "@typescript-eslint/keyword-spacing": "error", "require-atomic-updates": "warn", "no-case-declarations": "off", "no-empty": "off", @@ -25,15 +28,8 @@ "prefer-const": "error", "no-var": "error", "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], "no-extra-semi": "off", - "@typescript-eslint/no-extra-semi": "error", - "@typescript-eslint/no-empty-interface": "warn", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/typedef": "error", - "@typescript-eslint/explicit-function-return-type": "error", "keyword-spacing": "off", - "@typescript-eslint/keyword-spacing": "error", "curly": "error", "brace-style": "error", "one-var": [ @@ -55,5 +51,24 @@ "error", "always" ] - } + }, + "overrides": [ + { + "files": ["**/*.ts", "**/*.d.ts"], + "rules": { + "@typescript-eslint/no-empty-interface": "warn", + "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/typedef": "error", + "@typescript-eslint/explicit-function-return-type": "error", + "@typescript-eslint/consistent-type-imports": ["error", { "prefer": "type-imports" }] + } + }, + { + "files": ["**/*.js"], + "rules": { + // This rule does not work for js files + "@typescript-eslint/no-var-requires": "off" + } + } + ] } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 995ce859..828ab997 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ config.json certs src/logs uploads +dist/ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f2dc7fd1..86571a29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,12 +35,24 @@ "pako": "^2.0.2", "pngjs": "^6.0.0", "redis": "^4.6.13", - "sharp": "^0.31.3", + "sharp": "^0.32.6", "tga": "^1.0.4", "xml2js": "^0.4.23", "xmlbuilder2": "^3.0.2" }, "devDependencies": { + "@types/bmp-js": "^0.1.2", + "@types/cookie-parser": "^1.4.7", + "@types/express": "^4.17.21", + "@types/express-session": "^1.18.0", + "@types/fs-extra": "^11.0.4", + "@types/hashmap": "^2.3.4", + "@types/morgan": "^1.9.9", + "@types/multer": "^1.4.11", + "@types/node": "^20.12.12", + "@types/pako": "^2.0.3", + "@types/pngjs": "^6.0.0", + "@types/sharp": "^0.32.0", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "bmp-js": "^0.1.0", @@ -1644,21 +1656,195 @@ "node": ">=14.0.0" } }, + "node_modules/@types/bmp-js": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/bmp-js/-/bmp-js-0.1.2.tgz", + "integrity": "sha512-BF6u+M+KySDBKfr1DQOT3+z42n4/tq62uf9ong4i2SLLpa7mJqPwEmKbNgMzlVliT/jtE6elxyD/0X1mja8jLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express-session": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.0.tgz", + "integrity": "sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "node_modules/@types/hashmap": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hashmap/-/hashmap-2.3.4.tgz", + "integrity": "sha512-IoFSb7S7cwCM23HcAhUS57DBWPU0dPSF6Wz4M4y0S+B1xgGmM08WOzd1wBldesmZ28jhzRNmXda/FO5uY3tLlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/morgan": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.9.tgz", + "integrity": "sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/multer": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", + "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { - "version": "14.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.2.tgz", - "integrity": "sha512-onlIwbaeqvZyniGPfdw/TEhKIh79pz66L1q06WUQqJLnAb6wbjvOtepLYTGHTqzdXgBYIE3ZdmqHDGsRsbBz7A==" + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/pako": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", + "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/pngjs": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.5.tgz", + "integrity": "sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" }, "node_modules/@types/semver": { "version": "7.5.0", @@ -1666,6 +1852,40 @@ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sharp": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.32.0.tgz", + "integrity": "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==", + "deprecated": "This is a stub types definition. sharp provides its own type definitions, so you do not need this installed.", + "dev": true, + "license": "MIT", + "dependencies": { + "sharp": "*" + } + }, "node_modules/@types/webidl-conversions": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.2.tgz", @@ -2248,12 +2468,64 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "license": "Apache-2.0" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bare-events": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", + "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.0.tgz", + "integrity": "sha512-TNFqa1B4N99pds2a5NYHR15o0ZpdNKbAeKTE/+G6ED/UeOavv8RY3dr/Fu99HW3zU3pXpo2kDNO8Sjsm2esfOw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^1.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.3.0.tgz", + "integrity": "sha512-oPb8oMM1xZbhRQBngTgpcQ5gXw6kjOaRsSWsIeNyRxGed2w/ARyP7ScBYpWR1qfX2E5rS3gBw6OWcSQo+s+kUg==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.2.tgz", + "integrity": "sha512-o7KSt4prEphWUHa3QUwCxUI00R86VdjiuxmJK0iNVDHYPGo+HsDaVCnqCmPbf/MiW1ok8F4p3m8RTHlWk8K2ig==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-1.0.0.tgz", + "integrity": "sha512-KhNUoDL40iP4gFaLSsoGE479t0jHijfYdIcxRn/XtezA2BaUD0NRf/JGRpsMq6dMNM+SrCrB0YSSo/5wBY4rOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.16.1" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2994,9 +3266,10 @@ } }, "node_modules/detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", "engines": { "node": ">=8" } @@ -3824,6 +4097,12 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -5480,9 +5759,10 @@ } }, "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "license": "MIT" }, "node_modules/node-rsa": { "version": "1.1.1", @@ -5905,6 +6185,12 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "license": "MIT" + }, "node_modules/quote-stream": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", @@ -6244,18 +6530,19 @@ "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==" }, "node_modules/sharp": { - "version": "0.31.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.3.tgz", - "integrity": "sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==", + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "color": "^4.2.3", - "detect-libc": "^2.0.1", - "node-addon-api": "^5.0.0", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", "prebuild-install": "^7.1.1", - "semver": "^7.3.8", + "semver": "^7.5.4", "simple-get": "^4.0.1", - "tar-fs": "^2.1.1", + "tar-fs": "^3.0.4", "tunnel-agent": "^0.6.0" }, "engines": { @@ -6265,6 +6552,31 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/sharp/node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/sharp/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6485,6 +6797,19 @@ "node": ">=0.8.0" } }, + "node_modules/streamx": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", + "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -6860,6 +7185,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, "node_modules/universalify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", @@ -8479,21 +8810,176 @@ "tslib": "^2.5.0" } }, + "@types/bmp-js": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/bmp-js/-/bmp-js-0.1.2.tgz", + "integrity": "sha512-BF6u+M+KySDBKfr1DQOT3+z42n4/tq62uf9ong4i2SLLpa7mJqPwEmKbNgMzlVliT/jtE6elxyD/0X1mja8jLg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-Fvuyi354Z+uayxzIGCwYTayFKocfV7TuDYZClCdIP9ckhvAu/ixDtCB6qx2TT0FKjPLf1f3P/J1rgf6lPs64mw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz", + "integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "@types/express-session": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.0.tgz", + "integrity": "sha512-27JdDRgor6PoYlURY+Y5kCakqp5ulC0kmf7y+QwaY+hv9jEFuQOThgkjyA53RP3jmKuBsH5GR6qEfFmvb8mwOA==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "requires": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "@types/hashmap": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@types/hashmap/-/hashmap-2.3.4.tgz", + "integrity": "sha512-IoFSb7S7cwCM23HcAhUS57DBWPU0dPSF6Wz4M4y0S+B1xgGmM08WOzd1wBldesmZ28jhzRNmXda/FO5uY3tLlw==", + "dev": true + }, + "@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", "dev": true }, + "@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==" }, + "@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "@types/morgan": { + "version": "1.9.9", + "resolved": "https://registry.npmjs.org/@types/morgan/-/morgan-1.9.9.tgz", + "integrity": "sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/multer": { + "version": "1.4.11", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.11.tgz", + "integrity": "sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/node": { - "version": "14.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.2.tgz", - "integrity": "sha512-onlIwbaeqvZyniGPfdw/TEhKIh79pz66L1q06WUQqJLnAb6wbjvOtepLYTGHTqzdXgBYIE3ZdmqHDGsRsbBz7A==" + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/pako": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.3.tgz", + "integrity": "sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==", + "dev": true + }, + "@types/pngjs": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@types/pngjs/-/pngjs-6.0.5.tgz", + "integrity": "sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true }, "@types/semver": { "version": "7.5.0", @@ -8501,6 +8987,36 @@ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, + "@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "requires": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "@types/sharp": { + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@types/sharp/-/sharp-0.32.0.tgz", + "integrity": "sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==", + "dev": true, + "requires": { + "sharp": "*" + } + }, "@types/webidl-conversions": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.2.tgz", @@ -8911,12 +9427,58 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, + "b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "bare-events": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", + "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==", + "optional": true + }, + "bare-fs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.0.tgz", + "integrity": "sha512-TNFqa1B4N99pds2a5NYHR15o0ZpdNKbAeKTE/+G6ED/UeOavv8RY3dr/Fu99HW3zU3pXpo2kDNO8Sjsm2esfOw==", + "optional": true, + "requires": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^1.0.0" + } + }, + "bare-os": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.3.0.tgz", + "integrity": "sha512-oPb8oMM1xZbhRQBngTgpcQ5gXw6kjOaRsSWsIeNyRxGed2w/ARyP7ScBYpWR1qfX2E5rS3gBw6OWcSQo+s+kUg==", + "optional": true + }, + "bare-path": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.2.tgz", + "integrity": "sha512-o7KSt4prEphWUHa3QUwCxUI00R86VdjiuxmJK0iNVDHYPGo+HsDaVCnqCmPbf/MiW1ok8F4p3m8RTHlWk8K2ig==", + "optional": true, + "requires": { + "bare-os": "^2.1.0" + } + }, + "bare-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-1.0.0.tgz", + "integrity": "sha512-KhNUoDL40iP4gFaLSsoGE479t0jHijfYdIcxRn/XtezA2BaUD0NRf/JGRpsMq6dMNM+SrCrB0YSSo/5wBY4rOQ==", + "optional": true, + "requires": { + "streamx": "^2.16.1" + } + }, "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -9450,9 +10012,9 @@ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "detect-libc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz", - "integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==" }, "dicer": { "version": "0.2.5", @@ -10089,6 +10651,11 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, + "fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, "fast-glob": { "version": "3.2.12", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", @@ -11315,9 +11882,9 @@ } }, "node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" }, "node-rsa": { "version": "1.1.1", @@ -11630,6 +12197,11 @@ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, + "queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, "quote-stream": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz", @@ -11891,18 +12463,41 @@ "integrity": "sha512-b6i4ZpVuUxB9h5gfCxPiusKYkqTMOjEbBs4wMaFbkfia4yFv92UKZ6Df8WXcKbn08JNL/abvg3FnMAOfakDvUw==" }, "sharp": { - "version": "0.31.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.31.3.tgz", - "integrity": "sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==", + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", "requires": { "color": "^4.2.3", - "detect-libc": "^2.0.1", - "node-addon-api": "^5.0.0", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", "prebuild-install": "^7.1.1", - "semver": "^7.3.8", + "semver": "^7.5.4", "simple-get": "^4.0.1", - "tar-fs": "^2.1.1", + "tar-fs": "^3.0.4", "tunnel-agent": "^0.6.0" + }, + "dependencies": { + "tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "requires": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "requires": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + } } }, "shebang-command": { @@ -12059,6 +12654,16 @@ "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", "dev": true }, + "streamx": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", + "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "requires": { + "bare-events": "^2.2.0", + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -12358,6 +12963,11 @@ "which-boxed-primitive": "^1.0.2" } }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "universalify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", diff --git a/package.json b/package.json index 5132be73..759da629 100644 --- a/package.json +++ b/package.json @@ -47,12 +47,24 @@ "pako": "^2.0.2", "pngjs": "^6.0.0", "redis": "^4.6.13", - "sharp": "^0.31.3", + "sharp": "^0.32.6", "tga": "^1.0.4", "xml2js": "^0.4.23", "xmlbuilder2": "^3.0.2" }, "devDependencies": { + "@types/bmp-js": "^0.1.2", + "@types/cookie-parser": "^1.4.7", + "@types/express": "^4.17.21", + "@types/express-session": "^1.18.0", + "@types/fs-extra": "^11.0.4", + "@types/hashmap": "^2.3.4", + "@types/morgan": "^1.9.9", + "@types/multer": "^1.4.11", + "@types/node": "^20.12.12", + "@types/pako": "^2.0.3", + "@types/pngjs": "^6.0.0", + "@types/sharp": "^0.32.0", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", "bmp-js": "^0.1.0", diff --git a/src/database.js b/src/database.js deleted file mode 100644 index ca48e9bc..00000000 --- a/src/database.js +++ /dev/null @@ -1,548 +0,0 @@ -const mongoose = require('mongoose'); -const { FuzzySearch } = require('mongoose-fuzzy-search-next'); -const { mongoose: mongooseConfig } = require('../config.json'); -const { COMMUNITY } = require('./models/communities'); -const { CONTENT } = require('./models/content'); -const { CONVERSATION } = require('./models/conversation'); -const { ENDPOINT } = require('./models/endpoint'); -const { NOTIFICATION } = require('./models/notifications'); -const { POST } = require('./models/post'); -const { SETTINGS } = require('./models/settings'); -const { REPORT } = require('./models/report'); - -const { uri, database, options } = mongooseConfig; -const logger = require('./logger'); - -let connection; -mongoose.set('strictQuery', true); - -async function connect() { - await mongoose.connect(`${uri}/${database}`, options); - connection = mongoose.connection; - connection.on('connected', function () { - logger.info(`MongoDB connected ${this.name}`); - }); - connection.on('error', console.error.bind(console, 'connection error:')); - connection.on('close', () => { - connection.removeAllListeners(); - }); -} - -function verifyConnected() { - if (!connection) { - connect(); - } -} - -async function getCommunities(numberOfCommunities) { - verifyConnected(); - if (numberOfCommunities === -1) { - return COMMUNITY.find({ parent: null, type: [0,2] }); - } else { - return COMMUNITY.find({ parent: null, type: [0,2] }).limit(numberOfCommunities); - } -} - -async function getMostPopularCommunities(numberOfCommunities) { - verifyConnected(); - return COMMUNITY.find({ parent: null, type: 0 }).sort({followers: -1}).limit(numberOfCommunities); -} - -async function getNewCommunities(numberOfCommunities) { - verifyConnected(); - return COMMUNITY.find({ parent: null, type: 0 }).sort([['created_at', -1]]).limit(numberOfCommunities); -} - -async function getSubCommunities(communityID) { - verifyConnected(); - return COMMUNITY.find({ - parent: communityID - }); -} - -async function getCommunityByTitleID(title_id) { - verifyConnected(); - return COMMUNITY.findOne({ - title_id: title_id - }); -} - -async function getCommunityByID(community_id) { - verifyConnected(); - return COMMUNITY.findOne({ - olive_community_id: community_id - }); -} - -async function getTotalPostsByCommunity(community) { - verifyConnected(); - return POST.find({ - community_id: community.olive_community_id, - parent: null, - removed: false - }).countDocuments(); -} - -async function getPostByID(postID) { - verifyConnected(); - return POST.findOne({ - id: postID - }); -} - -async function getPostsByUserID(userID) { - verifyConnected(); - return POST.find({ - pid: userID, - parent: null, - removed: false - }); -} - -async function getPostReplies(postID, number) { - verifyConnected(); - return POST.find({ - parent: postID, - removed: false - }).limit(number); -} - -async function getDuplicatePosts(pid, post) { - verifyConnected(); - return POST.findOne({ - pid: pid, - body: post.body, - painting: post.painting, - screenshot: post.screenshot, - parent: null, - removed: false - }); -} - -async function getUserPostRepliesAfterTimestamp(post, numberOfPosts) { - verifyConnected(); - return POST.find({ - parent: post.pid, - created_at: { $lt: post.created_at }, - message_to_pid: null, - removed: false - }).limit(numberOfPosts); -} - -async function getNumberUserPostsByID(userID, number) { - verifyConnected(); - return POST.find({ - pid: userID, - parent: null, - message_to_pid: null, - removed: false - }).sort({ created_at: -1}).limit(number); -} - -async function getTotalPostsByUserID(userID) { - verifyConnected(); - return POST.find({ - pid: userID, - parent: null, - message_to_pid: null, - removed: false - }).countDocuments(); -} - -async function getHotPostsByCommunity(community, numberOfPosts) { - verifyConnected(); - return POST.find({ - community_id: community.olive_community_id, - parent: null, - removed: false - }).sort({empathy_count: -1}).limit(numberOfPosts); -} - -async function getNumberNewCommunityPostsByID(community, number) { - verifyConnected(); - return POST.find({ - community_id: community.olive_community_id, - parent: null, - removed: false - }).sort({ created_at: -1}).limit(number); -} - -async function getNumberPopularCommunityPostsByID(community, limit, offset) { - verifyConnected(); - return POST.find({ - community_id: community.olive_community_id, - parent: null, - removed: false - }).sort({ empathy_count: -1}).skip(offset).limit(limit); -} - -async function getNumberVerifiedCommunityPostsByID(community, limit, offset) { - verifyConnected(); - return POST.find({ - community_id: community.olive_community_id, - verified: true, - parent: null, - removed: false - }).sort({ created_at: -1}).skip(offset).limit(limit); -} - -async function getPostsByCommunity(community, numberOfPosts) { - verifyConnected(); - return POST.find({ - community_id: community.olive_community_id, - parent: null, - removed: false - }).limit(numberOfPosts); -} - -async function getPostsByCommunityKey(community, numberOfPosts, search_key) { - verifyConnected(); - return POST.find({ - community_id: community.olive_community_id, - search_key: search_key, - parent: null, - removed: false - }).limit(numberOfPosts); -} - -async function getNewPostsByCommunity(community, limit, offset) { - verifyConnected(); - return POST.find({ - community_id: community.olive_community_id, - parent: null, - removed: false - }).sort({ created_at: -1 }).skip(offset).limit(limit); -} - -async function getAllUserPosts(pid) { - verifyConnected(); - return POST.find({ - pid: pid, - message_to_pid: null - }); -} - -async function getRemovedUserPosts(pid) { - verifyConnected(); - return POST.find({ - pid: pid, - message_to_pid: null, - removed: true - }); -} - -async function getUserPostsAfterTimestamp(post, numberOfPosts) { - verifyConnected(); - return POST.find({ - pid: post.pid, - created_at: { $lt: post.created_at }, - parent: null, - message_to_pid: null, - removed: false - }).limit(numberOfPosts); -} - -async function getUserPostsOffset(pid, limit, offset) { - verifyConnected(); - return POST.find({ - pid: pid, - parent: null, - message_to_pid: null, - removed: false - }).skip(offset).limit(limit).sort({ created_at: -1}); -} - -async function getCommunityPostsAfterTimestamp(post, numberOfPosts) { - verifyConnected(); - return POST.find({ - community_id: post.community_id, - created_at: { $lt: post.created_at }, - parent: null, - removed: false - }).limit(numberOfPosts); -} - -async function getEndpoints() { - verifyConnected(); - return ENDPOINT.find({}); -} - -async function getEndPoint(accessLevel) { - verifyConnected(); - return ENDPOINT.findOne({ - server_access_level: accessLevel - }); -} - -async function getUsersSettings(numberOfUsers) { - verifyConnected(); - if (numberOfUsers === -1) { - return SETTINGS.find({}); - } else { - return SETTINGS.find({}).limit(numberOfUsers); - } -} - -async function getUsersContent(numberOfUsers, offset) { - verifyConnected(); - if (numberOfUsers === -1) { - return SETTINGS.find({}).skip(offset); - } else { - return SETTINGS.find({}).skip(offset).limit(numberOfUsers); - } -} - -async function getUserSettingsFuzzySearch(search_key, numberOfUsers, offset) { - verifyConnected(); - if (numberOfUsers === -1) { - return SETTINGS.find(FuzzySearch(['screen_name'], search_key)).skip(offset); - } else { - return SETTINGS.find(FuzzySearch(['screen_name'], search_key)).skip(offset).limit(numberOfUsers); - } -} - -async function getUserSettings(pid) { - verifyConnected(); - return SETTINGS.findOne({pid: pid}); -} - -async function getUserContent(pid) { - verifyConnected(); - return CONTENT.findOne({pid: pid}); -} - -async function getFollowingUsers(content) { - verifyConnected(); - return SETTINGS.find({ - pid: content.following_users - }); -} - -async function getFollowedUsers(content) { - verifyConnected(); - return SETTINGS.find({ - pid: content.followed_users - }); -} - -async function getNewsFeed(content, numberOfPosts) { - verifyConnected(); - return POST.find({ - $or: [ - {pid: content.followed_users}, - {pid: content.pid}, - {community_id: content.followed_communities}, - ], - parent: null, - message_to_pid: null, - removed: false - }).limit(numberOfPosts).sort({ created_at: -1}); -} - -async function getNewsFeedAfterTimestamp(content, numberOfPosts, post) { - verifyConnected(); - return POST.find({ - $or: [ - {pid: content.followed_users}, - {pid: content.pid}, - {community_id: content.followed_communities}, - ], - created_at: { $lt: post.created_at }, - parent: null, - message_to_pid: null, - removed: false - }).limit(numberOfPosts).sort({ created_at: -1}); -} - -async function getNewsFeedOffset(content, limit, offset) { - verifyConnected(); - return POST.find({ - $or: [ - {pid: content.followed_users}, - {pid: content.pid}, - {community_id: content.followed_communities}, - ], - parent: null, - message_to_pid: null, - removed: false - }).skip(offset).limit(limit).sort({ created_at: -1}); -} - -async function getConversations(pid) { - verifyConnected(); - return CONVERSATION.find({ - 'users.pid': pid - }).sort({ last_updated: -1}); -} - -async function getUnreadConversationCount(pid) { - verifyConnected(); - return CONVERSATION.find({ - 'users': { $elemMatch: { - 'pid': pid, - 'read': false - }} - - }).countDocuments(); -} - -async function getConversationByID(community_id) { - verifyConnected(); - return CONVERSATION.findOne({ - type: 3, - id: community_id - }); -} - -async function getConversationMessages(community_id, limit, offset) { - verifyConnected(); - return POST.find({ - community_id: community_id, - parent: null, - removed: false - }).sort({created_at: 1}).skip(offset).limit(limit); -} - -async function getConversationByUsers(pids) { - verifyConnected(); - return CONVERSATION.findOne({ - $and: [ - {'users.pid': pids[0]}, - {'users.pid': pids[1]} - ] - }); -} - -async function getLatestMessage(pid, pid2) { - verifyConnected(); - return POST.findOne({ - $or: [ - {pid: pid, message_to_pid: pid2}, - {pid: pid2, message_to_pid: pid} - ], - removed: false - }); -} - -async function getNotifications(pid, limit, offset) { - verifyConnected(); - return NOTIFICATION.find({ - pid: pid, - }).sort({lastUpdated: -1}).skip(offset).limit(limit); -} - -async function getNotification(pid, type, reference_id) { - verifyConnected(); - return NOTIFICATION.findOne({ - pid: pid, - type: type, - reference_id: reference_id - }); -} - -async function getLastNotification(pid) { - verifyConnected(); - return NOTIFICATION.findOne({ - pid: pid - }).sort({lastUpdated: -1}).limit(1); -} - -async function getUnreadNotificationCount(pid) { - verifyConnected(); - return NOTIFICATION.find({ - pid: pid, - read: false - }).countDocuments(); -} - -async function getAllReports(offset, limit) { - verifyConnected(); - return REPORT.find().sort({created_at: -1}).skip(offset).limit(limit); -} - -async function getAllOpenReports(offset, limit) { - verifyConnected(); - return REPORT.find({ resolved: false }).sort({created_at: -1}).skip(offset).limit(limit); -} - -async function getReportsByUser(pid, offset, limit) { - verifyConnected(); - return REPORT.find({ reported_by: pid }).sort({created_at: -1}).skip(offset).limit(limit); -} - -async function getReportsByPost(postID, offset, limit) { - verifyConnected(); - return REPORT.find({ post_id: postID }).sort({created_at: -1}).skip(offset).limit(limit); -} - -async function getDuplicateReports(pid, postID) { - verifyConnected(); - return REPORT.findOne({ - reported_by: pid, - post_id: postID - }); -} - -async function getReportById(id) { - verifyConnected(); - return REPORT.findById(id); -} - - -module.exports = { - connect, - getCommunities, - getMostPopularCommunities, - getNewCommunities, - getSubCommunities, - getCommunityByTitleID, - getCommunityByID, - getTotalPostsByCommunity, - getPostsByCommunity, - getHotPostsByCommunity, - getNumberNewCommunityPostsByID, - getNumberPopularCommunityPostsByID, - getNumberVerifiedCommunityPostsByID, - getNewPostsByCommunity, - getPostsByCommunityKey, - getPostsByUserID, - getPostReplies, - getUserPostRepliesAfterTimestamp, - getNumberUserPostsByID, - getTotalPostsByUserID, - getPostByID, - getDuplicatePosts, - getEndpoints, - getEndPoint, - getUserPostsAfterTimestamp, - getUserPostsOffset, - getCommunityPostsAfterTimestamp, - getNewsFeed, - getNewsFeedAfterTimestamp, - getNewsFeedOffset, - getFollowingUsers, - getFollowedUsers, - getConversations, - getConversationByID, - getConversationByUsers, - getConversationMessages, - getUnreadConversationCount, - getLatestMessage, - getUsersSettings, - getUsersContent, - getUserSettings, - getUserSettingsFuzzySearch, - getUserContent, - getNotifications, - getUnreadNotificationCount, - getNotification, - getLastNotification, - getAllUserPosts, - getRemovedUserPosts, - getAllReports, - getAllOpenReports, - getReportsByUser, - getReportsByPost, - getDuplicateReports, - getReportById -}; diff --git a/src/database.ts b/src/database.ts new file mode 100644 index 00000000..a97b6545 --- /dev/null +++ b/src/database.ts @@ -0,0 +1,582 @@ +import mongoose from 'mongoose'; +import { FuzzySearch } from 'mongoose-fuzzy-search-next'; +import { info } from '@/logger'; +import { COMMUNITY } from '@/models/communities'; +import { CONTENT } from '@/models/content'; +import { CONVERSATION } from '@/models/conversation'; +import { ENDPOINT } from '@/models/endpoint'; +import { NOTIFICATION } from '@/models/notifications'; +import { POST } from '@/models/post'; +import { SETTINGS } from '@/models/settings'; +import { REPORT } from '@/models/report'; +import type { HydratedCommunityDocument, ICommunity } from '@/types/mongoose/communities'; +import type { HydratedPostDocument, IPost } from '@/types/mongoose/post'; +import type { HydratedEndpointDocument } from '@/types/mongoose/endpoint'; +import type { HydratedSettingsDocument } from '@/types/mongoose/settings'; +import type { HydratedContentDocument, IContent } from '@/types/mongoose/content'; +import type { HydratedConversationDocument } from '@/types/mongoose/conversation'; +import type { HydratedNotificationDocument } from '@/types/mongoose/notifications'; +import type { HydratedReportDocument } from '@/types/mongoose/report'; +import { mongoose as mongooseConfig } from '../config.json'; + +const { uri, database, options } = mongooseConfig; + +let connection: mongoose.Connection; +mongoose.set('strictQuery', true); + +export async function connect(): Promise { + await mongoose.connect(`${uri}/${database}`, options as mongoose.ConnectOptions); + connection = mongoose.connection; + connection.on('connected', function (this: mongoose.Connection) { + info(`MongoDB connected ${this.name}`); + }); + // ? Should this use the logger? + connection.on('error', console.error.bind(console, 'connection error:')); + connection.on('close', () => { + connection.removeAllListeners(); + }); +} + +function verifyConnected(): void { + if (!connection) { + connect(); + } +} + +export async function getCommunities(numberOfCommunities: number): Promise { + verifyConnected(); + + if (numberOfCommunities === -1) { + return COMMUNITY.find({ parent: null, type: [0,2] }); + } else { + return COMMUNITY.find({ parent: null, type: [0,2] }).limit(numberOfCommunities); + } +} + +export async function getMostPopularCommunities(numberOfCommunities: number): Promise { + verifyConnected(); + + return COMMUNITY.find({ parent: null, type: 0 }).sort({followers: -1}).limit(numberOfCommunities); +} + +export async function getNewCommunities(numberOfCommunities: number): Promise { + verifyConnected(); + + return COMMUNITY.find({ parent: null, type: 0 }).sort([['created_at', -1]]).limit(numberOfCommunities); +} + +export async function getSubCommunities(communityID: string): Promise { + verifyConnected(); + + return COMMUNITY.find({ + parent: communityID + }); +} + +export async function getCommunityByTitleID(title_id: string): Promise { + verifyConnected(); + + return COMMUNITY.findOne({ + title_id: title_id + }); +} + +export async function getCommunityByID(community_id: string): Promise { + verifyConnected(); + + return COMMUNITY.findOne({ + olive_community_id: community_id + }); +} + +export async function getTotalPostsByCommunity(community: ICommunity): Promise { + verifyConnected(); + + return POST.find({ + community_id: community.olive_community_id, + parent: null, + removed: false + }).countDocuments(); +} + +export async function getPostByID(postID: string): Promise { + verifyConnected(); + + return POST.findOne({ + id: postID + }); +} + +export async function getPostsByUserID(userID: number): Promise { + verifyConnected(); + + return POST.find({ + pid: userID, + parent: null, + removed: false + }); +} + +export async function getPostReplies(postID: string, number: number): Promise { + verifyConnected(); + + return POST.find({ + parent: postID, + removed: false + }).limit(number); +} + +export async function getDuplicatePosts(pid: number, body: string, painting: string, screenshot: string): Promise { + verifyConnected(); + + return POST.findOne({ + pid: pid, + body: body, + painting: painting, + screenshot: screenshot, + parent: null, + removed: false + }); +} + +export async function getUserPostRepliesAfterTimestamp(post: IPost, numberOfPosts: number): Promise { + verifyConnected(); + + return POST.find({ + parent: post.pid, + created_at: { $lt: post.created_at }, + message_to_pid: null, + removed: false + }).limit(numberOfPosts); +} + +export async function getNumberUserPostsByID(userID: number, number: number): Promise { + verifyConnected(); + + return POST.find({ + pid: userID, + parent: null, + message_to_pid: null, + removed: false + }).sort({ created_at: -1}).limit(number); +} + +export async function getTotalPostsByUserID(userID: number): Promise { + verifyConnected(); + + return POST.find({ + pid: userID, + parent: null, + message_to_pid: null, + removed: false + }).countDocuments(); +} + +export async function getHotPostsByCommunity(community: ICommunity, numberOfPosts: number): Promise { + verifyConnected(); + + return POST.find({ + community_id: community.olive_community_id, + parent: null, + removed: false + }).sort({empathy_count: -1}).limit(numberOfPosts); +} + +export async function getNumberNewCommunityPostsByID(community: ICommunity, number: number): Promise { + verifyConnected(); + + return POST.find({ + community_id: community.olive_community_id, + parent: null, + removed: false + }).sort({ created_at: -1}).limit(number); +} + +export async function getNumberPopularCommunityPostsByID(community: ICommunity, limit?: number, offset?: number): Promise { + verifyConnected(); + + const options = { + limit, + offset, + sort: { empath_count: -1 } + }; + + return POST.find({ + community_id: community.olive_community_id, + parent: null, + removed: false + }, {}, options); +} + +export async function getNumberVerifiedCommunityPostsByID(community: ICommunity, limit?: number, offset?: number): Promise { + verifyConnected(); + + const options = { + limit, + offset, + sort: { created_at: -1 } + }; + + return POST.find({ + community_id: community.olive_community_id, + verified: true, + parent: null, + removed: false + }, {}, options); +} + +export async function getPostsByCommunity(community: ICommunity, numberOfPosts: number): Promise { + verifyConnected(); + + return POST.find({ + community_id: community.olive_community_id, + parent: null, + removed: false + }).limit(numberOfPosts); +} + +export async function getPostsByCommunityKey(community: ICommunity, numberOfPosts: number, search_key: string): Promise { + verifyConnected(); + + return POST.find({ + community_id: community.olive_community_id, + search_key: search_key, + parent: null, + removed: false + }).limit(numberOfPosts); +} + +export async function getNewPostsByCommunity(community: ICommunity, limit?: number, offset?: number): Promise { + verifyConnected(); + + const options = { + limit, + offset, + sort: { created_at: -1 } + }; + + return POST.find({ + community_id: community.olive_community_id, + parent: null, + removed: false + }, {}, options); +} + +export async function getAllUserPosts(pid: number): Promise { + verifyConnected(); + + return POST.find({ + pid: pid, + message_to_pid: null + }); +} + +export async function getRemovedUserPosts(pid: number): Promise { + verifyConnected(); + + return POST.find({ + pid: pid, + message_to_pid: null, + removed: true + }); +} + +export async function getUserPostsAfterTimestamp(post: IPost, numberOfPosts: number): Promise { + verifyConnected(); + + return POST.find({ + pid: post.pid, + created_at: { $lt: post.created_at }, + parent: null, + message_to_pid: null, + removed: false + }).limit(numberOfPosts); +} + +export async function getUserPostsOffset(pid: number, limit?: number, offset?: number): Promise { + verifyConnected(); + + const options = { + limit, + offset, + sort: { created_at: -1 } + }; + + return POST.find({ + pid: pid, + parent: null, + message_to_pid: null, + removed: false + }, {}, options); +} + +export async function getCommunityPostsAfterTimestamp(post: IPost, numberOfPosts: number): Promise { + verifyConnected(); + + return POST.find({ + community_id: post.community_id, + created_at: { $lt: post.created_at }, + parent: null, + removed: false + }).limit(numberOfPosts); +} + +export async function getEndpoints(): Promise { + verifyConnected(); + + return ENDPOINT.find({}); +} + +export async function getEndPoint(accessLevel: string): Promise { + verifyConnected(); + + return ENDPOINT.findOne({ + server_access_level: accessLevel + }); +} + +export async function getUsersSettings(numberOfUsers: number): Promise { + verifyConnected(); + + if (numberOfUsers === -1) { + return SETTINGS.find({}); + } else { + return SETTINGS.find({}).limit(numberOfUsers); + } +} + +export async function getUsersContent(numberOfUsers: number, offset: number): Promise { + verifyConnected(); + + if (numberOfUsers === -1) { + return SETTINGS.find({}).skip(offset); + } else { + return SETTINGS.find({}).skip(offset).limit(numberOfUsers); + } +} + +export async function getUserSettingsFuzzySearch(search_key: string, numberOfUsers?: number, offset?: number): Promise { + verifyConnected(); + + const options = { + offset, + limit: numberOfUsers + }; + + return SETTINGS.find(FuzzySearch(['screen_name'], search_key), {}, options); +} + +export async function getUserSettings(pid: number): Promise { + verifyConnected(); + + return SETTINGS.findOne({pid: pid}); +} + +export async function getUserContent(pid: number): Promise { + verifyConnected(); + + return CONTENT.findOne({pid: pid}); +} + +export async function getFollowingUsers(content: IContent): Promise { + verifyConnected(); + + return SETTINGS.find({ + pid: content.following_users + }); +} + +export async function getFollowedUsers(content: IContent): Promise { + verifyConnected(); + + return SETTINGS.find({ + pid: content.followed_users + }); +} + +export async function getNewsFeed(content: IContent, numberOfPosts: number): Promise { + verifyConnected(); + + return POST.find({ + $or: [ + { pid: content.followed_users }, + { pid: content.pid }, + { community_id: content.followed_communities }, + ], + parent: null, + message_to_pid: null, + removed: false + }).limit(numberOfPosts).sort({ created_at: -1}); +} + +export async function getNewsFeedAfterTimestamp(content: IContent, numberOfPosts: number, post: IPost): Promise { + verifyConnected(); + + return POST.find({ + $or: [ + {pid: content.followed_users}, + {pid: content.pid}, + {community_id: content.followed_communities}, + ], + created_at: { $lt: post.created_at }, + parent: null, + message_to_pid: null, + removed: false + }).limit(numberOfPosts).sort({ created_at: -1}); +} + +export async function getNewsFeedOffset(content: IContent, limit: number, offset: number): Promise { + verifyConnected(); + + return POST.find({ + $or: [ + {pid: content.followed_users}, + {pid: content.pid}, + {community_id: content.followed_communities}, + ], + parent: null, + message_to_pid: null, + removed: false + }).skip(offset).limit(limit).sort({ created_at: -1}); +} + +export async function getConversations(pid: number): Promise { + verifyConnected(); + + return CONVERSATION.find({ + 'users.pid': pid + }).sort({ last_updated: -1}); +} + +export async function getUnreadConversationCount(pid: number): Promise { + verifyConnected(); + + return CONVERSATION.find({ + 'users': { $elemMatch: { + 'pid': pid, + 'read': false + }} + + }).countDocuments(); +} + +export async function getConversationByID(conversation_id: string): Promise { + verifyConnected(); + + return CONVERSATION.findOne({ + type: 3, + id: conversation_id + }); +} + +export async function getConversationMessages(community_id: number, limit: number, offset: number): Promise { + verifyConnected(); + + return POST.find({ + community_id: community_id, + parent: null, + removed: false + }).sort({created_at: 1}).skip(offset).limit(limit); +} + +export async function getConversationByUsers(pids: number[]): Promise { + verifyConnected(); + + return CONVERSATION.findOne({ + $and: [ + {'users.pid': pids[0]}, + {'users.pid': pids[1]} + ] + }); +} + +export async function getLatestMessage(pid: number, pid2: number): Promise { + verifyConnected(); + + return POST.findOne({ + $or: [ + {pid: pid, message_to_pid: pid2}, + {pid: pid2, message_to_pid: pid} + ], + removed: false + }); +} + +export async function getNotifications(pid: number, limit: number, offset: number): Promise { + verifyConnected(); + + return NOTIFICATION.find({ + pid: pid, + }).sort({lastUpdated: -1}).skip(offset).limit(limit); +} + +export async function getNotification(pid: number, type: string, reference_id: number): Promise { + verifyConnected(); + + return NOTIFICATION.findOne({ + pid: pid, + type: type, + reference_id: reference_id + }); +} + +export async function getLastNotification(pid: number): Promise { + verifyConnected(); + + return NOTIFICATION.findOne({ + pid: pid + }).sort({lastUpdated: -1}).limit(1); +} + +export async function getUnreadNotificationCount(pid: number): Promise { + verifyConnected(); + + return NOTIFICATION.find({ + pid: pid, + read: false + }).countDocuments(); +} + +export async function getAllReports(offset: number, limit: number): Promise { + verifyConnected(); + + return REPORT.find().sort({created_at: -1}).skip(offset).limit(limit); +} + +export async function getAllOpenReports(offset?: number, limit?: number): Promise { + verifyConnected(); + + const options = { + sort: { created_at: -1 }, + offset, + limit + }; + + return REPORT.find({ resolved: false }, {}, options); +} + +export async function getReportsByUser(pid: number, offset: number, limit: number): Promise { + verifyConnected(); + + return REPORT.find({ reported_by: pid }).sort({created_at: -1}).skip(offset).limit(limit); +} + +export async function getReportsByPost(postID: number, offset: number, limit: number): Promise { + verifyConnected(); + + return REPORT.find({ post_id: postID }).sort({created_at: -1}).skip(offset).limit(limit); +} + +export async function getDuplicateReports(pid: number, postID: number): Promise { + verifyConnected(); + return REPORT.findOne({ + reported_by: pid, + post_id: postID + }); +} + +export async function getReportById(id: number): Promise { + verifyConnected(); + + return REPORT.findById(id); +} \ No newline at end of file diff --git a/src/logger.js b/src/logger.ts similarity index 81% rename from src/logger.js rename to src/logger.ts index 60b92d85..a9f0b1d5 100644 --- a/src/logger.js +++ b/src/logger.ts @@ -1,5 +1,7 @@ -const fs = require('fs-extra'); -require('colors'); +import fs from 'fs-extra'; +import colors from 'colors'; + +colors.enable(); const root = __dirname; fs.ensureDirSync(`${root}/logs`); @@ -11,9 +13,9 @@ const streams = { warn: fs.createWriteStream(`${root}/logs/warn.log`), info: fs.createWriteStream(`${root}/logs/info.log`), audit: fs.createWriteStream(`${root}/logs/audit.log`), -}; +} as const; -function success(input) { +export function success(input: string): void { const time = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`; streams.success.write(`${input}\n`); @@ -21,7 +23,7 @@ function success(input) { console.log(`${input}`.green.bold); } -function error(input) { +export function error(input: string): void { const time = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`; streams.error.write(`${input}\n`); @@ -29,7 +31,7 @@ function error(input) { console.log(`${input}`.red.bold); } -function warn(input) { +export function warn(input: string): void { const time = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`; streams.warn.write(`${input}\n`); @@ -37,7 +39,7 @@ function warn(input) { console.log(`${input}`.yellow.bold); } -function info(input) { +export function info(input: string): void { const time = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`; streams.info.write(`${input}\n`); @@ -45,7 +47,7 @@ function info(input) { console.log(`${input}`.cyan.bold); } -function audit(input) { +export function audit(input: string): void { const time = new Date(); input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [Audit]: ${input}`; streams.audit.write(`${input}\n`); @@ -53,7 +55,7 @@ function audit(input) { console.log(`${input}`.white.bold); } -module.exports = { +export default { success, error, warn, diff --git a/src/middleware/checkBan.js b/src/middleware/checkBan.ts similarity index 53% rename from src/middleware/checkBan.js rename to src/middleware/checkBan.ts index e438c0bb..df60a511 100644 --- a/src/middleware/checkBan.js +++ b/src/middleware/checkBan.ts @@ -1,17 +1,17 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable @typescript-eslint/no-var-requires */ -const config = require('../../config.json'); -const moment = require('moment/moment'); -const db = require('../database'); +import moment from 'moment/moment'; +import * as db from '@/database'; +import type { Request, Response, NextFunction } from 'express'; +import config from '../../config.json'; -async function checkBan(request, response, next) { +export async function checkBan(request: Request, response: Response, next: NextFunction): Promise { if (!request.user && !request.guest_access && request.path !== '/login') { - return response.status(401).send('Ban Check Failed: No user or guest access'); + response.status(401).send('Ban Check Failed: No user or guest access'); + return; } else if (!request.user && (request.guest_access || request.path === '/login')) { return next(); } - if (config.server_environment !== 'prod' && request.user.serverAccessLevel !== 'test' && request.user.serverAccessLevel !== 'dev') { + if (config.server_environment !== 'prod' && request.user?.serverAccessLevel !== 'test' && request.user?.serverAccessLevel !== 'dev') { response.status(500); if (request.directory === 'web') { return response.render('web/login.ejs', {toast: 'No access. Must be tester or dev', cdnURL: config.CDN_domain,}); @@ -23,7 +23,7 @@ async function checkBan(request, response, next) { } } // Set moderator status - request.moderator = request.user.accessLevel == 2 || request.user.accessLevel == 3; + request.moderator = request.user?.accessLevel == 2 || request.user?.accessLevel == 3; const user = await db.getUserSettings(request.pid); if (user && moment(user.ban_lift_date) <= moment() && user.account_status !== 3) { user.account_status = 0; @@ -31,18 +31,18 @@ async function checkBan(request, response, next) { } // This includes ban checks for both Juxt specifically and the account server, ideally this should be squashed // assuming we support more gradual bans on PNID's - if (user && (user.account_status < 0 || user.account_status > 1 || request.user.accessLevel < 0)) { + if (user && (user.account_status < 0 || user.account_status > 1 || (request.user?.accessLevel ?? 0) < 0)) { if (request.directory === 'web') { let banMessage = ''; switch (user.account_status) { case 2: - banMessage = `${request.user.username} has been banned until: ${ moment(user.ban_lift_date) }. \n\nReason: ${user.ban_reason}. \n\nIf you have any questions contact the developers in the Discord server.`; + banMessage = `${request.user?.username} has been banned until: ${ moment(user.ban_lift_date) }. \n\nReason: ${user.ban_reason}. \n\nIf you have any questions contact the developers in the Discord server.`; break; case 3: - banMessage = `${request.user.username} has been banned forever. \n\nReason: ${user.ban_reason}. \n\nIf you have any questions contact the developers in the Discord server.`; + banMessage = `${request.user?.username} has been banned forever. \n\nReason: ${user.ban_reason}. \n\nIf you have any questions contact the developers in the Discord server.`; break; default: - banMessage = `${request.user.username} has been banned. \n\nIf you have any questions contact the developers in the Discord server.`; + banMessage = `${request.user?.username} has been banned. \n\nIf you have any questions contact the developers in the Discord server.`; } return response.render('web/login.ejs', {toast: banMessage, cdnURL: config.CDN_domain,}); } else { @@ -52,12 +52,10 @@ async function checkBan(request, response, next) { cdnURL: config.CDN_domain, lang: request.lang, pid: request.pid, - PNID: request.user.username, - networkBan: request.user.accessLevel < 0 + PNID: request.user?.username, + networkBan: (request.user?.accessLevel ?? 0) < 0 }); } } next(); -} - -module.exports = checkBan; \ No newline at end of file +} \ No newline at end of file diff --git a/src/middleware/consoleAuth.js b/src/middleware/consoleAuth.ts similarity index 52% rename from src/middleware/consoleAuth.js rename to src/middleware/consoleAuth.ts index 1390be29..d3f04f55 100644 --- a/src/middleware/consoleAuth.js +++ b/src/middleware/consoleAuth.ts @@ -1,71 +1,77 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable @typescript-eslint/no-var-requires */ -/* eslint-disable require-atomic-updates */ -const config = require('../../config.json'); -const util = require('../util'); +import * as util from '@/util'; +import type { User } from '@/types/common/user'; +import type { Request, Response, NextFunction } from 'express'; +import config from '../../config.json'; + +export async function auth(request: Request, response: Response, next: NextFunction): Promise { + + let pid: number | null; + let user: User | null; -async function auth(request, response, next) { // Get pid and fetch user data if (request.session && request.session.user && request.session.pid && !request.isWrite) { - request.user = request.session.user; - request.pid = request.session.pid; + user = request.session.user; + pid = request.session.pid; } else { - request.pid = request.headers['x-nintendo-servicetoken'] ? await util.processServiceToken(request.headers['x-nintendo-servicetoken']) : null; - request.user = request.pid ? await util.getUserDataFromPid(request.pid) : null; + pid = request.get('x-nintendo-servicetoken') ? await util.processServiceToken(request.get('x-nintendo-servicetoken')) : null; + user = pid ? await util.getUserDataFromPid(pid) : null; - request.session.user = request.user; - request.session.pid = request.pid; + request.session.user = user; + request.session.pid = pid; } // Set headers - request.paramPackData = request.headers['x-nintendo-parampack'] ? util.decodeParamPack(request.headers['x-nintendo-parampack']) : null; + const encodedParamPack = request.get('x-nintendo-parampack'); + request.paramPackData = encodedParamPack ? util.decodeParamPack(encodedParamPack) : null; response.header('X-Nintendo-WhiteList', config.whitelist); - if (!request.user) { + if (!user) { try { request.user = await util.getUserDataFromToken(request.cookies.access_token); request.pid = request.user.pid; if (request.user.accessLevel !== 3) { - request.user = null; - request.pid = null; + user = null; + pid = null; } } catch (e) { console.log(e); - request.user = null; - request.pid = null; + user = null; + pid = null; } } // This section includes checks if a user is a developer and adds exceptions for these cases - if (!request.pid) { + if (!pid) { return response.render('portal/partials/ban_notification.ejs', { user: null, error: 'Unable to parse service token. Are you using a Nintendo Network ID?' }); } - if (!request.user) { + request.pid = pid; + + if (!user) { return response.render('portal/partials/ban_notification.ejs', { user: null, error: 'Unable to fetch user data. Please try again later.' }); } + request.user = user; + if (request.user.accessLevel < 3 && !request.paramPackData) { return response.render('portal/partials/ban_notification.ejs', { user: null, error: 'Missing auth headers' }); } - const userAgent = request.headers['user-agent']; - if (request.user.accessLevel < 3 && (request.cookies.access_token || (!userAgent.includes('Nintendo WiiU') && !userAgent.includes('Nintendo 3DS')))) { + const userAgent = request.get('user-agent'); + if (request.user.accessLevel < 3 && (request.cookies.access_token || (!userAgent?.includes('Nintendo WiiU') && !userAgent?.includes('Nintendo 3DS')))) { return response.render('portal/partials/ban_notification.ejs', { user: null, error: 'Invalid authentication method used.' }); } - request.lang = util.processLanguage(request.paramPackData); + request.lang = util.processLanguage(request?.paramPackData ?? undefined); //console.timeEnd(`Time Request ${request.timerDate}`); return next(); -} - -module.exports = auth; +} \ No newline at end of file diff --git a/src/middleware/detectVersion.js b/src/middleware/detectVersion.ts similarity index 65% rename from src/middleware/detectVersion.js rename to src/middleware/detectVersion.ts index 750b6bb6..4a4a96e5 100644 --- a/src/middleware/detectVersion.js +++ b/src/middleware/detectVersion.ts @@ -1,6 +1,7 @@ -const util = require('../util'); +import * as util from '@/util'; +import type { Request, Response, NextFunction } from 'express'; -async function detectVersion(request, response, next) { +export async function detectVersion(request: Request, response: Response, next: NextFunction): Promise { request.timerDate = Date.now(); console.time(`Time Request ${request.timerDate}`); // Check the domain and set the directory @@ -16,8 +17,6 @@ async function detectVersion(request, response, next) { next(); } -function includes(request, domain) { +function includes(request: Request, domain: string): boolean { return request.subdomains.findIndex(element => element.includes(domain)) !== -1; -} - -module.exports = detectVersion; +} \ No newline at end of file diff --git a/src/middleware/discovery.js b/src/middleware/discovery.ts similarity index 78% rename from src/middleware/discovery.js rename to src/middleware/discovery.ts index 3e8e801b..7ec64858 100644 --- a/src/middleware/discovery.js +++ b/src/middleware/discovery.ts @@ -1,7 +1,8 @@ -const config = require('../../config.json'); -const db = require('../database'); +import * as db from '@/database'; +import type { Request, Response, NextFunction } from 'express'; +import config from '../../config.json'; -async function checkDiscovery(request, response, next) { +export async function checkDiscovery(request: Request, response: Response, next: NextFunction): Promise { const discovery = await db.getEndPoint(config.server_environment); if (!discovery || discovery.status !== 0) { @@ -32,6 +33,4 @@ async function checkDiscovery(request, response, next) { } next(); -} - -module.exports = checkDiscovery; \ No newline at end of file +} \ No newline at end of file diff --git a/src/middleware/staticFiles.js b/src/middleware/staticFiles.ts similarity index 68% rename from src/middleware/staticFiles.js rename to src/middleware/staticFiles.ts index c71c8b78..403d9e99 100644 --- a/src/middleware/staticFiles.js +++ b/src/middleware/staticFiles.ts @@ -1,6 +1,7 @@ -const util = require('../util'); +import * as util from '@/util'; +import type { Request, Response, NextFunction } from 'express'; -async function staticFiles(request, response, next) { +export async function staticFiles(request: Request, response: Response, next: NextFunction): Promise { // Web files if (isStartOfPath(request.path, '/css/') || isStartOfPath(request.path, '/fonts/') || @@ -21,13 +22,11 @@ async function staticFiles(request, response, next) { } else if (request.path === '/') { return response.redirect('/titles/show'); } else { - return response.sendStatus(404); + response.sendStatus(404); + return; } } -function isStartOfPath(path, value) { +function isStartOfPath(path: string, value: string): boolean { return path.indexOf(value) === 0; -} - - -module.exports = staticFiles; +} \ No newline at end of file diff --git a/src/middleware/webAuth.js b/src/middleware/webAuth.ts similarity index 80% rename from src/middleware/webAuth.js rename to src/middleware/webAuth.ts index 46dcac68..0c0ab30b 100644 --- a/src/middleware/webAuth.js +++ b/src/middleware/webAuth.ts @@ -1,8 +1,7 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable @typescript-eslint/no-var-requires */ -const util = require('../util'); +import * as util from '@/util'; +import type { Request, Response, NextFunction } from 'express'; -async function webAuth(request, response, next) { +export async function webAuth(request: Request, response: Response, next: NextFunction): Promise { // Get pid and fetch user data if (request.session && request.session.user && request.session.pid && !request.isWrite && request.cookies.access_token) { @@ -16,7 +15,7 @@ async function webAuth(request, response, next) { request.session.user = request.user; request.session.pid = request.pid; } catch (e) { - const domain = request.get('host').replace('juxt', ''); + const domain = request.get('host')?.replace('juxt', ''); response.clearCookie('access_token', {domain: domain, path: '/'}); response.clearCookie('refresh_token', {domain: domain, path: '/'}); response.clearCookie('token_type', {domain: domain, path: '/'}); @@ -56,13 +55,10 @@ async function webAuth(request, response, next) { return next(); } -function isStartOfPath(path, value) { +function isStartOfPath(path: string, value: string): boolean { return path.indexOf(value) === 0; } -BigInt.prototype['toJSON'] = function () { +(BigInt as any).prototype['toJSON'] = function (): string { return this.toString(); -}; - - -module.exports = webAuth; +}; \ No newline at end of file diff --git a/src/models/communities.js b/src/models/communities.ts similarity index 63% rename from src/models/communities.js rename to src/models/communities.ts index 411064a7..dffcb993 100644 --- a/src/models/communities.js +++ b/src/models/communities.ts @@ -1,6 +1,7 @@ -const { Schema, model } = require('mongoose'); +import { Schema, model } from 'mongoose'; +import type { CommunityModel, HydratedCommunityDocument, ICommunity, ICommunityMethods, IPermissions, PermissionsModel } from '@/types/mongoose/communities'; -const PermissionsSchema = new Schema({ +export const PermissionsSchema = new Schema({ open: { type: Boolean, default: true @@ -19,7 +20,7 @@ const PermissionsSchema = new Schema({ }, }); -const CommunitySchema = new Schema({ +export const CommunitySchema = new Schema({ platform_id: Number, name: String, description: String, @@ -76,37 +77,32 @@ const CommunitySchema = new Schema({ permissions: PermissionsSchema, }); -CommunitySchema.methods.upEmpathy = async function() { +CommunitySchema.method('upEmpathy', async function() { const empathy = this.get('empathy_count'); this.set('empathy_count', empathy + 1); await this.save(); -}; +}); -CommunitySchema.methods.downEmpathy = async function() { +CommunitySchema.method('downEmpathy', async function(): Promise { const empathy = this.get('empathy_count'); this.set('empathy_count', empathy - 1); await this.save(); -}; +}); -CommunitySchema.methods.upFollower = async function() { +CommunitySchema.method('upFollower', async function(): Promise { const followers = this.get('followers'); this.set('followers', followers + 1); await this.save(); -}; +}); -CommunitySchema.methods.downFollower = async function() { +CommunitySchema.method('downFollower', async function(): Promise { const followers = this.get('followers'); this.set('followers', followers - 1); await this.save(); -}; - -const COMMUNITY = model('COMMUNITY', CommunitySchema); +}); -module.exports = { - CommunitySchema, - COMMUNITY -}; +export const COMMUNITY = model('COMMUNITY', CommunitySchema); \ No newline at end of file diff --git a/src/models/content.js b/src/models/content.js deleted file mode 100644 index 19feb859..00000000 --- a/src/models/content.js +++ /dev/null @@ -1,60 +0,0 @@ -const { Schema, model } = require('mongoose'); - -const ContentSchema = new Schema({ - pid: Number, - followed_communities: { - type: [String], - default: [0] - }, - followed_users: { - type: [Number], - default: [0] - }, - following_users: { - type: [Number], - default: [0] - }, -}); - -ContentSchema.methods.addToCommunities = async function(postID) { - const communities = this.get('followed_communities'); - communities.addToSet(postID); - await this.save(); -}; - -ContentSchema.methods.removeFromCommunities = async function(postID) { - const communities = this.get('followed_communities'); - communities.pull(postID); - await this.save(); -}; - -ContentSchema.methods.addToUsers = async function(postID) { - const users = this.get('followed_users'); - users.addToSet(postID); - await this.save(); -}; - -ContentSchema.methods.removeFromUsers = async function(postID) { - const users = this.get('followed_users'); - users.pull(postID); - await this.save(); -}; - -ContentSchema.methods.addToFollowers = async function(postID) { - const users = this.get('following_users'); - users.addToSet(postID); - await this.save(); -}; - -ContentSchema.methods.removeFromFollowers = async function(postID) { - const users = this.get('following_users'); - users.pull(postID); - await this.save(); -}; - -const CONTENT = model('CONTENT', ContentSchema); - -module.exports = { - ContentSchema, - CONTENT -}; diff --git a/src/models/content.ts b/src/models/content.ts new file mode 100644 index 00000000..1216de11 --- /dev/null +++ b/src/models/content.ts @@ -0,0 +1,56 @@ +import { Schema, model } from 'mongoose'; +import type { ContentModel, HydratedContentDocument, IContent, IContentMethods } from '@/types/mongoose/content'; + +export const ContentSchema = new Schema({ + pid: Number, + followed_communities: { + type: [String], + default: [0] + }, + followed_users: { + type: [Number], + default: [0] + }, + following_users: { + type: [Number], + default: [0] + }, +}); + +ContentSchema.method('addToCommunities', async function(postID: string) { + const communities = this.get('followed_communities'); + communities.addToSet(postID); + await this.save(); +}); + +ContentSchema.method('removeFromCommunities', async function(postID: string): Promise { + const communities = this.get('followed_communities'); + communities.pull(postID); + await this.save(); +}); + +ContentSchema.method('addToUsers', async function(postID: number): Promise { + const users = this.get('followed_users'); + users.addToSet(postID); + await this.save(); +}); + +ContentSchema.method('removeFromUsers', async function(postID: number): Promise { + const users = this.get('followed_users'); + users.pull(postID); + await this.save(); +}); + +ContentSchema.method('addToFollowers', async function(postID: number): Promise { + const users = this.get('following_users'); + users.addToSet(postID); + await this.save(); +}); + +ContentSchema.method('removeFromFollowers', async function(postID: number): Promise { + const users = this.get('following_users'); + users.pull(postID); + await this.save(); +}); + +export const CONTENT = model('CONTENT', ContentSchema); \ No newline at end of file diff --git a/src/models/conversation.js b/src/models/conversation.js deleted file mode 100644 index 8911ce7d..00000000 --- a/src/models/conversation.js +++ /dev/null @@ -1,60 +0,0 @@ -const { Schema, model } = require('mongoose'); -const moment = require('moment'); -const snowflake = require('node-snowflake').Snowflake; - -const user = new Schema({ - pid: Number, - official: { - type: Boolean, - default: false - }, - read: { - type: Boolean, - default: true - } -}); - -const ConversationSchema = new Schema({ - id: { - type: String, - default: snowflake.nextId() - }, - created_at: { - type: Date, - default: new Date(), - }, - last_updated: { - type: Date, - default: new Date(), - }, - message_preview: { - type: String, - default: '' - }, - users: [user] -}); - -ConversationSchema.methods.newMessage = async function(message, senderPID) { - this.last_updated = new Date(); - this.message_preview = message; - const sender = this.users.find(user => user.pid === senderPID); - if (sender) { - sender.read = false; - } - await this.save(); -}; - -ConversationSchema.methods.markAsRead = async function(receiverPID) { - const receiver = this.users.find(user => user.pid === receiverPID); - if (receiver) { - receiver.read = true; - } - await this.save(); -}; - -const CONVERSATION = model('CONVERSATION', ConversationSchema); - -module.exports = { - ConversationSchema: ConversationSchema, - CONVERSATION: CONVERSATION -}; diff --git a/src/models/conversation.ts b/src/models/conversation.ts new file mode 100644 index 00000000..625167d0 --- /dev/null +++ b/src/models/conversation.ts @@ -0,0 +1,55 @@ +import { Schema, model } from 'mongoose'; +import { Snowflake } from 'node-snowflake'; +import type { ConversationModel, HydratedConversationDocument, IConversation, IConversationMethods, IUser, UserModel } from '@/types/mongoose/conversation'; + +const user = new Schema({ + pid: Number, + official: { + type: Boolean, + default: false + }, + read: { + type: Boolean, + default: true + } +}); + +export const ConversationSchema = new Schema({ + id: { + type: String, + default: Snowflake.nextId() + }, + created_at: { + type: Date, + default: new Date(), + }, + last_updated: { + type: Date, + default: new Date(), + }, + message_preview: { + type: String, + default: '' + }, + users: [user] +}); + +ConversationSchema.method('newMessage', async function(message: string, senderPID: number) { + this.last_updated = new Date(); + this.message_preview = message; + const sender = this.users.find(user => user.pid === senderPID); + if (sender) { + sender.read = false; + } + await this.save(); +}); + +ConversationSchema.method('markAsRead', async function(receiverPID: number) { + const receiver = this.users.find(user => user.pid === receiverPID); + if (receiver) { + receiver.read = true; + } + await this.save(); +}); + +export const CONVERSATION = model('CONVERSATION', ConversationSchema); \ No newline at end of file diff --git a/src/models/endpoint.js b/src/models/endpoint.js deleted file mode 100644 index af437afe..00000000 --- a/src/models/endpoint.js +++ /dev/null @@ -1,23 +0,0 @@ -const { Schema, model } = require('mongoose'); - -const endpointSchema = new Schema({ - status: Number, - server_access_level: String, - topics: Boolean, - guest_access: Boolean, - new_users: { - type: Boolean, - default: true, - }, - host: String, - api_host: String, - portal_host: String, - n3ds_host: String, -}); - -const ENDPOINT = model('ENDPOINT', endpointSchema); - -module.exports = { - endpointSchema, - ENDPOINT, -}; diff --git a/src/models/endpoint.ts b/src/models/endpoint.ts new file mode 100644 index 00000000..fae5280e --- /dev/null +++ b/src/models/endpoint.ts @@ -0,0 +1,19 @@ +import { Schema, model } from 'mongoose'; +import type { EndpointModel, IEndpoint } from '@/types/mongoose/endpoint'; + +export const endpointSchema = new Schema({ + status: Number, + server_access_level: String, + topics: Boolean, + guest_access: Boolean, + new_users: { + type: Boolean, + default: true, + }, + host: String, + api_host: String, + portal_host: String, + n3ds_host: String, +}); + +export const ENDPOINT = model('ENDPOINT', endpointSchema); \ No newline at end of file diff --git a/src/models/notifications.js b/src/models/notifications.js deleted file mode 100644 index 5e603fe7..00000000 --- a/src/models/notifications.js +++ /dev/null @@ -1,26 +0,0 @@ -const { Schema, model } = require('mongoose'); - -const NotificationSchema = new Schema({ - pid: String, - type: String, - link: String, - objectID: String, - users: [{ - user: String, - timestamp: Date - }], - read: Boolean, - lastUpdated: Date -}); - -NotificationSchema.methods.markRead = async function() { - this.set('read', true); - await this.save(); -}; - -const NOTIFICATION = model('NOTIFICATION', NotificationSchema); - -module.exports = { - NotificationSchema, - NOTIFICATION -}; diff --git a/src/models/notifications.ts b/src/models/notifications.ts new file mode 100644 index 00000000..fca70918 --- /dev/null +++ b/src/models/notifications.ts @@ -0,0 +1,22 @@ +import { Schema, model } from 'mongoose'; +import type { HydratedNotificationDocument, INotification, INotificationMethods, NotificationModel } from '@/types/mongoose/notifications'; + +export const NotificationSchema = new Schema({ + pid: String, + type: String, + link: String, + objectID: String, + users: [{ + user: String, + timestamp: Date + }], + read: Boolean, + lastUpdated: Date +}); + +NotificationSchema.method('markRead', async function() { + this.set('read', true); + await this.save(); +}); + +export const NOTIFICATION = model('NOTIFICATION', NotificationSchema); \ No newline at end of file diff --git a/src/models/post.js b/src/models/post.ts similarity index 73% rename from src/models/post.js rename to src/models/post.ts index f83b19fc..59446dd2 100644 --- a/src/models/post.js +++ b/src/models/post.ts @@ -1,6 +1,7 @@ -const { Schema, model } = require('mongoose'); +import { Schema, model } from 'mongoose'; +import type { HydratedPostDocument, IPost, IPostMethods, PostModel } from '@/types/mongoose/post'; -const PostSchema = new Schema({ +export const PostSchema = new Schema({ id: String, title_id: String, screen_name: String, @@ -80,7 +81,7 @@ const PostSchema = new Schema({ yeahs: [Number] }); -PostSchema.methods.upReply = async function() { +PostSchema.method('upReply', async function() { const replyCount = this.get('reply_count'); if (replyCount + 1 < 0) { this.set('reply_count', 0); @@ -89,9 +90,9 @@ PostSchema.methods.upReply = async function() { } await this.save(); -}; +}); -PostSchema.methods.downReply = async function() { +PostSchema.method('downReply', async function() { const replyCount = this.get('reply_count'); if (replyCount - 1 < 0) { this.set('reply_count', 0); @@ -100,25 +101,20 @@ PostSchema.methods.downReply = async function() { } await this.save(); -}; +}); -PostSchema.methods.removePost = async function(reason, pid) { +PostSchema.method('removePost', async function(reason: string, pid: number) { this.set('removed', true); this.set('removed_reason', reason); this.set('removed_by', pid); this.set('removed_at', new Date()); await this.save(); -}; +}); -PostSchema.methods.unRemove = async function(reason) { +PostSchema.method('unRemove', async function(reason: string) { this.set('removed', false); this.set('removed_reason', reason); await this.save(); -}; - -const POST = model('POST', PostSchema); +}); -module.exports = { - PostSchema, - POST -}; +export const POST = model('POST', PostSchema); \ No newline at end of file diff --git a/src/models/report.js b/src/models/report.ts similarity index 52% rename from src/models/report.js rename to src/models/report.ts index a8014189..b57924a3 100644 --- a/src/models/report.js +++ b/src/models/report.ts @@ -1,6 +1,7 @@ -const { Schema, model } = require('mongoose'); +import { Schema, model } from 'mongoose'; +import type { HydratedReportDocument, IReport, IReportMethods, ReportModel } from '@/types/mongoose/report'; -const ReportSchema = new Schema({ +export const ReportSchema = new Schema({ pid: Number, reported_by: Number, post_id: String, @@ -19,17 +20,12 @@ const ReportSchema = new Schema({ resolved_at: Date, }); -ReportSchema.methods.resolve = async function(pid, note) { +ReportSchema.method('resolve', async function(pid, note) { this.set('resolved', true); this.set('resolved_by', pid); this.set('resolved_at', new Date()); this.set('note', note); await this.save(); -}; - -const REPORT = model('REPORT', ReportSchema); +}); -module.exports = { - ReportSchema, - REPORT -}; +export const REPORT = model('REPORT', ReportSchema); \ No newline at end of file diff --git a/src/models/settings.js b/src/models/settings.ts similarity index 52% rename from src/models/settings.js rename to src/models/settings.ts index cef9d782..3f283ea3 100644 --- a/src/models/settings.js +++ b/src/models/settings.ts @@ -1,6 +1,7 @@ -const { Schema, model } = require('mongoose'); +import { Schema, model } from 'mongoose'; +import type { HydratedSettingsDocument, ISettings, ISettingsMethods, SettingsModel } from '@/types/mongoose/settings'; -const SettingsSchema = new Schema({ +export const SettingsSchema = new Schema({ pid: Number, screen_name: String, account_status: { @@ -51,49 +52,44 @@ const SettingsSchema = new Schema({ }, }); -SettingsSchema.methods.updateComment = async function(comment) { +SettingsSchema.method('updateComment', async function(comment: string) { this.set('profile_comment', comment); await this.save(); -}; +}); -SettingsSchema.methods.updateSkill = async function(skill) { +SettingsSchema.method('updateSkill', async function(skill: number) { this.set('game_skill', skill); await this.save(); -}; +}); -SettingsSchema.methods.commentVisible = async function(active) { +SettingsSchema.method('commentVisible', async function(active: boolean) { this.set('profile_comment_visibility', active); await this.save(); -}; +}); -SettingsSchema.methods.skillVisible = async function(active) { +SettingsSchema.method('skillVisible', async function(active: boolean) { this.set('game_skill_visibility', active); await this.save(); -}; +}); -SettingsSchema.methods.birthdayVisible = async function(active) { +SettingsSchema.method('birthdayVisible', async function(active: boolean) { this.set('birthday_visibility', active); await this.save(); -}; +}); -SettingsSchema.methods.relationshipVisible = async function(active) { +SettingsSchema.method('relationshipVisible', async function(active: boolean) { this.set('relationship_visibility', active); await this.save(); -}; +}); -SettingsSchema.methods.countryVisible = async function(active) { +SettingsSchema.method('countryVisible', async function(active: boolean) { this.set('country_visibility', active); await this.save(); -}; +}); -SettingsSchema.methods.favCommunityVisible = async function(active) { +SettingsSchema.method('favCommunityVisible', async function(active: boolean) { this.set('profile_favorite_community_visibility', active); await this.save(); -}; - -const SETTINGS = model('SETTINGS', SettingsSchema); +}); -module.exports = { - SettingsSchema, - SETTINGS -}; +export const SETTINGS = model('SETTINGS', SettingsSchema); \ No newline at end of file diff --git a/src/redisCache.ts b/src/redisCache.ts new file mode 100644 index 00000000..839fb634 --- /dev/null +++ b/src/redisCache.ts @@ -0,0 +1,42 @@ +import { createClient } from 'redis'; +import logger from '@/logger'; +import config from '../config.json'; + +const { host, port } = config.redis; + +export const redisClient = createClient({ socket: { host, port } }); + +redisClient.on('error', (error) => { + logger.error(error); +}); + +redisClient.on('connect', () => { + logger.success('Redis connected'); +}); + +export async function setValue(key: string, value: string, expireTime: number): Promise { + if (!redisClient.isOpen) { + return false; + } + + await redisClient.set(key, value, { 'EX': expireTime }); + return true; +} + +export async function getValue(key: string): Promise { + if (!redisClient.isOpen) { + return null; + } + + const result = await redisClient.get(key); + return result; +} + +export async function removeValue(key: string): Promise { + if (!redisClient.isOpen) { + return false; + } + + await redisClient.del(key); + return true; +} \ No newline at end of file diff --git a/src/server.js b/src/server.ts similarity index 72% rename from src/server.js rename to src/server.ts index eb628fbe..1fab4576 100644 --- a/src/server.js +++ b/src/server.ts @@ -1,26 +1,24 @@ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -/* eslint-disable @typescript-eslint/no-var-requires */ +import express from 'express'; +import morgan from 'morgan'; +import cookieParser from 'cookie-parser'; +import session from 'express-session'; +import { default as RedisStore } from 'connect-redis'; +import logger from '@/logger'; +import * as database from '@/database'; +import { redisClient } from '@/redisCache'; +import juxt_web from '@/services/juxt-web'; +import type { Request, Response } from 'express'; +import config from '../config.json'; + process.title = 'Pretendo - Juxt-Web'; process.on('SIGTERM', () => { process.exit(0); }); -const express = require('express'); -const morgan = require('morgan'); -const cookieParser = require('cookie-parser'); -const session = require('express-session'); -const RedisStore = require('connect-redis').default; - -const database = require('./database'); -const logger = require('./logger'); -const { redisClient } = require('./redisCache'); -const config = require('../config.json'); const { http: { port } } = config; const app = express(); -const juxt_web = require('./services/juxt-web'); - app.set('etag', false); app.disable('x-powered-by'); app.set('view engine', 'ejs'); @@ -66,7 +64,7 @@ app.use((req, res) => { // non-404 error handler logger.info('Creating non-404 status handler'); -app.use((error, request, response) => { +app.use((error: any, request: Request, response: Response) => { const status = error.status || 500; response.status(status); @@ -79,7 +77,7 @@ app.use((error, request, response) => { }); // Starts the server -async function main() { +async function main(): Promise { // Starts the server logger.info('Starting server'); diff --git a/src/services/juxt-web/index.js b/src/services/juxt-web/index.ts similarity index 82% rename from src/services/juxt-web/index.js rename to src/services/juxt-web/index.ts index d2c97b85..dcd8cfb6 100644 --- a/src/services/juxt-web/index.js +++ b/src/services/juxt-web/index.ts @@ -1,12 +1,12 @@ -const express = require('express'); -const subdomain = require('express-subdomain'); -const logger = require('../../logger'); -const routes = require('./routes'); -const webAuth = require('../../middleware/webAuth'); -const consoleAuth = require('../../middleware/consoleAuth'); -const checkBan = require('../../middleware/checkBan'); -const detectVersion = require('../../middleware/detectVersion'); -const checkDiscovery = require('../../middleware/discovery'); +import express from 'express'; +import subdomain from 'express-subdomain'; +import logger from '../../logger'; +import * as routes from './routes'; +import { webAuth } from '../../middleware/webAuth'; +import { auth as consoleAuth } from '../../middleware/consoleAuth'; +import { checkBan } from '../../middleware/checkBan'; +import { detectVersion } from '../../middleware/detectVersion'; +import { checkDiscovery } from '../../middleware/discovery'; const router = express.Router(); const consoleRouter = express.Router(); @@ -65,4 +65,4 @@ webRouter.use('/news', routes.PORTAL_NEWS); webRouter.use('/login', routes.WEB_LOGIN); webRouter.use('/admin', routes.ADMIN); -module.exports = router; +export default router; diff --git a/src/services/juxt-web/routes/admin/admin.js b/src/services/juxt-web/routes/admin/admin.ts similarity index 56% rename from src/services/juxt-web/routes/admin/admin.js rename to src/services/juxt-web/routes/admin/admin.ts index 7a2c6514..327e58ca 100644 --- a/src/services/juxt-web/routes/admin/admin.js +++ b/src/services/juxt-web/routes/admin/admin.ts @@ -1,24 +1,26 @@ -const express = require('express'); -const database = require('../../../../database'); -const { POST } = require('../../../../models/post'); -const { SETTINGS } = require('../../../../models/settings'); -const util = require('../../../../util'); -const moment = require('moment'); -const config = require('../../../../../config.json'); +import moment from 'moment'; +import express from 'express'; +import * as database from '@/database'; +import { POST } from '@/models/post'; +import { SETTINGS } from '@/models/settings'; +import * as util from '@/util'; +import type { HydratedPostDocument } from '@/types/mongoose/post'; +import config from '../../../../../config.json'; + const router = express.Router(); -router.get('/posts', async function (req, res) { +router.get('/posts', async function (req, res): Promise { if (!req.moderator) { return res.redirect('/titles/show'); } const reports = await database.getAllOpenReports(); - const communityMap = await util.getCommunityHash(); + const communityMap = util.getCommunityHash(); const userContent = await database.getUserContent(req.pid); const userMap = util.getUserHash(); const postIDs = reports.map(obj => obj.post_id); - const posts = await POST.aggregate([ + const posts = await POST.aggregate([ { $match: { id: { $in: postIDs } } }, {$addFields: { '__order': { $indexOfArray: [ postIDs, '$id' ] } @@ -47,12 +49,18 @@ router.get('/accounts', async function (req, res) { return res.redirect('/titles/show'); } - const page = req.query.page ? parseInt(req.query.page) : 0; - const search = req.query.search; + let search: string; + if (typeof req.query.search === 'string') { + search = req.query.search; + } else { + search = ''; + } + + const page = (req.query.page && typeof req.query.page === 'string') ? parseInt(req.query.page) : 0; const limit = 20; const users = search ? await database.getUserSettingsFuzzySearch(search, limit, page * limit) : await database.getUsersContent(limit, page * limit); - const userMap = await util.getUserHash(); + const userMap = util.getUserHash(); res.render(req.directory + '/users.ejs', { lang: req.lang, @@ -68,21 +76,21 @@ router.get('/accounts', async function (req, res) { }); }); - -router.get('/accounts/:pid', async function (req, res) { +router.get('/accounts/:pid', async function (req, res): Promise { if (!req.moderator) { return res.redirect('/titles/show'); } - const pnid = await util.getUserDataFromPid(req.params.pid).catch((e) => { + const pid = parseInt(req.params.pid); + const pnid = await util.getUserDataFromPid(pid).catch((e) => { console.log(e.details); }); - const userContent = await database.getUserContent(req.params.pid); - if (isNaN(req.params.pid) || !pnid || !userContent) { + const userContent = await database.getUserContent(pid); + if (isNaN(pid) || !pnid || !userContent) { return res.redirect('/404'); } - const userSettings = await database.getUserSettings(req.params.pid); - const posts = await database.getNumberUserPostsByID(req.params.pid, config.post_limit); - const communityMap = await util.getCommunityHash(); + const userSettings = await database.getUserSettings(pid); + const posts = await database.getNumberUserPostsByID(pid, config.post_limit); + const communityMap = util.getCommunityHash(); res.render(req.directory + '/moderate_user.ejs', { lang: req.lang, @@ -108,7 +116,7 @@ router.post('/accounts/:pid', async (req, res) => { await SETTINGS.findOneAndUpdate({pid: pid}, { account_status: req.body.account_status, ban_lift_date: req.body.ban_lift_date, - ban_reason: `${req.user.username} (${req.pid}): ${req.body.ban_reason}` + ban_reason: `${req.user?.username} (${req.pid}): ${req.body.ban_reason}` }); res.json({ @@ -117,11 +125,8 @@ router.post('/accounts/:pid', async (req, res) => { }); router.delete('/:reportID', async function (req, res) { - if (!req.moderator) { - return res.sendStatus(401); - } - - const report = await database.getReportById(req.params.reportID); + const reportID = parseInt(req.params.reportID); + const report = await database.getReportById(reportID); if (!report) { return res.sendStatus(402); } @@ -130,8 +135,13 @@ router.delete('/:reportID', async function (req, res) { return res.sendStatus(404); } - await post.removePost(req.query.reason ? req.query.reason : 'Removed by moderator', req.pid); - await report.resolve(req.pid, req.query.reason ? req.query.reason : 'Removed by moderator'); + let reason: string = 'Removed by moderator'; + if (req.query.reason && typeof req.query.reason === 'string') { + reason = req.query.reason; + } + + await post.removePost(reason, req.pid); + await report.resolve(req.pid, reason); return res.sendStatus(200); }); @@ -141,14 +151,21 @@ router.put('/:reportID', async function (req, res) { return res.sendStatus(401); } - const report = await database.getReportById(req.params.reportID); + const reportID = parseInt(req.params.reportID); + + const report = await database.getReportById(reportID); if (!report) { return res.sendStatus(402); } - await report.resolve(req.pid, req.query.reason); + let reason = ''; + if (req.query.reason && typeof req.query.reason === 'string') { + reason = req.query.reason; + } + + await report.resolve(req.pid, reason); return res.sendStatus(200); }); -module.exports = router; +export default router; diff --git a/src/services/juxt-web/routes/console/communities.js b/src/services/juxt-web/routes/console/communities.ts similarity index 71% rename from src/services/juxt-web/routes/console/communities.js rename to src/services/juxt-web/routes/console/communities.ts index 9dc80759..2d3f0974 100644 --- a/src/services/juxt-web/routes/console/communities.js +++ b/src/services/juxt-web/routes/console/communities.ts @@ -1,20 +1,34 @@ -const express = require('express'); -const database = require('../../../../database'); -const util = require('../../../../util'); -const config = require('../../../../../config.json'); -const multer = require('multer'); -const moment = require('moment'); +import express from 'express'; +import multer from 'multer'; +import moment from 'moment'; +import * as database from '@/database'; +import * as util from '@/util'; +import { POST } from '@/models/post'; +import { COMMUNITY } from '@/models/communities'; +import * as redis from '@/redisCache'; +import type { HydratedCommunityDocument, ICommunity } from '@/types/mongoose/communities'; +import type { HydratedPostDocument } from '@/types/mongoose/post'; +import config from '../../../../../config.json'; + const upload = multer({dest: 'uploads/'}); const router = express.Router(); -const { POST } = require('../../../../models/post'); -const { COMMUNITY } = require('../../../../models/communities'); -const redis = require('../../../../redisCache'); router.get('/', async function (req, res) { - const newCommunities = JSON.parse(await redis.getValue('newCommunities')) || await database.getNewCommunities(6); - let popularCommunities = JSON.parse(await redis.getValue('popularCommunities')); - if (!popularCommunities) { + let newCommunities: HydratedCommunityDocument[]; + const cachedNewCommunities = await redis.getValue('newCommunities'); + if (cachedNewCommunities) { + newCommunities = JSON.parse(cachedNewCommunities); + } else { + newCommunities = await database.getNewCommunities(6); + redis.setValue('newCommunities', JSON.stringify(newCommunities), 60 * 60); + } + + let popularCommunities: HydratedCommunityDocument[]; + const cachedPopularCommunities = await redis.getValue('popularCommunities'); + if (cachedPopularCommunities) { + popularCommunities = JSON.parse(cachedPopularCommunities); + } else { const last24Hours = await calculateMostPopularCommunities(); popularCommunities = await COMMUNITY.aggregate([ { $match: { olive_community_id: { $in: last24Hours }, parent: null } }, @@ -26,7 +40,6 @@ router.get('/', async function (req, res) { { $project: { index: 0, _id: 0 } } ]); redis.setValue('popularCommunities', JSON.stringify(popularCommunities), 60 * 60); - redis.setValue('newCommunities', JSON.stringify(newCommunities), 60 * 60); } res.render(req.directory + '/communities.ejs', { @@ -54,7 +67,7 @@ router.get('/all', async function (req, res) { }); router.get('/:communityID', async function (req, res) { - if (req.query.title_id) { + if (typeof req.query.title_id === 'string') { const community = await database.getCommunityByTitleID(req.query.title_id); if (!community) { return res.redirect('/404'); @@ -107,19 +120,22 @@ router.get('/:communityID/:type', async function (req, res) { if (!community.permissions) { community.permissions = { - open: community.open, + open: (community as any).open, minimum_new_post_access_level: 0, minimum_new_comment_access_level: 0, minimum_new_community_access_level: 0 }; await community.save(); } - const communityMap = await util.getCommunityHash(); - let children = await database.getSubCommunities(community.olive_community_id); - if (children.length === 0) { + const communityMap = util.getCommunityHash(); + + let children: HydratedCommunityDocument[] | null = await database.getSubCommunities(community.olive_community_id); + if (children?.length === 0) { children = null; } - let posts; let type; + + let posts: HydratedPostDocument[]; + let type: number; if (req.params.type === 'hot') { posts = await database.getNumberPopularCommunityPostsByID(community, config.post_limit); @@ -176,7 +192,12 @@ router.get('/:communityID/:type', async function (req, res) { }); router.get('/:communityID/:type/more', async function (req, res) { - let offset = parseInt(req.query.offset); + + let offset: number | undefined; + if (typeof req.query.offset === 'string') { + offset = parseInt(req.query.offset); + } + const userContent = await database.getUserContent(req.pid); const communityMap = await util.getCommunityHash(); let posts; @@ -229,48 +250,57 @@ router.get('/:communityID/:type/more', async function (req, res) { }); router.post('/follow', upload.none(), async function (req, res) { + const community = await database.getCommunityByID(req.body.id); const userContent = await database.getUserContent(req.pid); - const popularCommunities = JSON.parse(await redis.getValue('popularCommunities')); - let updated = false; - - if (userContent !== null && userContent.followed_communities.indexOf(community.olive_community_id) === -1) { - community.upFollower(); - userContent.addToCommunities(community.olive_community_id); - res.send({ status: 200, id: community.olive_community_id, count: community.followers }); - updated = true; - } else if (userContent !== null && userContent.followed_communities.indexOf(community.olive_community_id) !== -1) { - community.downFollower(); - userContent.removeFromCommunities(community.olive_community_id); - res.send({ status: 200, id: community.olive_community_id, count: community.followers }); - updated = true; - } else { - res.send({ status: 423, id: community.olive_community_id, count: community.followers }); + + let popularCommunities: ICommunity[] | undefined = undefined; + const cachedPopularCommunities = await redis.getValue('popularCommunities'); + if (cachedPopularCommunities) { + popularCommunities = JSON.parse(cachedPopularCommunities); } - if (popularCommunities && updated) { - const index = popularCommunities.findIndex((element) => element.olive_community_id === community.olive_community_id); - if (index !== -1) { - popularCommunities[index].followers = community.followers; - redis.setValue('popularCommunities', JSON.stringify(popularCommunities), 60 * 60); + if (userContent && community) { + + if (userContent.followed_communities.indexOf(community.olive_community_id) === -1) { + community.upFollower(); + userContent.addToCommunities(community.olive_community_id); + res.send({ status: 200, id: community.olive_community_id, count: community.followers }); + } else { + community.downFollower(); + userContent.removeFromCommunities(community.olive_community_id); + res.send({ status: 200, id: community.olive_community_id, count: community.followers }); } + + if (popularCommunities) { + const index = popularCommunities.findIndex(element => element.olive_community_id === community.olive_community_id); + if (index !== -1) { + popularCommunities[index].followers = community.followers; + redis.setValue('popularCommunities', JSON.stringify(popularCommunities), 60 * 60); + } + } + + return; } + + res.send({ status: 423, id: community?.olive_community_id, count: community?.followers}); }); -async function calculateMostPopularCommunities() { +async function calculateMostPopularCommunities(): Promise { const now = new Date(); const last24Hours = new Date(now.getTime() - 24 * 60 * 60 * 1000); const posts = await POST.find({ created_at: { $gte: last24Hours }, message_to_pid: null }).lean(); - const communityIds = {}; + const communityIds: Record = {}; for (const post of posts) { const communityId = post.community_id; communityIds[communityId] = (communityIds[communityId] || 0) + 1; } + return Object.entries(communityIds) .sort((a, b) => b[1] - a[1]) .map((entry) => entry[0]); } -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/console/feed.js b/src/services/juxt-web/routes/console/feed.ts similarity index 76% rename from src/services/juxt-web/routes/console/feed.js rename to src/services/juxt-web/routes/console/feed.ts index 71d2f6c5..16f755d5 100644 --- a/src/services/juxt-web/routes/console/feed.js +++ b/src/services/juxt-web/routes/console/feed.ts @@ -1,8 +1,10 @@ -const express = require('express'); -const database = require('../../../../database'); -const util = require('../../../../util'); -const config = require('../../../../../config.json'); -const moment = require('moment'); +import express from 'express'; +import moment from 'moment'; +import * as database from '@/database'; +import * as util from '@/util'; +import type { HydratedPostDocument } from '@/types/mongoose/post'; +import config from '../../../../../config.json'; + const router = express.Router(); router.get('/', async function (req, res) { @@ -49,13 +51,19 @@ router.get('/', async function (req, res) { }); router.get('/more', async function (req, res) { - let offset = parseInt(req.query.offset); + + let offset = 0; + if (typeof req.query?.offset === 'string') { + offset = parseInt(req.query.offset); + } + const userContent = await database.getUserContent(req.pid); - const communityMap = await util.getCommunityHash(); - if (!offset) { - offset = 0; + const communityMap = util.getCommunityHash(); + + let posts: HydratedPostDocument[] = []; + if (userContent) { + posts = await database.getNewsFeedOffset(userContent, config.post_limit, offset); } - const posts = await database.getNewsFeedOffset(userContent, config.post_limit, offset); const bundle = { posts, @@ -87,4 +95,4 @@ router.get('/more', async function (req, res) { } }); -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/console/messages.js b/src/services/juxt-web/routes/console/messages.ts similarity index 71% rename from src/services/juxt-web/routes/console/messages.js rename to src/services/juxt-web/routes/console/messages.ts index 23f38202..600124eb 100644 --- a/src/services/juxt-web/routes/console/messages.js +++ b/src/services/juxt-web/routes/console/messages.ts @@ -1,12 +1,14 @@ -const express = require('express'); -const database = require('../../../../database'); -const util = require('../../../../util'); -const config = require('../../../../../config.json'); -const { POST } = require('../../../../models/post'); -const moment = require('moment'); -const {CONVERSATION} = require('../../../../models/conversation'); -const crypto = require('crypto'); -const snowflake = require('node-snowflake').Snowflake; +import crypto from 'crypto'; +import express from 'express'; +import moment from 'moment'; +import { Snowflake as snowflake } from 'node-snowflake'; +import * as database from '@/database'; +import * as util from '@/util'; +import { POST } from '@/models/post'; +import { CONVERSATION } from '@/models/conversation'; +import type { IPost } from '@/types/mongoose/post'; +import config from '../../../../../config.json'; + const router = express.Router(); router.get('/', async function (req, res) { @@ -65,13 +67,17 @@ router.post('/new', async function (req, res) { res.status(422); return res.redirect(`/friend_messages/${conversation.id}`); } - let painting = ''; let paintingURI = ''; let screenshot = null; + let painting = ''; + let paintingURI: string | null = ''; + let screenshot = null; if (req.body._post_type === 'painting' && req.body.painting) { painting = req.body.painting.replace(/\0/g, '').trim(); - paintingURI = await util.processPainting(painting, true); - await util.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read'); + paintingURI = util.processPainting(painting, true); + if (paintingURI) { + await util.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, Buffer.from(paintingURI, 'base64'), 'public-read'); + } } - if (req.body.screenshot) { + if (typeof req.body.screenshot === 'string') { screenshot = req.body.screenshot.replace(/\0/g, '').trim(); await util.uploadCDNAsset('pn-cdn', `screenshots/${req.pid}/${postID}.jpg`, Buffer.from(screenshot, 'base64'), 'public-read'); } @@ -98,23 +104,35 @@ router.post('/new', async function (req, res) { break; } const body = req.body.body; - if (body && util.INVALID_POST_BODY_REGEX.test(body)) { + if (typeof body !== 'string' || util.INVALID_POST_BODY_REGEX.test(body)) { // TODO - Log this error - return res.sendStatus(422); + res.sendStatus(422); + return; } if (body && body.length > 280) { // TODO - Log this error - return res.sendStatus(422); + res.sendStatus(422); + return; } - const document = { + if (screenshot) { + screenshot = `/screenshots/${req.pid}/${postID}.jpg`; + } else { + screenshot = ''; + } + + if (!painting) { + painting = ''; + } + + const document: Partial = { community_id: conversation.id, - screen_name: req.user.mii.name, + screen_name: req.user?.mii?.name, body: body, painting: painting, screenshot: screenshot ? `/screenshots/${req.pid}/${postID}.jpg`: '', - country_id: req.paramPackData ? req.paramPackData.country_id : 49, + country_id: req.paramPackData ? parseInt(req.paramPackData.country_id) : 49, created_at: new Date(), feeling_id: req.body.feeling_id, id: postID, @@ -122,35 +140,34 @@ router.post('/new', async function (req, res) { is_spoiler: (req.body.spoiler) ? 1 : 0, is_app_jumpable: req.body.is_app_jumpable, language_id: req.body.language_id, - mii: req.user.mii.data, + mii: req.user?.mii?.data, mii_face_url: `https://mii.olv.pretendo.cc/mii/${req.pid}/${miiFace}`, pid: req.pid, - platform_id: req.paramPackData ? req.paramPackData.platform_id : 0, - region_id: req.paramPackData ? req.paramPackData.region_id : 2, - verified: (req.user.accessLevel >= 2), - message_to_pid: req.body.message_to_pid, - moderator: req.moderator + platform_id: req.paramPackData ? parseInt(req.paramPackData.platform_id) : 0, + region_id: req.paramPackData ? parseInt(req.paramPackData.region_id) : 2, + verified: ((req.user?.accessLevel ?? 0) >= 2), + message_to_pid: req.body.message_to_pid }; - const duplicatePost = await database.getDuplicatePosts(req.pid, document); - if (duplicatePost && req.params.post_id) { - return res.redirect('/posts/' + req.params.post_id); - } + // const duplicatePost = await database.getDuplicatePosts(req.pid, body, painting, screenshot); + // if (duplicatePost && req.params.post_id) { + // return res.redirect('/posts/' + req.params.post_id); + // } const newPost = new POST(document); newPost.save(); res.redirect(`/friend_messages/${conversation.id}`); - let postPreviewText; + let postPreviewText: string; if (document.painting) { postPreviewText = 'sent a Drawing'; - } else if (document.body.length > 25) { - postPreviewText = document.body.substring(0, 25) + '...'; + } else if (body.length > 25) { + postPreviewText = body.substring(0, 25) + '...'; } else { - postPreviewText = document.body; + postPreviewText = body; } await conversation.newMessage(postPreviewText, user2.pid); }); router.get('/new/:pid', async function (req, res) { - const user2 = await util.getUserDataFromPid(req.params.pid); + const user2 = await util.getUserDataFromPid(parseInt(req.params.pid)); const friends = await util.getFriends(user2.pid); if (!req.user || !user2) { return res.sendStatus(422); @@ -183,13 +200,13 @@ router.get('/new/:pid', async function (req, res) { if (!conversation) { return res.sendStatus(404); } - const body = `${req.user.mii.name} started a new chat!`; + const body = `${req.user?.mii?.name} started a new chat!`; const newMessage = { - screen_name: req.user.mii.name, + screen_name: req.user?.mii?.name, body: body, created_at: new Date(), id: await generatePostUID(21), - mii: req.user.mii.data, + mii: req.user?.mii?.data, mii_face_url: `https://mii.olv.pretendo.cc/mii/${req.pid}/normal_face.png`, pid: req.pid, verified: (req.user.accessLevel >= 2), @@ -199,12 +216,12 @@ router.get('/new/:pid', async function (req, res) { }; const newPost = new POST(newMessage); newPost.save(); - await conversation.newMessage(`${req.user.mii.name} started a new chat!`, user2.pid); + await conversation.newMessage(`${req.user?.mii?.name} started a new chat!`, user2.pid); res.redirect(`/friend_messages/${conversation.id}`); }); router.get('/:message_id', async function (req, res) { - const conversation = await database.getConversationByID(req.params.message_id.toString()); + const conversation = await database.getConversationByID(req.params.message_id); if (!conversation) { return res.sendStatus(404); } @@ -229,12 +246,11 @@ router.get('/:message_id', async function (req, res) { await conversation.markAsRead(req.pid); }); -async function generatePostUID(length) { +async function generatePostUID(length: number): Promise { let id = Buffer.from(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(length * 2))), 'binary').toString('base64').replace(/[+/]/g, '').substring(0, length); const inuse = await POST.findOne({ id }); - id = (inuse ? await generatePostUID() : id); + id = (inuse ? await generatePostUID(length) : id); return id; } - -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/console/notifications.js b/src/services/juxt-web/routes/console/notifications.ts similarity index 85% rename from src/services/juxt-web/routes/console/notifications.js rename to src/services/juxt-web/routes/console/notifications.ts index 5070734a..fa26153b 100644 --- a/src/services/juxt-web/routes/console/notifications.js +++ b/src/services/juxt-web/routes/console/notifications.ts @@ -1,8 +1,9 @@ -const express = require('express'); -const database = require('../../../../database'); -const config = require('../../../../../config.json'); -const util = require('../../../../util'); -const moment = require('moment'); +import express from 'express'; +import moment from 'moment'; +import * as database from '@/database'; +import * as util from '@/util'; +import config from '../../../../../config.json'; + const router = express.Router(); router.get('/my_news', async function (req, res) { @@ -66,4 +67,4 @@ router.get('/friend_requests', async function (req, res) { }); }); -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/console/posts.js b/src/services/juxt-web/routes/console/posts.ts similarity index 76% rename from src/services/juxt-web/routes/console/posts.js rename to src/services/juxt-web/routes/console/posts.ts index 344aadc1..228ebe01 100644 --- a/src/services/juxt-web/routes/console/posts.js +++ b/src/services/juxt-web/routes/console/posts.ts @@ -1,15 +1,18 @@ -const express = require('express'); -const database = require('../../../../database'); -const util = require('../../../../util'); -const config = require('../../../../../config.json'); -const {POST} = require('../../../../models/post'); -const multer = require('multer'); -const moment = require('moment'); -const rateLimit = require('express-rate-limit'); -const {REPORT} = require('../../../../models/report'); +import crypto from 'crypto'; +import express from 'express'; +import multer from 'multer'; +import moment from 'moment'; +import rateLimit from 'express-rate-limit'; +import * as database from '../../../../database'; +import * as util from '@/util'; +import * as redis from '@/redisCache'; +import { POST } from '@/models/post'; +import { REPORT } from '@/models/report'; +import type { Request, Response } from 'express'; +import type { IPost } from '@/types/mongoose/post'; +import config from '../../../../../config.json'; + const upload = multer({dest: 'uploads/'}); -const crypto = require('crypto'); -const redis = require('../../../../redisCache'); const router = express.Router(); const postLimit = rateLimit({ @@ -43,19 +46,31 @@ const yeahLimit = rateLimit({ }); router.get('/:post_id/oembed.json', async function (req, res) { + const post = await database.getPostByID(req.params.post_id.toString()); + + if (!post) { + res.sendStatus(404); + return; + } + const doc = { 'author_name': post.screen_name, 'author_url': 'https://juxt.pretendo.network/users/show?pid=' + post.pid, }; + res.send(doc); }); router.post('/empathy', yeahLimit, async function (req, res) { + const post = await database.getPostByID(req.body.postID); + if (!post) { - return res.sendStatus(404); + res.sendStatus(404); + return; } + if (post.yeahs.indexOf(req.pid) === -1) { await POST.updateOne({ id: post.id, @@ -74,10 +89,9 @@ router.post('/empathy', yeahLimit, async function (req, res) { res.send({status: 200, id: post.id, count: post.empathy_count + 1}); if (req.pid !== post.pid) { await util.newNotification({ - pid: post.pid, + pid: post.pid.toString(), type: 'yeah', objectID: post.id, - userPID: req.pid, link: `/posts/${post.id}` }); } @@ -122,7 +136,7 @@ router.get('/:post_id', async function (req, res) { return res.redirect(`/posts/${post.id}`); } const community = await database.getCommunityByID(post.community_id); - const communityMap = await util.getCommunityHash(); + const communityMap = util.getCommunityHash(); const replies = await database.getPostReplies(req.params.post_id.toString(), 25); const postPNID = await util.getUserDataFromPid(post.pid); res.render(req.directory + '/post.ejs', { @@ -144,15 +158,23 @@ router.get('/:post_id', async function (req, res) { }); router.delete('/:post_id', async function (req, res) { + const post = await database.getPostByID(req.params.post_id); if (!post) { return res.sendStatus(404); } + if (req.pid !== post.pid && !req.moderator) { return res.sendStatus(401); } + if (req.moderator && req.pid !== post.pid) { - await post.removePost(req.query.reason ? req.query.reason : 'Removed by moderator', req.pid); + + let reason: string = 'Removed by moderator'; + if (req.query.reason && typeof req.query.reason === 'string') { + reason = req.query.reason; + } + await post.removePost(reason, req.pid); } else { await post.removePost('User requested removal', req.pid); } @@ -171,6 +193,7 @@ router.post('/:post_id/new', postLimit, upload.none(), async function (req, res) }); router.post('/:post_id/report', upload.none(), async function (req, res) { + const { reason, message, post_id } = req.body; const post = await database.getPostByID(post_id); if (!reason || !post_id || !post) { @@ -197,7 +220,8 @@ router.post('/:post_id/report', upload.none(), async function (req, res) { return res.redirect(`/posts/${post.id}`); }); -async function newPost(req, res) { +async function newPost(req: Request, res: Response): Promise { + const userSettings = await database.getUserSettings(req.pid); let parentPost = null; const postID = await generatePostUID(21); const community = await database.getCommunityByID(req.body.community_id); if (!community || !userSettings || !req.user) { @@ -212,24 +236,29 @@ async function newPost(req, res) { if (req.params.post_id) { parentPost = await database.getPostByID(req.params.post_id.toString()); if (!parentPost) { - return res.sendStatus(403); + res.sendStatus(403); + return; } } - if (!(community.admins && community.admins.indexOf(req.pid) !== -1 && userSettings.account_status === 0) && req.user.access_level >= community.permissions.minimum_new_post_access_level - && (community.type >= 2) && !(parentPost && req.user.access_level >= community.permissions.minimum_new_comment_access_level && community.permissions.open)) { + if (!(community.admins && community.admins.indexOf(req.pid) !== -1 && userSettings.account_status === 0) && req.user.accessLevel >= community.permissions.minimum_new_post_access_level + && (community.type >= 2) && !(parentPost && req.user.accessLevel >= community.permissions.minimum_new_comment_access_level && community.permissions.open)) { res.status(403); return res.redirect(`/titles/${community.olive_community_id}/new`); } - let painting = ''; let paintingURI = ''; let screenshot = null; + let painting: string | null = ''; + let paintingURI: string | null = ''; + let screenshot = null; if (req.body._post_type === 'painting' && req.body.painting) { if (req.body.bmp === 'true') { - painting = await util.processPainting(req.body.painting.replace(/\0/g, '').trim(), false); + painting = util.processPainting(req.body.painting.replace(/\0/g, '').trim(), false); } else { painting = req.body.painting; } - paintingURI = await util.processPainting(painting, true); - await util.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, paintingURI, 'public-read'); + paintingURI = util.processPainting(painting, true); + if (paintingURI) { + await util.uploadCDNAsset('pn-cdn', `paintings/${req.pid}/${postID}.png`, Buffer.from(paintingURI, 'base64'), 'public-read'); + } } if (req.body.screenshot) { screenshot = req.body.screenshot.replace(/\0/g, '').trim(); @@ -257,24 +286,38 @@ async function newPost(req, res) { miiFace = 'normal_face.png'; break; } + const body = req.body.body; - if (body && util.INVALID_POST_BODY_REGEX.test(body)) { + if (typeof body !== 'string' || util.INVALID_POST_BODY_REGEX.test(body)) { // TODO - Log this error - return res.sendStatus(422); + res.sendStatus(422); + return; } if (body && body.length > 280) { // TODO - Log this error - return res.sendStatus(422); + res.sendStatus(422); + return; + } + + if (screenshot) { + screenshot = `/screenshots/${req.pid}/${postID}.jpg`; + } else { + screenshot = ''; + } + + if (!painting) { + painting = ''; } - const document = { + + const document: Partial = { title_id: community.title_id[0], community_id: community.olive_community_id, screen_name: userSettings.screen_name, - body: body, - painting: painting, - screenshot: screenshot ? `/screenshots/${req.pid}/${postID}.jpg` : '', - country_id: req.paramPackData ? req.paramPackData.country_id : 49, + body, + painting, + screenshot, + country_id: req.paramPackData ? parseInt(req.paramPackData.country_id) : 49, created_at: new Date(), feeling_id: req.body.feeling_id, id: postID, @@ -282,16 +325,15 @@ async function newPost(req, res) { is_spoiler: (req.body.spoiler) ? 1 : 0, is_app_jumpable: req.body.is_app_jumpable, language_id: req.body.language_id, - mii: req.user.mii.data, + mii: req.user.mii?.data ?? '', mii_face_url: `https://mii.olv.pretendo.cc/mii/${req.user.pid}/${miiFace}`, pid: req.pid, - platform_id: req.paramPackData ? req.paramPackData.platform_id : 0, - region_id: req.paramPackData ? req.paramPackData.region_id : 2, + platform_id: req.paramPackData ? parseInt(req.paramPackData.platform_id) : 0, + region_id: req.paramPackData ? parseInt(req.paramPackData.region_id) : 2, verified: req.moderator, parent: parentPost ? parentPost.id : null, - moderator: req.moderator }; - const duplicatePost = await database.getDuplicatePosts(req.pid, document); + const duplicatePost = await database.getDuplicatePosts(req.pid, body, painting, screenshot); if (duplicatePost && req.params.post_id) { return res.redirect('/posts/' + req.params.post_id.toString()); } @@ -306,9 +348,9 @@ async function newPost(req, res) { } if (parentPost && (parentPost.pid !== req.user.pid)) { await util.newNotification({ - pid: parentPost.pid, + pid: parentPost.pid.toString(), type: 'reply', - user: req.pid, + objectID: req.pid.toString(), link: `/posts/${parentPost.id}` }); } @@ -321,12 +363,11 @@ async function newPost(req, res) { } } -async function generatePostUID(length) { +async function generatePostUID(length: number): Promise { let id = Buffer.from(String.fromCharCode(...crypto.getRandomValues(new Uint8Array(length * 2))), 'binary').toString('base64').replace(/[+/]/g, '').substring(0, length); const inuse = await POST.findOne({id}); - id = (inuse ? await generatePostUID() : id); + id = (inuse ? await generatePostUID(length) : id); return id; } - -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/console/show.js b/src/services/juxt-web/routes/console/show.ts similarity index 75% rename from src/services/juxt-web/routes/console/show.js rename to src/services/juxt-web/routes/console/show.ts index 881123a9..9ac8b99a 100644 --- a/src/services/juxt-web/routes/console/show.js +++ b/src/services/juxt-web/routes/console/show.ts @@ -1,7 +1,8 @@ -const express = require('express'); -const database = require('../../../../database'); -const util = require('../../../../util'); -const config = require('../../../../../config.json'); +import express from 'express'; +import * as database from '@/database'; +import * as util from '@/util'; +import config from '../../../../../config.json'; + const router = express.Router(); router.get('/', async function (req, res) { @@ -32,11 +33,13 @@ router.get('/', async function (req, res) { res.redirect('/titles'); } - const usrMii = await database.getUserSettings(req.pid); - if (req.user.mii.name !== usrMii.screen_name) { - util.setName(req.pid, req.user.mii.name); - usrMii.screen_name = req.user.mii.name; - await usrMii.save(); + if (req.pid) { + const usrMii = await database.getUserSettings(req.pid); + if (req?.user?.mii?.name && usrMii && req.user.mii.name !== usrMii?.screen_name) { + util.setName(req.pid, req.user.mii.name); + usrMii.screen_name = req.user.mii.name; + await usrMii.save(); + } } }); @@ -67,4 +70,4 @@ router.post('/newUser', async function (req, res) { }); -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/console/topics.js b/src/services/juxt-web/routes/console/topics.ts similarity index 66% rename from src/services/juxt-web/routes/console/topics.js rename to src/services/juxt-web/routes/console/topics.ts index d545b3ae..6a195cd3 100644 --- a/src/services/juxt-web/routes/console/topics.js +++ b/src/services/juxt-web/routes/console/topics.ts @@ -1,20 +1,32 @@ -const express = require('express'); -const database = require('../../../../database'); -const util = require('../../../../util'); -const config = require('../../../../../config.json'); -const moment = require('moment'); -const { POST } = require('../../../../models/post'); +import express from 'express'; +import moment from 'moment'; +import * as database from '@/database'; +import * as util from '@/util'; +import { POST } from '@/models/post'; +import config from '../../../../../config.json'; + const router = express.Router(); router.get('/', async function (req, res) { const userContent = await database.getUserContent(req.pid); - const communityMap = await util.getCommunityHash(); + const communityMap = util.getCommunityHash(); const tag = req.query.topic_tag; console.log(tag); if (!userContent || !tag) { return res.redirect('/404'); } - const posts = await POST.find({ topic_tag: req.query.topic_tag }).sort({ created_at: -1}).limit(parseInt(req.query.limit)); + + let limit: number | undefined; + if (typeof req.query?.limit === 'string') { + limit = parseInt(req.query.limit); + } + + const options = { + limit, + sort: { created_at: -1 } + }; + + const posts = await POST.find({ topic_tag: req.query.topic_tag }, {}, options); const bundle = { posts, @@ -52,14 +64,30 @@ router.get('/', async function (req, res) { }); router.get('/more', async function (req, res) { - const offset = req.query.offset ? parseInt(req.query.offset) : 0; const userContent = await database.getUserContent(req.pid); - const communityMap = await util.getCommunityHash(); + const communityMap = util.getCommunityHash(); + + let limit: number | undefined; + if (typeof req.query?.limit === 'string') { + limit = parseInt(req.query.limit); + } + + let offset: number | undefined; + if (typeof req.query?.offset === 'string') { + limit = parseInt(req.query.offset); + } + + const options = { + sort: { created_at: -1 }, + limit, + offset + }; + const tag = req.query.topic_tag; if (!tag) { return res.sendStatus(204); } - const posts = await POST.find({ topic_tag: req.query.topic_tag }).sort({ created_at: -1}).limit(parseInt(req.query.limit)); + const posts = await POST.find({ topic_tag: req.query.topic_tag }, {}, options); const bundle = { posts, @@ -69,7 +97,7 @@ router.get('/more', async function (req, res) { userContent, lang: req.lang, mii_image_CDN: config.mii_image_CDN, - link: `/topics/more?tag=${tag}&offset=${posts.length}&pjax=true` + link: `/topics/more?tag=${tag}&offset=${(offset ?? 0) + posts.length}&pjax=true` }; if (posts.length > 0) { @@ -90,4 +118,4 @@ router.get('/more', async function (req, res) { } }); -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/console/userpage.js b/src/services/juxt-web/routes/console/userpage.ts similarity index 66% rename from src/services/juxt-web/routes/console/userpage.js rename to src/services/juxt-web/routes/console/userpage.ts index af9fafc7..6dc299a2 100644 --- a/src/services/juxt-web/routes/console/userpage.js +++ b/src/services/juxt-web/routes/console/userpage.ts @@ -1,13 +1,17 @@ -const express = require('express'); -const database = require('../../../../database'); -const util = require('../../../../util'); -const config = require('../../../../../config.json'); -const multer = require('multer'); -const moment = require('moment'); +import express from 'express'; +import multer from 'multer'; +import moment from 'moment'; +import * as database from '@/database'; +import * as util from '@/util'; +import * as redis from '@/redisCache'; +import { POST } from '@/models/post'; +import { SETTINGS } from '@/models/settings'; +import type { Request, Response } from 'express'; +import type { IPost } from '@/types/mongoose/post'; +import type { HydratedSettingsDocument } from '@/types/mongoose/settings'; +import config from '../../../../../config.json'; + const upload = multer({ dest: 'uploads/' }); -const { POST } = require('../../../../models/post'); -const {SETTINGS} = require('../../../../models/settings'); -const redis = require('../../../../redisCache'); const router = express.Router(); router.get('/menu', async function (req, res) { @@ -24,6 +28,7 @@ router.get('/me', async function (req, res) { router.get('/notifications.json', async function (req, res) { const notifications = await database.getUnreadNotificationCount(req.pid); const messagesCount = await database.getUnreadConversationCount(req.pid); + res.send( { message_count: messagesCount, @@ -67,8 +72,14 @@ router.get('/me/:type', async function (req, res) { }); router.post('/me/settings', upload.none(), async function (req, res) { + const userSettings = await database.getUserSettings(req.pid); + if (!userSettings) { + res.status(400).send('No user settings found'); + return; + } + userSettings.country_visibility = !!req.body.country; userSettings.birthday_visibility = !!req.body.birthday; userSettings.game_skill_visibility = !!req.body.experience; @@ -88,37 +99,45 @@ router.get('/show', async function (req, res) { }); router.get('/:pid/more', async function (req, res) { - await morePosts(req, res, req.params.pid); -}); - -router.get('/:pid/yeahs/more', async function (req, res) { - await moreYeahPosts(req, res, req.params.pid); + await morePosts(req, res, parseInt(req.params.pid)); }); router.get('/:pid/:type', async function (req, res) { - await userRelations(req, res, req.params.pid); + await userRelations(req, res, parseInt(req.params.pid)); }); // TODO: Remove the need for a parameter to toggle the following state router.post('/follow', upload.none(), async function (req, res) { + + if (!req.body.id) { + res.status(400).send('Invalid request body'); + return; + } + const userToFollowContent = await database.getUserContent(req.body.id); const userContent = await database.getUserContent(req.pid); - if (userContent !== null && userContent.followed_users.indexOf(userToFollowContent.pid) === -1) { - userToFollowContent.addToFollowers(userContent.pid); - userContent.addToUsers(userToFollowContent.pid); - res.send({ status: 200, id: userToFollowContent.pid, count: userToFollowContent.following_users.length - 1 }); - const picked = await database.getNotification(userToFollowContent.pid, 2, userContent.pid); - //pid, type, reference_id, origin_pid, title, content - if (picked === null) { - await util.newNotification({ pid: userToFollowContent.pid, type: 'follow', objectID: req.pid, link: `/users/${req.pid}` }); + + if (userContent && userToFollowContent) { + + if (userContent.followed_users.indexOf(userToFollowContent.pid) === -1) { + userToFollowContent.addToFollowers(userContent.pid); + userContent.addToUsers(userToFollowContent.pid); + res.send({ status: 200, id: userToFollowContent.pid, count: userToFollowContent.following_users.length - 1 }); + const picked = await database.getNotification(userToFollowContent.pid, '2', userContent.pid); + //pid, type, reference_id, origin_pid, title, content + if (picked === null) { + await util.newNotification({ pid: userToFollowContent.pid.toString(), type: 'follow', objectID: req.pid?.toString(), link: `/users/${req.pid}` }); + } + return; + } else { + userToFollowContent.removeFromFollowers(userContent.pid); + userContent.removeFromUsers(userToFollowContent.pid); + res.send({ status: 200, id: userToFollowContent.pid, count: userToFollowContent.following_users.length - 1 }); + return; } - } else if (userContent !== null && userContent.followed_users.indexOf(userToFollowContent.pid) !== -1) { - userToFollowContent.removeFromFollowers(userContent.pid); - userContent.removeFromUsers(userToFollowContent.pid); - res.send({ status: 200, id: userToFollowContent.pid, count: userToFollowContent.following_users.length - 1 }); - } else { - res.send({ status: 423, id: userToFollowContent.pid, count: userToFollowContent.following_users.length - 1 }); } + + res.send({ status: 423, id: userToFollowContent?.pid, count: (userToFollowContent?.following_users.length ?? 0) - 1 }); }); router.get('/:pid', async function (req, res) { @@ -126,7 +145,8 @@ router.get('/:pid', async function (req, res) { if (userID === 'me' || Number(userID) === req.pid) { return res.redirect('/users/me'); } - await userPage(req, res, userID); + + await userPage(req, res, parseInt(userID)); }); router.get('/:pid/:type', async function (req, res) { @@ -134,10 +154,10 @@ router.get('/:pid/:type', async function (req, res) { if (userID === 'me' || Number(userID) === req.pid) { return res.redirect('/users/me'); } - await userRelations(req, res, userID); + await userRelations(req, res, parseInt(userID)); }); -async function userPage(req, res, userID) { +async function userPage(req: Request, res: Response, userID: number): Promise { if (!userID || isNaN(userID)) { return res.redirect('/404'); } @@ -150,16 +170,18 @@ async function userPage(req, res, userID) { } const userSettings = await database.getUserSettings(userID); - let posts = JSON.parse(await redis.getValue(`${userID}-user_page_posts`)); - if (!posts) { + const cachedPosts = await redis.getValue(`${userID}-user_page_posts`); + let posts: IPost[]; + if (cachedPosts) { + posts = JSON.parse(cachedPosts); + } else { posts = await database.getNumberUserPostsByID(userID, config.post_limit); await redis.setValue(`${userID}_user_page_posts`, JSON.stringify(posts), 60 * 60 * 1); } - const numPosts = await database.getTotalPostsByUserID(userID); const communityMap = await util.getCommunityHash(); - let friends = []; + let friends: number[] = []; try { friends = await util.getFriends(userID); } catch (e) {} @@ -209,13 +231,29 @@ async function userPage(req, res, userID) { }); } -async function userRelations(req, res, userID) { +async function userRelations(req: Request, res: Response, userID: number | null): Promise { + if (!userID) { + res.redirect('/404'); + return; + } + const pnid = userID === req.pid ? req.user : await util.getUserDataFromPid(userID); + if (!pnid) { + res.redirect('/404'); + return; + } + const userContent = await database.getUserContent(userID); + if (!userContent) { + res.redirect('/404'); + return; + } + const link = (pnid.pid === req.pid) ? '/users/me/' : `/users/${userID}/`; const userSettings = await database.getUserSettings(userID); const numPosts = await database.getTotalPostsByUserID(userID); const friends = await util.getFriends(userID); + let parentUserContent; if (pnid.pid !== req.pid) { parentUserContent = await database.getUserContent(req.pid); @@ -224,20 +262,10 @@ async function userRelations(req, res, userID) { return res.redirect('/404'); } - let followers; let communities; let communityMap; let selection; + const communityMap = util.getCommunityHash(); if (req.params.type === 'yeahs') { const posts = await POST.find({ yeahs: req.pid, removed: false }).sort({created_at: -1}); - /*let posts = await POST.aggregate([ - { $match: { id: { $in: likesArray } } }, - {$addFields: { - "__order": { $indexOfArray: [ likesArray, "$id" ] } - }}, - { $sort: { "__order": 1 } }, - { $project: { index: 0, _id: 0 } }, - { $limit: config.post_limit } - ]);*/ - const communityMap = await util.getCommunityHash(); const bundle = { posts, open: true, @@ -278,6 +306,9 @@ async function userRelations(req, res, userID) { } } + let followers: HydratedSettingsDocument[]; + let communities: string[]; + let selection: number; if (req.params.type === 'friends') { followers = await SETTINGS.find({ pid: friends }); communities = []; @@ -289,13 +320,9 @@ async function userRelations(req, res, userID) { } else { followers = await database.getFollowedUsers(userContent); communities = userContent.followed_communities; - communityMap = await util.getCommunityHash(); selection = 2; } - if (followers[0] === '0') { - followers.splice(0, 0); - } if (communities[0] === '0') { communities.splice(0, 1); } @@ -331,73 +358,27 @@ async function userRelations(req, res, userID) { }); } -async function morePosts(req, res, userID) { - let offset = parseInt(req.query.offset); - const userContent = await database.getUserContent(req.pid); - const communityMap = await util.getCommunityHash(); - if (!offset) { - offset = 0; - } - const posts = await database.getUserPostsOffset(userID, config.post_limit, offset); +async function morePosts(req: Request, res: Response, userID: number): Promise { - const bundle = { - posts, - numPosts: posts.length, - open: true, - communityMap, - userContent, - lang: req.lang, - mii_image_CDN: config.mii_image_CDN, - link: `/users/${userID}/more?offset=${offset + posts.length}&pjax=true` - }; - - if (posts.length > 0) { - res.render(req.directory + '/partials/posts_list.ejs', { - communityMap: communityMap, - moment: moment, - database: database, - bundle, - account_server: config.account_server_domain.slice(8), - cdnURL: config.CDN_domain, - lang: req.lang, - mii_image_CDN: config.mii_image_CDN, - pid: req.pid, - moderator: req.moderator - }); - } else { - res.sendStatus(204); + let offset: number | undefined = undefined; + if (typeof req.query.offset === 'string') { + offset = parseInt(req.query.offset); } -} -async function moreYeahPosts(req, res, userID) { - let offset = parseInt(req.query.offset); - const parentUserContent = await database.getUserContent(userID); const userContent = await database.getUserContent(req.pid); - const communityMap = await util.getCommunityHash(); - if (!offset) { - offset = 0; - } - const likesArray = await userContent.likes.slice().reverse(); - const posts = await POST.aggregate([ - { $match: { id: { $in: likesArray }, removed: false } }, - {$addFields: { - '__order': { $indexOfArray: [ likesArray, '$id' ] } - }}, - { $sort: { '__order': 1 } }, - { $project: { index: 0, _id: 0 } }, - { $skip : offset }, - { $limit: config.post_limit } - ]); + const communityMap = util.getCommunityHash(); + + const posts = await database.getUserPostsOffset(userID, config.post_limit, offset); const bundle = { - posts: posts.reverse(), + posts, numPosts: posts.length, open: true, communityMap, userContent, lang: req.lang, mii_image_CDN: config.mii_image_CDN, - link: `/users/${userID}/yeahs/more?offset=${offset + posts.length}&pjax=true` + link: `/users/${userID}/more?offset=${(offset ?? 0) + posts.length}&pjax=true` }; if (posts.length > 0) { @@ -417,4 +398,5 @@ async function moreYeahPosts(req, res, userID) { res.sendStatus(204); } } -module.exports = router; + +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/console/web.js b/src/services/juxt-web/routes/console/web.ts similarity index 92% rename from src/services/juxt-web/routes/console/web.js rename to src/services/juxt-web/routes/console/web.ts index 0ccfa181..d5f2add1 100644 --- a/src/services/juxt-web/routes/console/web.js +++ b/src/services/juxt-web/routes/console/web.ts @@ -1,6 +1,7 @@ -const express = require('express'); +import path from 'path'; +import express from 'express'; + const router = express.Router(); -const path = require('path'); router.get('/', function (req, res) { res.redirect('/titles/show'); @@ -31,4 +32,4 @@ router.get('/favicon.ico', function (req, res) { res.sendFile('/images/favicon.ico', {root: path.join(__dirname, '../../../../webfiles/' + req.directory)}); }); -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/index.js b/src/services/juxt-web/routes/index.js deleted file mode 100644 index 12bde1fc..00000000 --- a/src/services/juxt-web/routes/index.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - PORTAL_SHOW: require('./console/show'), - PORTAL_COMMUNITIES: require('./console/communities'), - PORTAL_USER: require('./console/userpage'), - PORTAL_POST: require('./console/posts'), - PORTAL_FEED: require('./console/feed'), - PORTAL_NEWS: require('./console/notifications'), - PORTAL_MESSAGES: require('./console/messages'), - PORTAL_TOPICS: require('./console/topics'), - WEB_LOGIN: require('./web/login'), - ROBOTS: require('./web/robots'), - PWA: require('./web/pwa'), - ADMIN: require('./admin/admin'), - WEB_FILES: require('./console/web'), -}; \ No newline at end of file diff --git a/src/services/juxt-web/routes/index.ts b/src/services/juxt-web/routes/index.ts new file mode 100644 index 00000000..a4c66f36 --- /dev/null +++ b/src/services/juxt-web/routes/index.ts @@ -0,0 +1,13 @@ +export { default as PORTAL_SHOW } from '@/services/juxt-web/routes/console/show'; +export { default as PORTAL_COMMUNITIES } from '@/services/juxt-web/routes/console/communities'; +export { default as PORTAL_USER } from '@/services/juxt-web/routes/console/userpage'; +export { default as PORTAL_POST } from '@/services/juxt-web/routes/console/posts'; +export { default as PORTAL_FEED } from '@/services/juxt-web/routes/console/feed'; +export { default as PORTAL_NEWS } from '@/services/juxt-web/routes/console/notifications'; +export { default as PORTAL_MESSAGES } from '@/services/juxt-web/routes/console/messages'; +export { default as PORTAL_TOPICS } from '@/services/juxt-web/routes/console/topics'; +export { default as WEB_LOGIN } from '@/services/juxt-web/routes/web/login'; +export { default as ROBOTS } from '@/services/juxt-web/routes/web/robots'; +export { default as PWA } from '@/services/juxt-web/routes/web/pwa'; +export { default as ADMIN } from '@/services/juxt-web/routes/admin/admin'; +export { default as WEB_FILES } from '@/services/juxt-web/routes/console/web'; \ No newline at end of file diff --git a/src/services/juxt-web/routes/web/login.js b/src/services/juxt-web/routes/web/login.ts similarity index 79% rename from src/services/juxt-web/routes/web/login.js rename to src/services/juxt-web/routes/web/login.ts index 3a46a229..f43316a4 100644 --- a/src/services/juxt-web/routes/web/login.js +++ b/src/services/juxt-web/routes/web/login.ts @@ -1,11 +1,9 @@ -const express = require('express'); +import express from 'express'; +import * as database from '@/database'; +import * as util from '@/util'; +import config from '../../../../../config.json'; + const router = express.Router(); -const parseString = require('xml2js').parseString; -const database = require('../../../../database'); -const util = require('../../../../util'); -const config = require('../../../../../config.json'); -const request = require('request'); -const logger = require('../../../../logger'); router.get('/', async function (req, res) { res.render(req.directory + '/login.ejs', {toast: null, cdnURL: config.CDN_domain,}); @@ -38,14 +36,11 @@ router.post('/', async (req, res) => { const pid = PNID.pid; - let discovery = await database.getEndPoint(config.server_environment); - if (!discovery) { - discovery = { - status: 5 - }; - } + const discovery = await database.getEndPoint(config.server_environment); + const status = discovery?.status ?? 5; + let message = ''; - switch (discovery.status) { + switch (status) { case 3: message = 'Juxt is currently undergoing maintenance. Please try again later.'; break; @@ -56,7 +51,7 @@ router.post('/', async (req, res) => { message = 'Juxt is currently unavailable. Please try again later.'; break; } - if (discovery.status !== 0) { + if (status !== 0) { return res.render(req.directory + '/error.ejs', { code: 504, message: message, @@ -74,5 +69,4 @@ router.post('/', async (req, res) => { res.redirect('/'); }); - -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/web/pwa.js b/src/services/juxt-web/routes/web/pwa.ts similarity index 82% rename from src/services/juxt-web/routes/web/pwa.js rename to src/services/juxt-web/routes/web/pwa.ts index bdcad2ff..3d93a496 100644 --- a/src/services/juxt-web/routes/web/pwa.js +++ b/src/services/juxt-web/routes/web/pwa.ts @@ -1,5 +1,6 @@ -const express = require('express'); -const path = require('path'); +import path from 'path'; +import express from 'express'; + const router = express.Router(); router.get('/icons/:filename', function (req, res) { @@ -12,4 +13,4 @@ router.get('/manifest.json', function (req, res) { res.sendFile('manifest.json', {root: path.join(__dirname, '../../../../webfiles/web')}); }); -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/web/robots.js b/src/services/juxt-web/routes/web/robots.ts similarity index 69% rename from src/services/juxt-web/routes/web/robots.js rename to src/services/juxt-web/routes/web/robots.ts index 859b5450..d2425f01 100644 --- a/src/services/juxt-web/routes/web/robots.js +++ b/src/services/juxt-web/routes/web/robots.ts @@ -1,5 +1,6 @@ -const express = require('express'); -const path = require('path'); +import path from 'path'; +import express from 'express'; + const router = express.Router(); router.get('/', function (req, res) { @@ -7,5 +8,4 @@ router.get('/', function (req, res) { res.sendFile('robots.txt', {root: path.join(__dirname, '../../../../webfiles/web')}); }); - -module.exports = router; +export default router; \ No newline at end of file diff --git a/src/types/common/express-session.d.ts b/src/types/common/express-session.d.ts new file mode 100644 index 00000000..db52b372 --- /dev/null +++ b/src/types/common/express-session.d.ts @@ -0,0 +1,9 @@ +import type { User } from './user'; + +declare module 'express-session' { + + interface SessionData { + pid: number | null; + user: User | null; + } +} \ No newline at end of file diff --git a/src/types/common/express-subdomain.d.ts b/src/types/common/express-subdomain.d.ts new file mode 100644 index 00000000..b4ae4902 --- /dev/null +++ b/src/types/common/express-subdomain.d.ts @@ -0,0 +1,16 @@ +// * Credit to https://github.com/bmullan91/express-subdomain/pull/61 for the types! + +declare module 'express-subdomain'{ + import type { Request, Response, Router } from 'express'; + + /** + * @description The subdomain function. + * @param subdomain The subdomain to listen on. + * @param fn The listener function, takes a response and request. + * @returns A function call to the value passed as FN, or void (the next function). + */ + export default function subdomain( + subdomain: string, + fn: Router + ): (req: Request, res: Response, next: () => void) => void | typeof fn; +} \ No newline at end of file diff --git a/src/types/common/express.d.ts b/src/types/common/express.d.ts new file mode 100644 index 00000000..e84333ad --- /dev/null +++ b/src/types/common/express.d.ts @@ -0,0 +1,23 @@ +import type translations from '../../translations'; +import type { ParamPack } from './param-pack'; +import type { User } from './user'; + +declare global { + + namespace Express { + + interface Request { + timerDate: number; + directory: string; + lang: typeof translations.EN; + isWrite: boolean; + guest_access: boolean; + new_users: boolean; + pid: number; + user: User; + token: string; + paramPackData: ParamPack | null; + moderator: boolean; + } + } +} \ No newline at end of file diff --git a/src/types/common/param-pack.ts b/src/types/common/param-pack.ts new file mode 100644 index 00000000..7bb252bd --- /dev/null +++ b/src/types/common/param-pack.ts @@ -0,0 +1,16 @@ +export interface ParamPack { + title_id: string; + access_key: string; + platform_id: string; + region_id: string; + language_id: string; + country_id: string; + area_id: string; + network_restriction: string; + friend_restriction: string; + rating_restriction: string; + rating_organization: string; + transferable_id: string; + tz_name: string; + utc_offset: string; +} \ No newline at end of file diff --git a/src/types/common/token.ts b/src/types/common/token.ts new file mode 100644 index 00000000..e414ba36 --- /dev/null +++ b/src/types/common/token.ts @@ -0,0 +1,8 @@ +export interface Token { + system_type: number; + token_type: number; + pid: number; + access_level: number; + title_id: bigint; + expire_time: bigint; +} \ No newline at end of file diff --git a/src/types/common/user.ts b/src/types/common/user.ts new file mode 100644 index 00000000..d81f8902 --- /dev/null +++ b/src/types/common/user.ts @@ -0,0 +1,15 @@ +import type { Mii } from '@pretendonetwork/grpc/api/mii'; + +export interface User { + deleted: boolean; + creationDate: string; + pid: number; + username: string; + accessLevel: number; + serverAccessLevel: string; + mii: Mii | undefined; + gender: string; + country: string; + language: string; + emailAddress: string; +} \ No newline at end of file diff --git a/src/types/mongoose-fuzzy-search-next.d.ts b/src/types/mongoose-fuzzy-search-next.d.ts new file mode 100644 index 00000000..6f80331e --- /dev/null +++ b/src/types/mongoose-fuzzy-search-next.d.ts @@ -0,0 +1,6 @@ +declare module 'mongoose-fuzzy-search-next' { + + import type { FilterQuery } from 'mongoose'; + + export function FuzzySearch(fields: string[], searchKey: string): FilterQuery +} diff --git a/src/types/mongoose/communities.ts b/src/types/mongoose/communities.ts new file mode 100644 index 00000000..14d5d4ae --- /dev/null +++ b/src/types/mongoose/communities.ts @@ -0,0 +1,50 @@ +import type { Model, Types, HydratedDocument } from 'mongoose'; + +export interface IPermissions { + open: boolean, + minimum_new_post_access_level: number, + minimum_new_comment_access_level: number, + minimum_new_community_access_level: number +} + +export type PermissionsModel = Model; + +enum COMMUNITY_TYPE { + Main = 0, + Sub = 1, + Announcement = 2, + Private = 3 +} + +export interface ICommunity { + platform_id: number; + name: string; + description: string; + type: COMMUNITY_TYPE; + parent: string; + admins: Types.Array; + created_at: Date; + empathy_count: number; + followers: number; + has_shop_page: number; + icon: string; + title_ids: Types.Array; + title_id: Types.Array; + community_id: string; + olive_community_id: string; + is_recommended: number; + app_data: string; + permissions: IPermissions; + children?: HydratedCommunityDocument[]; +} + +export interface ICommunityMethods { + upEmpathy(): Promise; + downEmpathy(): Promise; + upFollower(): Promise; + downFollower(): Promise; +} + +export type CommunityModel = Model; + +export type HydratedCommunityDocument = HydratedDocument; \ No newline at end of file diff --git a/src/types/mongoose/content.ts b/src/types/mongoose/content.ts new file mode 100644 index 00000000..20fc6f08 --- /dev/null +++ b/src/types/mongoose/content.ts @@ -0,0 +1,21 @@ +import type { Model, Types, HydratedDocument } from 'mongoose'; + +export interface IContent { + pid: number; + followed_communities: Types.Array; + followed_users: Types.Array; + following_users: Types.Array; +} + +export interface IContentMethods { + addToCommunities(postID: string): Promise; + removeFromCommunities(postID: string): Promise; + addToUsers(postID: number): Promise; + removeFromUsers(postID: number): Promise; + addToFollowers(postID: number): Promise; + removeFromFollowers(postID: number): Promise; +} + +export type ContentModel = Model; + +export type HydratedContentDocument = HydratedDocument; \ No newline at end of file diff --git a/src/types/mongoose/conversation.ts b/src/types/mongoose/conversation.ts new file mode 100644 index 00000000..6468ee8a --- /dev/null +++ b/src/types/mongoose/conversation.ts @@ -0,0 +1,26 @@ +import type { Model, Types, HydratedDocument } from 'mongoose'; + +export interface IUser { + pid: number; + official: boolean; + read: boolean; +} + +export type UserModel = Model; + +export interface IConversation { + id: string; + created_at: Date; + last_updated: Date; + message_preview: string; + users: Types.Array; +} + +export interface IConversationMethods { + newMessage(message: string, senderPID: number): Promise; + markAsRead(receivedPID: number): Promise; +} + +export type ConversationModel = Model; + +export type HydratedConversationDocument = HydratedDocument; \ No newline at end of file diff --git a/src/types/mongoose/endpoint.ts b/src/types/mongoose/endpoint.ts new file mode 100644 index 00000000..d0eed644 --- /dev/null +++ b/src/types/mongoose/endpoint.ts @@ -0,0 +1,17 @@ +import type { Model, HydratedDocument } from 'mongoose'; + +export interface IEndpoint { + status: number; + server_access_level: string; + topics: boolean; + guest_access: boolean; + new_users: boolean; + host: string; + api_host: string; + portal_host: string; + n3ds_host: string; +} + +export type EndpointModel = Model; + +export type HydratedEndpointDocument = HydratedDocument; \ No newline at end of file diff --git a/src/types/mongoose/notifications.ts b/src/types/mongoose/notifications.ts new file mode 100644 index 00000000..076e53fd --- /dev/null +++ b/src/types/mongoose/notifications.ts @@ -0,0 +1,24 @@ +import type { Model, Types, HydratedDocument } from 'mongoose'; + +export interface IUser { + user: string; + timestamp: Date +} + +export interface INotification { + pid: string; + type: string; + link: string; + objectID: string; + users: Types.Array; + read: boolean; + lastUpdated: Date; +} + +export interface INotificationMethods { + markRead(): Promise; +} + +export type NotificationModel = Model; + +export type HydratedNotificationDocument = HydratedDocument; \ No newline at end of file diff --git a/src/types/mongoose/post.ts b/src/types/mongoose/post.ts new file mode 100644 index 00000000..e6d66c95 --- /dev/null +++ b/src/types/mongoose/post.ts @@ -0,0 +1,49 @@ +import type { Model, Types, HydratedDocument } from 'mongoose'; + +export interface IPost { + id: string; + title_id: string; + screen_name: string; + body: string; + app_data: string; + painting: string; + screenshot: string; + screenshot_length: number; + search_key: Types.Array; + topic_tag: string; + community_id: string; + created_at: Date; + feeling_id: number; + is_autopost: number; + is_community_private_autopost: number; + is_spoiler: number; + is_app_jumpable: number; + empathy_count: number; + country_id: number; + language_id: number; + mii: string; + mii_face_url: string; + pid: number; + platform_id: number; + region_id: number; + parent: string; + reply_count: number; + verified: boolean; + message_to_pid: string; + removed: boolean; + removed_reason: string; + removed_by: number; + removed_at: Date; + yeahs: Types.Array +} + +export interface IPostMethods { + unReply(): Promise; + downReply(): Promise; + removePost(reason: string, pid: number): Promise; + unRemove(reason: string): Promise; +} + +export type PostModel = Model; + +export type HydratedPostDocument = HydratedDocument; \ No newline at end of file diff --git a/src/types/mongoose/report.ts b/src/types/mongoose/report.ts new file mode 100644 index 00000000..c0e9e585 --- /dev/null +++ b/src/types/mongoose/report.ts @@ -0,0 +1,22 @@ +import type { Model, HydratedDocument } from 'mongoose'; + +export interface IReport { + pid: number; + reported_by: number; + post_id: string; + reason: number; + message: string; + created_at: Date; + resolved: boolean; + note: string; + resolved_by: number; + resolved_at: Date; +} + +export interface IReportMethods { + resolve(pid: number, note: string): Promise; +} + +export type ReportModel = Model; + +export type HydratedReportDocument = HydratedDocument; \ No newline at end of file diff --git a/src/types/mongoose/settings.ts b/src/types/mongoose/settings.ts new file mode 100644 index 00000000..896f86a0 --- /dev/null +++ b/src/types/mongoose/settings.ts @@ -0,0 +1,34 @@ +import type { Model, HydratedDocument } from 'mongoose'; + +export interface ISettings { + pid: number; + screen_name: string; + account_status: number; + ban_lift_date: Date; + ban_reason: string; + profile_comment: string; + profile_comment_visibility: boolean; + game_skill: number; + game_skill_visibility: boolean; + birthday_visibility: boolean; + relationship_visibility: boolean; + country_visibility: boolean; + profile_favorite_community_visibility: boolean; + receive_notifications: boolean; + created_at: Date; +} + +export interface ISettingsMethods { + updateComment(comment: string): Promise; + updateSkill(skill: number): Promise; + commentVisible(active: boolean): Promise; + skillVisible(active: boolean): Promise; + birthdayVisible(active: boolean): Promise; + relationshipVisible(active: boolean): Promise; + countryVisible(active: boolean): Promise; + favCommunityVisible(active: boolean): Promise; +} + +export type SettingsModel = Model; + +export type HydratedSettingsDocument = HydratedDocument; \ No newline at end of file diff --git a/src/types/node-snowflake.d.ts b/src/types/node-snowflake.d.ts new file mode 100644 index 00000000..6404ab0b --- /dev/null +++ b/src/types/node-snowflake.d.ts @@ -0,0 +1,14 @@ +declare module 'node-snowflake' { + export interface SnowflakeInitConfig { + worker_id: number; + data_center_id: number; + sequence: number; + } + + export function Server(port: number): void; + + export const Snowflake: { + init: (config: SnowflakeInitConfig) => void; + nextId: (workerId?: number, dataCenterId?: number, sequence?: number) => string; + }; +} \ No newline at end of file diff --git a/src/types/tga.d.ts b/src/types/tga.d.ts new file mode 100644 index 00000000..16eb4a27 --- /dev/null +++ b/src/types/tga.d.ts @@ -0,0 +1,58 @@ +declare module 'tga' { + export interface TGAHeader { + idLength: number; + colorMapType: number; + dataType: number; + colorMapOrigin: number; + colorMapLength: number; + colorMapDepth: number; + xOrigin: number; + yOrigin: number; + width: number; + height: number; + bitsPerPixel: number; + flags: number; + id: string; + } + + export default class TGA { + constructor(buf: Buffer, opt?: { dontFixAlpha: boolean }); + + static createTgaBuffer(width: number, height: number, pixels: Uint8Array, dontFlipY: boolean): Buffer; + + parseHeader(): void; + parseFooter(): void; + parseExtension(extensionAreaOffset: number): void; + readColor(offset: number, bytesPerPixel: number): Uint8Array; + readColorWithColorMap(offset: number): Uint8Array; + readColorAuto(offset: number, bytesPerPixel: number, isUsingColorMap: boolean): Uint8Array; + parseColorMap(): void; + setPixel(pixels: Uint8Array, idx: number, color: Uint8Array): void; + parsePixels(): void; + parse(): void; + fixForAlpha(): void; + + public dontFixAlpha: boolean; + public _buff: Buffer; + public data: Uint8Array; + public currentOffset: number; + + public header: TGAHeader; + + public width: number; + public height: number; + + public isUsingColorMap: boolean; + public isUsingRLE: boolean; + public isGray: boolean; + + public hasAlpha: boolean; + + public isFlipX: boolean; + public isFlipY: boolean; + + public colorMap: Uint8Array; + + public pixels: Uint8Array; + } +} \ No newline at end of file diff --git a/src/util.js b/src/util.ts similarity index 58% rename from src/util.js rename to src/util.ts index 53f2b428..d37e9513 100644 --- a/src/util.js +++ b/src/util.ts @@ -1,38 +1,44 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ -const crypto = require('crypto'); -const database = require('./database'); -const logger = require('./logger'); -const grpc = require('nice-grpc'); -const config = require('../config.json'); -const { SETTINGS } = require('./models/settings'); -const { CONTENT } = require('./models/content'); -const { NOTIFICATION } = require('./models/notifications'); -const { COMMUNITY } = require('./models/communities'); -const { AccountDefinition } = require('@pretendonetwork/grpc/account/account_service'); -const { FriendsDefinition } = require('@pretendonetwork/grpc/friends/friends_service'); -const { APIDefinition } = require('@pretendonetwork/grpc/api/api_service'); -const translations = require('./translations'); -const HashMap = require('hashmap'); -const TGA = require('tga'); -const pako = require('pako'); -const PNG = require('pngjs').PNG; -const bmp = require('bmp-js'); -const aws = require('aws-sdk'); -const crc32 = require('crc/crc32'); -const communityMap = new HashMap(); -const userMap = new HashMap(); +import aws from 'aws-sdk'; +import bmp from 'bmp-js'; +import crc32 from 'crc/crc32'; +import crypto from 'crypto'; +import HashMap from 'hashmap'; +import { createChannel, createClient, Metadata } from 'nice-grpc'; +import pako from 'pako'; +import { PNG } from 'pngjs'; +import sharp from 'sharp'; +import TGA from 'tga'; +import { AccountDefinition } from '@pretendonetwork/grpc/account/account_service'; +import { APIDefinition } from '@pretendonetwork/grpc/api/api_service'; +import { FriendsDefinition } from '@pretendonetwork/grpc/friends/friends_service'; +import logger from '@/logger'; +import * as database from '@/database'; +import { COMMUNITY } from '@/models/communities'; +import { CONTENT } from '@/models/content'; +import { NOTIFICATION } from '@/models/notifications'; +import { SETTINGS } from '@/models/settings'; +import translations from '@/translations'; +import type { LoginResponse as ApiLoginResponse } from '@pretendonetwork/grpc/api/login_rpc'; +import type { FriendRequest } from '@pretendonetwork/grpc/friends/friend_request'; +import type { ParamPack } from '@/types/common/param-pack'; +import type { Token } from '@/types/common/token'; +import type { User } from '@/types/common/user'; +import type { INotification } from '@/types/mongoose/notifications'; +import config from '../config.json'; + +const communityMap = new HashMap(); +const userMap = new HashMap(); const { ip: friendsIP, port: friendsPort, api_key: friendsKey } = config.grpc.friends; -const friendsChannel = grpc.createChannel(`${friendsIP}:${friendsPort}`); -const friendsClient = grpc.createClient(FriendsDefinition, friendsChannel); +const friendsChannel = createChannel(`${friendsIP}:${friendsPort}`); +const friendsClient = createClient(FriendsDefinition, friendsChannel); const { ip: apiIP, port: apiPort, api_key: apiKey } = config.grpc.account; -const apiChannel = grpc.createChannel(`${apiIP}:${apiPort}`); -const apiClient = grpc.createClient(APIDefinition, apiChannel); +const apiChannel = createChannel(`${apiIP}:${apiPort}`); +const apiClient = createClient(APIDefinition, apiChannel); -const accountChannel = grpc.createChannel(`${apiIP}:${apiPort}`); -const accountClient = grpc.createClient(AccountDefinition, accountChannel); +const accountChannel = createChannel(`${apiIP}:${apiPort}`); +const accountClient = createClient(AccountDefinition, accountChannel); const spacesEndpoint = new aws.Endpoint('nyc3.digitaloceanspaces.com'); const s3 = new aws.S3({ @@ -43,8 +49,8 @@ const s3 = new aws.S3({ nameCache(); -function nameCache() { - database.connect().then(async e => { +function nameCache(): void { + database.connect().then(async _ => { const communities = await COMMUNITY.find(); if (communities !== null) { for (let i = 0; i < communities.length; i++ ) { @@ -74,15 +80,15 @@ function nameCache() { } // TODO - This doesn't belong here, just hacking it in. Gonna redo this whole server anyway so fuck it -const INVALID_POST_BODY_REGEX = /[^\p{L}\p{P}\d\n\r$^¨←→↑↓√¦⇒⇔¤¢€£¥™©®+×÷=±∞˘˙¸˛˜°¹²³♭♪¬¯¼½¾♡♥●◆■▲▼☆★♀♂<> ]/gu; -async function create_user(pid, experience, notifications) { - const pnid = await this.getUserDataFromPid(pid); +export const INVALID_POST_BODY_REGEX = /[^\p{L}\p{P}\d\n\r$^¨←→↑↓√¦⇒⇔¤¢€£¥™©®+×÷=±∞˘˙¸˛˜°¹²³♭♪¬¯¼½¾♡♥●◆■▲▼☆★♀♂<> ]/gu; +export async function create_user(pid: number, experience: number, notifications: boolean): Promise { + const pnid = await getUserDataFromPid(pid); if (!pnid) { return; } const newSettings = { pid: pid, - screen_name: pnid.mii.name, + screen_name: pnid.mii?.name, game_skill: experience, receive_notifications: notifications, }; @@ -95,25 +101,32 @@ async function create_user(pid, experience, notifications) { const newContentObj = new CONTENT(newContent); await newContentObj.save(); - this.setName(pid, pnid.mii.name); + setName(pid, pnid.mii?.name); } -function decodeParamPack(paramPack) { - /* Decode base64 */ - let dec = Buffer.from(paramPack, 'base64').toString('ascii'); - /* Remove starting and ending '/', split into array */ - dec = dec.slice(1, -1).split('\\'); - /* Parameters are in the format [name, val, name, val]. Copy into out{}. */ - const out = {}; - for (let i = 0; i < dec.length; i += 2) { - out[dec[i].trim()] = dec[i + 1].trim(); - } - return out; + +export function decodeParamPack(paramPack: string): ParamPack { + const values = Buffer.from(paramPack, 'base64').toString().split('\\'); + const entries = values.filter(value => value).reduce((entries: string[][], value: string, index: number) => { + if (0 === index % 2) { + entries.push([value]); + } else { + entries[Math.ceil(index / 2 - 1)].push(value); + } + + return entries; + }, []); + + return Object.fromEntries(entries); } -function processServiceToken(encryptedToken) { + +export function processServiceToken(encryptedToken: string | undefined): number | null { + if (!encryptedToken) { + return null; + } try { const B64token = Buffer.from(encryptedToken, 'base64'); - const decryptedToken = this.decryptToken(B64token); - const token = this.unpackToken(decryptedToken); + const decryptedToken = decryptToken(B64token); + const token = unpackToken(decryptedToken); // * Only allow token types 1 (Wii U) and 2 (3DS) if (token.system_type !== 1 && token.system_type !== 2) { @@ -127,7 +140,8 @@ function processServiceToken(encryptedToken) { } } -function decryptToken(token) { + +export function decryptToken(token: Buffer): Buffer { if (!config.aes_key) { throw new Error('Service token AES key not found. Set config.aes_key'); } @@ -151,7 +165,8 @@ function decryptToken(token) { return decrypted; } -function unpackToken(token) { + +export function unpackToken(token: Buffer): Token { return { system_type: token.readUInt8(0x0), token_type: token.readUInt8(0x1), @@ -161,14 +176,21 @@ function unpackToken(token) { access_level: token.readInt8(0x16) }; } -async function processPainting(painting, isTGA) { + +export function processPainting(painting: string | null, isTGA: boolean): string | null { + + if (painting === null) { + return null; + } + if (isTGA) { const paintingBuffer = Buffer.from(painting, 'base64'); - let output = ''; + let output: Uint8Array; try { output = pako.inflate(paintingBuffer); } catch (err) { console.error(err); + return null; } let tga; try { @@ -181,24 +203,27 @@ async function processPainting(painting, isTGA) { width: tga.width, height: tga.height }); - png.data = tga.pixels; - return PNG.sync.write(png); + png.data = Buffer.from(tga.pixels); + return PNG.sync.write(png).toString('base64'); //return `data:image/png;base64,${pngBuffer.toString('base64')}`; } else { const paintingBuffer = Buffer.from(painting, 'base64'); const bitmap = bmp.decode(paintingBuffer); - const tga = this.createBMPTgaBuffer(bitmap.width, bitmap.height, bitmap.data, false); + const tga = createBMPTgaBuffer(bitmap.width, bitmap.height, bitmap.data, false); - let output; + let output: Uint8Array; try { output = pako.deflate(tga, {level: 6}); + return Buffer.from(output).toString('base64'); } catch (err) { console.error(err); } - return new Buffer(output).toString('base64'); + + return null; } } -function nintendoPasswordHash(password, pid) { + +export function nintendoPasswordHash(password: string, pid: number): string { const pidBuffer = Buffer.alloc(4); pidBuffer.writeUInt32LE(pid); @@ -209,31 +234,35 @@ function nintendoPasswordHash(password, pid) { ]); return crypto.createHash('sha256').update(unpacked).digest().toString('hex'); } -function getCommunityHash() { + +export function getCommunityHash(): HashMap { return communityMap; } -function getUserHash() { + +export function getUserHash(): HashMap { return userMap; } -function refreshCache() { + +export function refreshCache(): void { nameCache(); } -function setName(pid, name) { + +export function setName(pid: number, name: string | undefined): void { if (!pid || !name) { return; } userMap.delete(pid); userMap.set(pid, name.replace(/[\u{0080}-\u{FFFF}]/gu,'').replace(/\u202e/g, '')); } -function resizeImage(file, width, height) { - sharp(file) + +// ? TODO is this used? +export async function resizeImage(file: sharp.SharpOptions, width: number, height: number): Promise { + return sharp(file) .resize({ height: height, width: width }) - .toBuffer() - .then(data => { - return data; - }); + .toBuffer(); } -function createBMPTgaBuffer(width, height, pixels, dontFlipY) { + +export function createBMPTgaBuffer(width: number, height: number, pixels: Buffer, dontFlipY: boolean): Buffer { const buffer = Buffer.alloc(18 + pixels.length); // write header buffer.writeInt8(0, 0); @@ -262,7 +291,8 @@ function createBMPTgaBuffer(width, height, pixels, dontFlipY) { return buffer; } -function processLanguage(paramPackData) { + +export function processLanguage(paramPackData?: ParamPack): typeof translations.EN { if (!paramPackData) { return translations.EN; } @@ -295,7 +325,8 @@ function processLanguage(paramPackData) { return translations.EN; } } -async function uploadCDNAsset(bucket, key, data, acl) { + +export async function uploadCDNAsset(bucket: string, key: string, data: Buffer, acl: string): Promise { const awsPutParams = { Body: data, Key: key, @@ -305,7 +336,8 @@ async function uploadCDNAsset(bucket, key, data, acl) { await s3.putObject(awsPutParams).promise(); } -async function newNotification(notification) { + +export async function newNotification(notification: Omit): Promise { const now = new Date(); if (notification.type === 'follow') { // { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` } @@ -313,7 +345,7 @@ async function newNotification(notification) { if (existingNotification) { existingNotification.lastUpdated = now; existingNotification.read = false; - return await existingNotification.save(); + await existingNotification.save(); } const last60min = new Date(now.getTime() - 60 * 60 * 1000); existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'follow', lastUpdated: { $gte: last60min } }); @@ -326,7 +358,7 @@ async function newNotification(notification) { existingNotification.link = notification.link; existingNotification.objectID = notification.objectID; existingNotification.read = false; - return await existingNotification.save(); + await existingNotification.save(); } else { const newNotification = new NOTIFICATION({ pid: notification.pid, @@ -343,47 +375,14 @@ async function newNotification(notification) { await newNotification.save(); } } - /*else if(notification.type === 'yeah') { - // { pid: userToFollowContent.pid, type: "follow", objectID: req.pid, link: `/users/${req.pid}` } - let existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, objectID: notification.objectID }) - if(existingNotification) { - existingNotification.lastUpdated = new Date(); - return await existingNotification.save(); - } - existingNotification = await NOTIFICATION.findOne({ pid: notification.pid, type: 'yeah' }); - if(existingNotification) { - existingNotification.users.push({ - user: notification.objectID, - timeStamp: new Date() - }); - existingNotification.lastUpdated = new Date(); - existingNotification.link = notification.link; - existingNotification.objectID = notification.objectID; - return await existingNotification.save(); - } - else { - let newNotification = new NOTIFICATION({ - pid: notification.pid, - type: notification.type, - users: [{ - user: notification.objectID, - timestamp: new Date() - }], - link: notification.link, - objectID: notification.objectID, - read: false, - lastUpdated: new Date() - }); - await newNotification.save(); - } - }*/ } -async function getFriends(pid) { + +export async function getFriends(pid: number): Promise { try { const pids = await friendsClient.getUserFriendPIDs({ pid: pid }, { - metadata: grpc.Metadata({ + metadata: Metadata({ 'X-API-Key': friendsKey }) }); @@ -392,12 +391,13 @@ async function getFriends(pid) { return []; } } -async function getFriendRequests(pid) { + +export async function getFriendRequests(pid: number): Promise { try { const requests = await friendsClient.getUserFriendRequestsIncoming({ pid: pid }, { - metadata: grpc.Metadata({ + metadata: Metadata({ 'X-API-Key': friendsKey }) }); @@ -406,70 +406,49 @@ async function getFriendRequests(pid) { return []; } } -async function login(username, password) { + +export async function login(username: string, password: string): Promise { return await apiClient.login({ username: username, password: password, grantType: 'password' }, { - metadata: grpc.Metadata({ + metadata: Metadata({ 'X-API-Key': apiKey }) }); } -async function refreshLogin(refreshToken) { + +export async function refreshLogin(refreshToken: string): Promise { return await apiClient.login({ refreshToken: refreshToken }, { - metadata: grpc.Metadata({ + metadata: Metadata({ 'X-API-Key': apiKey }) }); } -async function getUserDataFromToken(token) { + +export async function getUserDataFromToken(token: string): Promise { return apiClient.getUserData({}, { - metadata: grpc.Metadata({ + metadata: Metadata({ 'X-API-Key': apiKey, 'X-Token': token }) }); } -async function getUserDataFromPid(pid) { + +export async function getUserDataFromPid(pid: number): Promise { return accountClient.getUserData({ pid: pid }, { - metadata: grpc.Metadata({ + metadata: Metadata({ 'X-API-Key': apiKey }) }); } -async function getPid(token) { - const user = await this.getUserDataFromToken(token); + +export async function getPid(token: string): Promise { + const user = await getUserDataFromToken(token); return user.pid; } -module.exports = { - decodeParamPack, - processServiceToken, - decryptToken, - unpackToken, - processPainting, - nintendoPasswordHash, - getCommunityHash, - getUserHash, - refreshCache, - setName, - resizeImage, - createBMPTgaBuffer, - processLanguage, - uploadCDNAsset, - newNotification, - getFriends, - getFriendRequests, - login, - refreshLogin, - getUserDataFromToken, - getUserDataFromPid, - getPid, - create_user, - INVALID_POST_BODY_REGEX -}; diff --git a/src/webfiles/.eslintrc.json b/src/webfiles/.eslintrc.json new file mode 100644 index 00000000..efb731ed --- /dev/null +++ b/src/webfiles/.eslintrc.json @@ -0,0 +1,55 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es6": true + }, + "globals": { + "wiiu": "readonly", + "wiiuMemo": "readonly", + "wiiuBrowser": "readonly", + "wiiuSound": "readonly", + "wiiuErrorViewer": "readonly", + "wiiuDialog": "readonly", + "wiiuMainApplication": "readonly", + "Olv": "readonly" + }, + "extends": [ + "eslint:recommended" + ], + "rules": { + "require-atomic-updates": "warn", + "no-case-declarations": "off", + "no-empty": "off", + "no-console": "off", + "linebreak-style": "off", + "no-global-assign": "off", + "prefer-const": "error", + "no-var": "error", + "no-unused-vars": "off", + "no-extra-semi": "off", + "keyword-spacing": "off", + "curly": "error", + "brace-style": "error", + "one-var": [ + "error", + "never" + ], + "indent": [ + "error", + "tab", + { + "SwitchCase": 1 + } + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ] + }, + "root": true +} \ No newline at end of file diff --git a/src/webfiles/portal/js/juxt.js b/src/webfiles/portal/js/juxt.js index 794a7eb7..40bd4f56 100644 --- a/src/webfiles/portal/js/juxt.js +++ b/src/webfiles/portal/js/juxt.js @@ -1,4 +1,3 @@ -/* eslint-disable */ var scrollPosition, pjax; var updateCheck = setInterval(checkForUpdates, 30000); var inputCheck = setInterval(input, 100); diff --git a/tsconfig.json b/tsconfig.json index 0de55463..e6846d48 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,7 @@ } }, "include": ["src"], + "exclude": ["src/webfiles/**"], "watchOptions": { "watchFile": "useFsEvents", "watchDirectory": "useFsEvents",