diff --git a/package.json b/package.json index 628f568..d1addd1 100644 --- a/package.json +++ b/package.json @@ -27,16 +27,21 @@ "node": ">=20.0.0" }, "dependencies": { + "@apollo/server": "^5.5.1", + "@as-integrations/express4": "^1.1.2", "@endgit/database": "workspace:*", "@endgit/storage": "workspace:*", "@endgit/types": "workspace:*", "bcryptjs": "^2.4.3", + "body-parser": "^2.2.2", "bullmq": "^5.76.2", "compression": "^1.8.1", "cors": "^2.8.5", + "dataloader": "^2.2.3", "dotenv": "^16.4.0", "express": "^4.21.0", "express-rate-limit": "^7.4.0", + "graphql": "^16.14.2", "helmet": "^8.0.0", "ioredis": "^5.10.1", "jsonwebtoken": "^9.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bfe5b6..8c5e41b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@apollo/server': + specifier: ^5.5.1 + version: 5.5.1(graphql@16.14.2) + '@as-integrations/express4': + specifier: ^1.1.2 + version: 1.1.2(@apollo/server@5.5.1(graphql@16.14.2))(express@4.22.1) '@endgit/database': specifier: workspace:* version: link:packages/database @@ -20,6 +26,9 @@ importers: bcryptjs: specifier: ^2.4.3 version: 2.4.3 + body-parser: + specifier: ^2.2.2 + version: 2.2.2 bullmq: specifier: ^5.76.2 version: 5.76.4 @@ -29,6 +38,9 @@ importers: cors: specifier: ^2.8.5 version: 2.8.6 + dataloader: + specifier: ^2.2.3 + version: 2.2.3 dotenv: specifier: ^16.4.0 version: 16.6.1 @@ -38,6 +50,9 @@ importers: express-rate-limit: specifier: ^7.4.0 version: 7.5.1(express@4.22.1) + graphql: + specifier: ^16.14.2 + version: 16.14.2 helmet: specifier: ^8.0.0 version: 8.1.0 @@ -143,6 +158,96 @@ importers: packages: + '@apollo/cache-control-types@1.0.3': + resolution: {integrity: sha512-F17/vCp7QVwom9eG7ToauIKdAxpSoadsJnqIfyryLFSkLSOEqu+eC5Z3N8OXcUVStuOMcNHlyraRsA6rRICu4g==} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/protobufjs@1.2.8': + resolution: {integrity: sha512-r7xNeUqZX+eBBEmyvaPw0/cSz6zgf5jdH8mjUz8ynKpNs/GU7vi2T7sNcZINk2ZID7wwjG91FCgdpCrQuJ8rzA==} + hasBin: true + + '@apollo/server-gateway-interface@2.0.0': + resolution: {integrity: sha512-3HEMD6fSantG2My3jWkb9dvfkF9vJ4BDLRjMgsnD790VINtuPaEp+h3Hg9HOHiWkML6QsOhnaRqZ+gvhp3y8Nw==} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/server@5.5.1': + resolution: {integrity: sha512-Rn3g5TJQsMSUY23CWZTghWdBWyjX7dP1eaEBPkvmM2RHi82cDcpgTIkSCbGvtTUEGjwopLv1AAooU/n7iIZ20A==} + engines: {node: '>=20'} + peerDependencies: + graphql: ^16.11.0 + + '@apollo/usage-reporting-protobuf@4.1.2': + resolution: {integrity: sha512-aTnAD41RYz0d5dawlyR5Iclkgzx0Xb0njUJmEfvZ6pS4f4HU8wCYyctPpWat/HWp2PmRwDfX5R1k4uVcDKZ4xA==} + + '@apollo/utils.createhash@3.0.1': + resolution: {integrity: sha512-CKrlySj4eQYftBE5MJ8IzKwIibQnftDT7yGfsJy5KSEEnLlPASX0UTpbKqkjlVEwPPd4mEwI7WOM7XNxEuO05A==} + engines: {node: '>=16'} + + '@apollo/utils.dropunuseddefinitions@2.0.1': + resolution: {integrity: sha512-EsPIBqsSt2BwDsv8Wu76LK5R1KtsVkNoO4b0M5aK0hx+dGg9xJXuqlr7Fo34Dl+y83jmzn+UvEW+t1/GP2melA==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.fetcher@3.1.0': + resolution: {integrity: sha512-Z3QAyrsQkvrdTuHAFwWDNd+0l50guwoQUoaDQssLOjkmnmVuvXlJykqlEJolio+4rFwBnWdoY1ByFdKaQEcm7A==} + engines: {node: '>=16'} + + '@apollo/utils.isnodelike@3.0.0': + resolution: {integrity: sha512-xrjyjfkzunZ0DeF6xkHaK5IKR8F1FBq6qV+uZ+h9worIF/2YSzA0uoBxGv6tbTeo9QoIQnRW4PVFzGix5E7n/g==} + engines: {node: '>=16'} + + '@apollo/utils.keyvaluecache@4.0.0': + resolution: {integrity: sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw==} + engines: {node: '>=20'} + + '@apollo/utils.logger@3.0.0': + resolution: {integrity: sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg==} + engines: {node: '>=16'} + + '@apollo/utils.printwithreducedwhitespace@2.0.1': + resolution: {integrity: sha512-9M4LUXV/fQBh8vZWlLvb/HyyhjJ77/I5ZKu+NBWV/BmYGyRmoEP9EVAy7LCVoY3t8BDcyCAGfxJaLFCSuQkPUg==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.removealiases@2.0.1': + resolution: {integrity: sha512-0joRc2HBO4u594Op1nev+mUF6yRnxoUH64xw8x3bX7n8QBDYdeYgY4tF0vJReTy+zdn2xv6fMsquATSgC722FA==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.sortast@2.0.1': + resolution: {integrity: sha512-eciIavsWpJ09za1pn37wpsCGrQNXUhM0TktnZmHwO+Zy9O4fu/WdB4+5BvVhFiZYOXvfjzJUcc+hsIV8RUOtMw==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.stripsensitiveliterals@2.0.1': + resolution: {integrity: sha512-QJs7HtzXS/JIPMKWimFnUMK7VjkGQTzqD9bKD1h3iuPAqLsxd0mUNVbkYOPTsDhUKgcvUOfOqOJWYohAKMvcSA==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.usagereporting@2.1.0': + resolution: {integrity: sha512-LPSlBrn+S17oBy5eWkrRSGb98sWmnEzo3DPTZgp8IQc8sJe0prDgDuppGq4NeQlpoqEHz0hQeYHAOA0Z3aQsxQ==} + engines: {node: '>=14'} + peerDependencies: + graphql: 14.x || 15.x || 16.x + + '@apollo/utils.withrequired@3.0.0': + resolution: {integrity: sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA==} + engines: {node: '>=16'} + + '@as-integrations/express4@1.1.2': + resolution: {integrity: sha512-PGeMcwoOKdYnZ4LtsmM7aLNoel3tbK8wKnfyahdRau1qb7wLbuaXB35zg3w34Ov4bm3WJtO3yzd8Bw5jVE+aIQ==} + engines: {node: '>=20'} + peerDependencies: + '@apollo/server': ^4.0.0 || ^5.0.0 + express: ^4.0.0 + '@aws-crypto/crc32@5.2.0': resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} engines: {node: '>=16.0.0'} @@ -496,6 +601,29 @@ packages: cpu: [x64] os: [win32] + '@graphql-tools/merge@9.1.9': + resolution: {integrity: sha512-iHUWNjRHeQRYdgIMIuChThOwoKzA9vrzYeslgfBo5eUYEyHGZCoDPjAavssoYXLwstYt1dZj2J22jSzc2DrN0Q==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/schema@10.0.33': + resolution: {integrity: sha512-O6P3RIftO0jafnSsFAqpjurUuUxJ43s/AdPVLQsBkI6y4Ic/tKm4C1Qm1KKQsCDTOxXPJClh/v3g7k7yLKCFBQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-tools/utils@11.1.0': + resolution: {integrity: sha512-PtFVG4r8Z2LEBSaPYQMusBiB3o6kjLVJyjCLbnWem/SpSuM21v6LTmgpkXfYU1qpBV2UGsFyuEnSJInl8fR1Ag==} + engines: {node: '>=16.0.0'} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@graphql-typed-document-node/core@3.2.0': + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + '@ioredis/commands@1.5.1': resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} @@ -575,6 +703,36 @@ packages: '@prisma/get-platform@5.22.0': resolution: {integrity: sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==} + '@protobufjs/aspromise@1.1.2': + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + + '@protobufjs/base64@1.1.2': + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + + '@protobufjs/codegen@2.0.5': + resolution: {integrity: sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==} + + '@protobufjs/eventemitter@1.1.1': + resolution: {integrity: sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==} + + '@protobufjs/fetch@1.1.1': + resolution: {integrity: sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==} + + '@protobufjs/float@1.0.2': + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + + '@protobufjs/inquire@1.1.2': + resolution: {integrity: sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==} + + '@protobufjs/path@1.1.2': + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + + '@protobufjs/pool@1.1.0': + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + + '@protobufjs/utf8@1.1.1': + resolution: {integrity: sha512-oOAWABowe8EAbMyWKM0tYDKi8Yaox52D+HWZhAIJqQXbqe0xI/GV7FhLWqlEKreMkfDjshR5FKgi3mnle0h6Eg==} + '@rolldown/binding-android-arm64@1.0.2': resolution: {integrity: sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -927,6 +1085,9 @@ packages: '@types/jsonwebtoken@9.0.10': resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/long@4.0.2': + resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + '@types/morgan@1.9.10': resolution: {integrity: sha512-sS4A1zheMvsADRVfT0lYbJ4S9lmsey8Zo2F7cnbYjWHP67Q0AwMYuuzLlkIM2N8gAbb9cubhIVFwcIN2XyYCkA==} @@ -995,6 +1156,10 @@ packages: '@vitest/utils@4.1.7': resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} + '@whatwg-node/promise-helpers@1.3.2': + resolution: {integrity: sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==} + engines: {node: '>=16.0.0'} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -1012,6 +1177,13 @@ packages: ast-v8-to-istanbul@1.0.2: resolution: {integrity: sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==} + async-retry@1.3.3: + resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + basic-auth@2.0.1: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} @@ -1023,6 +1195,10 @@ packages: resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} + engines: {node: '>=18'} + bowser@2.14.1: resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==} @@ -1048,6 +1224,10 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} @@ -1080,6 +1260,10 @@ packages: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1098,6 +1282,13 @@ packages: resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==} engines: {node: '>=12.0.0'} + cross-inspect@1.0.1: + resolution: {integrity: sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==} + engines: {node: '>=16.0.0'} + + dataloader@2.2.3: + resolution: {integrity: sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -1115,6 +1306,10 @@ packages: supports-color: optional: true + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + denque@2.1.0: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} @@ -1213,6 +1408,14 @@ packages: resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} engines: {node: '>= 0.8'} + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1244,14 +1447,25 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + graphql@16.14.2: + resolution: {integrity: sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + hasown@2.0.3: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} @@ -1271,6 +1485,10 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -1282,6 +1500,17 @@ packages: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -1408,6 +1637,20 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + engines: {node: '>= 0.6.0'} + + long@4.0.0: + resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + luxon@3.7.2: resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==} engines: {node: '>=12'} @@ -1430,6 +1673,10 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} + media-typer@1.1.0: + resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} + engines: {node: '>= 0.8'} + merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} @@ -1441,10 +1688,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -1484,6 +1739,10 @@ packages: resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} engines: {node: '>= 0.6'} + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} @@ -1539,6 +1798,10 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss@8.5.15: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} @@ -1573,6 +1836,10 @@ packages: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -1588,6 +1855,10 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + rolldown@1.0.2: resolution: {integrity: sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1615,9 +1886,18 @@ packages: resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} engines: {node: '>= 0.8.0'} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} + hasBin: true + side-channel-list@1.0.1: resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} @@ -1683,6 +1963,10 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -1699,6 +1983,14 @@ packages: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} @@ -1809,6 +2101,14 @@ packages: jsdom: optional: true + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + which-typed-array@1.1.22: + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} + engines: {node: '>= 0.4'} + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -1816,6 +2116,117 @@ packages: snapshots: + '@apollo/cache-control-types@1.0.3(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@apollo/protobufjs@1.2.8': + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.5 + '@protobufjs/eventemitter': 1.1.1 + '@protobufjs/fetch': 1.1.1 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.2 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.1 + '@types/long': 4.0.2 + long: 4.0.0 + + '@apollo/server-gateway-interface@2.0.0(graphql@16.14.2)': + dependencies: + '@apollo/usage-reporting-protobuf': 4.1.2 + '@apollo/utils.fetcher': 3.1.0 + '@apollo/utils.keyvaluecache': 4.0.0 + '@apollo/utils.logger': 3.0.0 + graphql: 16.14.2 + + '@apollo/server@5.5.1(graphql@16.14.2)': + dependencies: + '@apollo/cache-control-types': 1.0.3(graphql@16.14.2) + '@apollo/server-gateway-interface': 2.0.0(graphql@16.14.2) + '@apollo/usage-reporting-protobuf': 4.1.2 + '@apollo/utils.createhash': 3.0.1 + '@apollo/utils.fetcher': 3.1.0 + '@apollo/utils.isnodelike': 3.0.0 + '@apollo/utils.keyvaluecache': 4.0.0 + '@apollo/utils.logger': 3.0.0 + '@apollo/utils.usagereporting': 2.1.0(graphql@16.14.2) + '@apollo/utils.withrequired': 3.0.0 + '@graphql-tools/schema': 10.0.33(graphql@16.14.2) + async-retry: 1.3.3 + body-parser: 2.2.2 + content-type: 1.0.5 + cors: 2.8.6 + finalhandler: 2.1.1 + graphql: 16.14.2 + loglevel: 1.9.2 + lru-cache: 11.5.1 + negotiator: 1.0.0 + whatwg-mimetype: 4.0.0 + transitivePeerDependencies: + - supports-color + + '@apollo/usage-reporting-protobuf@4.1.2': + dependencies: + '@apollo/protobufjs': 1.2.8 + + '@apollo/utils.createhash@3.0.1': + dependencies: + '@apollo/utils.isnodelike': 3.0.0 + sha.js: 2.4.12 + + '@apollo/utils.dropunuseddefinitions@2.0.1(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@apollo/utils.fetcher@3.1.0': {} + + '@apollo/utils.isnodelike@3.0.0': {} + + '@apollo/utils.keyvaluecache@4.0.0': + dependencies: + '@apollo/utils.logger': 3.0.0 + lru-cache: 11.5.1 + + '@apollo/utils.logger@3.0.0': {} + + '@apollo/utils.printwithreducedwhitespace@2.0.1(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@apollo/utils.removealiases@2.0.1(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@apollo/utils.sortast@2.0.1(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + lodash.sortby: 4.7.0 + + '@apollo/utils.stripsensitiveliterals@2.0.1(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + + '@apollo/utils.usagereporting@2.1.0(graphql@16.14.2)': + dependencies: + '@apollo/usage-reporting-protobuf': 4.1.2 + '@apollo/utils.dropunuseddefinitions': 2.0.1(graphql@16.14.2) + '@apollo/utils.printwithreducedwhitespace': 2.0.1(graphql@16.14.2) + '@apollo/utils.removealiases': 2.0.1(graphql@16.14.2) + '@apollo/utils.sortast': 2.0.1(graphql@16.14.2) + '@apollo/utils.stripsensitiveliterals': 2.0.1(graphql@16.14.2) + graphql: 16.14.2 + + '@apollo/utils.withrequired@3.0.0': {} + + '@as-integrations/express4@1.1.2(@apollo/server@5.5.1(graphql@16.14.2))(express@4.22.1)': + dependencies: + '@apollo/server': 5.5.1(graphql@16.14.2) + express: 4.22.1 + '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 @@ -2390,6 +2801,31 @@ snapshots: '@esbuild/win32-x64@0.27.7': optional: true + '@graphql-tools/merge@9.1.9(graphql@16.14.2)': + dependencies: + '@graphql-tools/utils': 11.1.0(graphql@16.14.2) + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/schema@10.0.33(graphql@16.14.2)': + dependencies: + '@graphql-tools/merge': 9.1.9(graphql@16.14.2) + '@graphql-tools/utils': 11.1.0(graphql@16.14.2) + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-tools/utils@11.1.0(graphql@16.14.2)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.14.2) + '@whatwg-node/promise-helpers': 1.3.2 + cross-inspect: 1.0.1 + graphql: 16.14.2 + tslib: 2.8.1 + + '@graphql-typed-document-node/core@3.2.0(graphql@16.14.2)': + dependencies: + graphql: 16.14.2 + '@ioredis/commands@1.5.1': {} '@jridgewell/resolve-uri@3.1.2': {} @@ -2455,6 +2891,28 @@ snapshots: dependencies: '@prisma/debug': 5.22.0 + '@protobufjs/aspromise@1.1.2': {} + + '@protobufjs/base64@1.1.2': {} + + '@protobufjs/codegen@2.0.5': {} + + '@protobufjs/eventemitter@1.1.1': {} + + '@protobufjs/fetch@1.1.1': + dependencies: + '@protobufjs/aspromise': 1.1.2 + + '@protobufjs/float@1.0.2': {} + + '@protobufjs/inquire@1.1.2': {} + + '@protobufjs/path@1.1.2': {} + + '@protobufjs/pool@1.1.0': {} + + '@protobufjs/utf8@1.1.1': {} + '@rolldown/binding-android-arm64@1.0.2': optional: true @@ -2895,6 +3353,8 @@ snapshots: '@types/ms': 2.1.0 '@types/node': 25.6.0 + '@types/long@4.0.2': {} + '@types/morgan@1.9.10': dependencies: '@types/node': 25.6.0 @@ -2983,6 +3443,10 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@whatwg-node/promise-helpers@1.3.2': + dependencies: + tslib: 2.8.1 + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -3000,6 +3464,14 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 + async-retry@1.3.3: + dependencies: + retry: 0.13.1 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + basic-auth@2.0.1: dependencies: safe-buffer: 5.1.2 @@ -3023,6 +3495,20 @@ snapshots: transitivePeerDependencies: - supports-color + body-parser@2.2.2: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + on-finished: 2.4.1 + qs: 6.15.1 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + bowser@2.14.1: {} buffer-equal-constant-time@1.0.1: {} @@ -3051,6 +3537,13 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3089,6 +3582,8 @@ snapshots: content-type@1.0.5: {} + content-type@2.0.0: {} + convert-source-map@2.0.0: {} cookie-signature@1.0.7: {} @@ -3104,6 +3599,12 @@ snapshots: dependencies: luxon: 3.7.2 + cross-inspect@1.0.1: + dependencies: + tslib: 2.8.1 + + dataloader@2.2.3: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -3112,6 +3613,12 @@ snapshots: dependencies: ms: 2.1.3 + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + denque@2.1.0: {} depd@2.0.0: {} @@ -3252,6 +3759,21 @@ snapshots: transitivePeerDependencies: - supports-color + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + forwarded@0.2.0: {} fresh@0.5.2: {} @@ -3285,10 +3807,20 @@ snapshots: gopd@1.2.0: {} + graphql@16.14.2: {} + has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + has-symbols@1.1.0: {} + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -3309,6 +3841,10 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + inherits@2.0.4: {} ioredis@5.10.1: @@ -3327,6 +3863,14 @@ snapshots: ipaddr.js@1.9.1: {} + is-callable@1.2.7: {} + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.22 + + isarray@2.0.5: {} + istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -3433,6 +3977,14 @@ snapshots: lodash.once@4.1.1: {} + lodash.sortby@4.7.0: {} + + loglevel@1.9.2: {} + + long@4.0.0: {} + + lru-cache@11.5.1: {} + luxon@3.7.2: {} magic-string@0.30.21: @@ -3453,16 +4005,24 @@ snapshots: media-typer@0.3.0: {} + media-typer@1.1.0: {} + merge-descriptors@1.0.3: {} methods@1.1.2: {} mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} morgan@1.10.1: @@ -3508,6 +4068,8 @@ snapshots: negotiator@0.6.4: {} + negotiator@1.0.0: {} + node-abort-controller@3.1.1: {} node-gyp-build-optional-packages@5.2.2: @@ -3545,6 +4107,8 @@ snapshots: picomatch@4.0.4: {} + possible-typed-array-names@1.1.0: {} + postcss@8.5.15: dependencies: nanoid: 3.3.12 @@ -3581,6 +4145,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.2 + unpipe: 1.0.0 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -3595,6 +4166,8 @@ snapshots: resolve-pkg-maps@1.0.0: {} + retry@0.13.1: {} + rolldown@1.0.2: dependencies: '@oxc-project/types': 0.132.0 @@ -3651,8 +4224,23 @@ snapshots: transitivePeerDependencies: - supports-color + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + setprototypeof@1.2.0: {} + sha.js@2.4.12: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 @@ -3716,6 +4304,12 @@ snapshots: tinyrainbow@3.1.0: {} + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + toidentifier@1.0.1: {} tslib@2.8.1: {} @@ -3732,6 +4326,18 @@ snapshots: media-typer: 0.3.0 mime-types: 2.1.35 + type-is@2.1.0: + dependencies: + content-type: 2.0.0 + media-typer: 1.1.0 + mime-types: 3.0.2 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + typedarray@0.0.6: {} typescript@5.9.3: {} @@ -3787,6 +4393,18 @@ snapshots: transitivePeerDependencies: - msw + whatwg-mimetype@4.0.0: {} + + which-typed-array@1.1.22: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 diff --git a/skills-lock.json b/skills-lock.json index fe09847..02290ab 100644 --- a/skills-lock.json +++ b/skills-lock.json @@ -13,6 +13,12 @@ "skillPath": "skills/find-skills/SKILL.md", "computedHash": "9e1c8b3103f92fa8092568a44fe64858de7c5c9dc65ce4bea8f168080e889cfd" }, + "graphql-schema": { + "source": "apollographql/skills", + "sourceType": "github", + "skillPath": "skills/graphql-schema/SKILL.md", + "computedHash": "95f5384cc907635fc31121648dffffed0bb6f773ee594cbfe9be9e5e4a0e6aba" + }, "prisma-client-api": { "source": "prisma/skills", "sourceType": "github", diff --git a/src/graphql/index.ts b/src/graphql/index.ts new file mode 100644 index 0000000..fa97f26 --- /dev/null +++ b/src/graphql/index.ts @@ -0,0 +1,8 @@ +import { ApolloServer } from "@apollo/server"; +import { typeDefs } from "./typeDefs"; +import { resolvers } from "./resolvers"; + +export const apolloServer = new ApolloServer({ + typeDefs, + resolvers, +}); diff --git a/src/graphql/resolvers.ts b/src/graphql/resolvers.ts new file mode 100644 index 0000000..7b5c7e1 --- /dev/null +++ b/src/graphql/resolvers.ts @@ -0,0 +1,83 @@ +import { prisma } from "@endgit/database"; +import { dashboardService } from "../modules/dashboard/dashboard.service"; +import { pluginsService } from "../modules/plugins/plugins.service"; + +export const resolvers = { + Query: { + plugins: async (_: any, args: { limit: number; offset: number; status?: string }) => { + return prisma.plugin.findMany({ + where: args.status ? { status: args.status as any } : undefined, + take: args.limit, + skip: args.offset, + orderBy: { heatScore: "desc" }, + }); + }, + plugin: async (_: any, args: { slug: string }, context: any) => { + return pluginsService.getBySlug(args.slug, context.user); + }, + me: async (_: any, __: any, context: any) => { + if (!context.user) return null; + return prisma.user.findUnique({ where: { id: context.user.id } }); + }, + homePlugins: async () => { + return pluginsService.getHome(); + }, + myPlugins: async (_: any, __: any, context: any) => { + if (!context.user) throw new Error("Unauthorized"); + return dashboardService.getMyPlugins(context.user.id); + }, + myStats: async (_: any, __: any, context: any) => { + if (!context.user) throw new Error("Unauthorized"); + return dashboardService.getMyStats(context.user.id); + }, + dashboardStatus: async (_: any, __: any, context: any) => { + if (!context.user) throw new Error("Unauthorized"); + return dashboardService.getStatus(context.user.id); + } + }, + Mutation: { + createPlugin: async (_: any, { input }: any, context: any) => { + if (!context.user) throw new Error("Unauthorized"); + return pluginsService.createPlugin(input, context.user.id); + }, + updatePlugin: async (_: any, { slug, input }: any, context: any) => { + if (!context.user) throw new Error("Unauthorized"); + return pluginsService.updatePlugin(slug, input, context.user); + }, + deletePlugin: async (_: any, { slug }: any, context: any) => { + if (!context.user) throw new Error("Unauthorized"); + await pluginsService.deletePlugin(slug, context.user); + return true; + }, + triggerBuild: async (_: any, { slug, commitHash, branch }: any, context: any) => { + if (!context.user) throw new Error("Unauthorized"); + return pluginsService.triggerBuild(slug, { commitHash, branch }, context.user); + } + }, + Plugin: { + author: async (parent: any, _: any, context: any) => { + if (parent.author) return parent.author; + return context.loaders.userLoader.load(parent.authorId); + }, + versions: async (parent: any, args: { status?: string }, context: any) => { + let versions; + if (parent.versions) { + versions = parent.versions; + } else { + versions = await context.loaders.versionLoader.load(parent.id); + } + if (args.status) { + return versions.filter((v: any) => v.status === args.status); + } + return versions; + }, + stars: (parent: any) => parent.stars || 0, + downloads: (parent: any) => parent.downloads || 0, + commentCount: (parent: any) => parent.commentCount || 0, + heatScore: (parent: any) => parent.heatScore || 0, + status: (parent: any) => parent.status || "UNKNOWN", + isVerified: (parent: any) => parent.isVerified || false, + isFeatured: (parent: any) => parent.isFeatured || false, + pluginType: (parent: any) => parent.pluginType || "UNKNOWN", + }, +}; diff --git a/src/graphql/typeDefs.ts b/src/graphql/typeDefs.ts new file mode 100644 index 0000000..bd6a4d2 --- /dev/null +++ b/src/graphql/typeDefs.ts @@ -0,0 +1,144 @@ +export const typeDefs = `#graphql + type User { + id: ID! + githubId: String! + username: String! + displayName: String + email: String + avatarUrl: String + bio: String + trustLevel: String! + trustScore: Int! + createdAt: String! + updatedAt: String! + } + + type Producer { + githubUser: String! + role: String! + } + + type VirusTotal { + scanId: String + status: String + malicious: Int + suspicious: Int + undetected: Int + total: Int + permalink: String + scanDate: String + } + + type Version { + id: ID! + version: String! + changelog: String + longDescription: String + fileName: String! + fileSize: Int! + fileHash: String! + minApiVersion: String + supportedApis: [String!]! + downloads: Int! + isLatest: Boolean! + isPreRelease: Boolean! + status: String! + statusReason: String + createdAt: String! + producers: [Producer!] + virustotal: VirusTotal + } + + type Plugin { + id: ID! + name: String! + slug: String! + displayName: String! + description: String! + longDescription: String + iconUrl: String + repoUrl: String + license: String + tags: [String!]! + keywords: [String!]! + pluginType: String! + downloads: Int! + stars: Int! + commentCount: Int! + heatScore: Int! + status: String! + qualityBadge: String! + isVerified: Boolean! + isFeatured: Boolean! + createdAt: String! + updatedAt: String! + author: User! + versions(status: String): [Version!]! + latestVersion: String + isPreRelease: Boolean + } + + type HomePlugins { + hotPlugins: [Plugin!]! + newPlugins: [Plugin!]! + topPlugins: [Plugin!]! + featuredPlugins: [Plugin!]! + } + + type DashboardStats { + totalPlugins: Int! + totalDownloads: Int! + totalVersions: Int! + pendingReviews: Int! + } + + type Quota { + used: Int! + limit: Int! + resetsAt: String! + } + + type DashboardStatus { + hasAppInstalled: Boolean! + githubTokenExpired: Boolean! + quota: Quota! + } + + type Query { + plugins(limit: Int = 10, offset: Int = 0, status: String): [Plugin!]! + plugin(slug: String!): Plugin + me: User + homePlugins: HomePlugins! + myPlugins: [Plugin!]! + myStats: DashboardStats! + dashboardStatus: DashboardStatus! + } + + input CreatePluginInput { + name: String! + displayName: String! + description: String! + longDescription: String + pluginType: String + repoUrl: String + license: String + tags: [String!] + } + + input UpdatePluginInput { + displayName: String + description: String + longDescription: String + iconUrl: String + license: String + tags: [String!] + isPreRelease: Boolean + } + + type Mutation { + createPlugin(input: CreatePluginInput!): Plugin! + updatePlugin(slug: String!, input: UpdatePluginInput!): Plugin! + deletePlugin(slug: String!): Boolean! + triggerBuild(slug: String!, commitHash: String, branch: String): String! + } +`; diff --git a/src/index.ts b/src/index.ts index 2b1376c..bdc08b6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,11 @@ import helmet from "helmet"; import morgan from "morgan"; import zlib from "zlib"; import { publicRateLimit } from "./middleware/rateLimit"; +import { apolloServer } from "./graphql"; +import { optionalAuth, AuthRequest } from "./middleware/auth"; +import DataLoader from "dataloader"; +import { prisma } from "@endgit/database"; +import { expressMiddleware } from "@as-integrations/express4"; import { pluginsRouter } from "./modules/plugins/plugins.routes"; import { versionsRouter } from "./modules/versions/versions.routes"; import { downloadRouter } from "./modules/download/download.routes"; @@ -66,7 +71,9 @@ app.use( crossOriginEmbedderPolicy: false, }), ); -app.use(publicRateLimit); +if (process.env.NODE_ENV !== "development" && process.env.NODE_ENV !== "test") { + app.use(publicRateLimit); +} app.use( cors({ origin: [ @@ -118,48 +125,104 @@ app.use("/api/v1/submit", submitRouter); app.use("/api/v1/webhooks", webhookRouter); app.use("/api/v1/builds", callbackRouter); // GitHub Actions artifact callbacks -// ── Error Handler ──────────────────────────────────────── -app.use( - ( - err: any, - _req: express.Request, - res: express.Response, - _next: express.NextFunction, - ) => { - console.error("Error:", err.message); - res.status(err.status || 500).json({ - success: false, - error: err.message || "Internal Server Error", - }); - }, -); -// ── 404 Handler ────────────────────────────────────────── +// ── Start ──────────────────────────────────────────────── + +import { recalculateAllHeatScores } from "./modules/comments/comments.service"; -app.use((_req, res) => { - res.status(404).json({ - success: false, - error: "Not Found", +const createUserLoader = () => new DataLoader(async (userIds: readonly string[]) => { + const users = await prisma.user.findMany({ where: { id: { in: userIds as string[] } } }); + const userMap = new Map(users.map(u => [u.id, u])); + return userIds.map(id => userMap.get(id) || null); +}); + +const createVersionLoader = () => new DataLoader(async (pluginIds: readonly string[]) => { + const versions = await prisma.version.findMany({ + where: { pluginId: { in: pluginIds as string[] } }, + orderBy: { createdAt: "desc" } + }); + const map = new Map(); + versions.forEach(v => { + if (!map.has(v.pluginId)) map.set(v.pluginId, []); + map.get(v.pluginId)!.push({ + ...v, + virustotal: { + scanId: v.vtScanId, + status: v.vtStatus, + malicious: v.vtMalicious, + suspicious: v.vtSuspicious, + undetected: v.vtUndetected, + total: v.vtTotal, + permalink: v.vtPermalink, + scanDate: v.vtScanDate, + } + }); }); + return pluginIds.map(id => map.get(id) || []); }); -// ── Start ──────────────────────────────────────────────── +async function startServer() { + await apolloServer.start(); + app.use( + "/api/graphql", + optionalAuth, + expressMiddleware(apolloServer, { + context: async ({ req }: { req: any }) => { + return { + user: (req as AuthRequest).user, + loaders: { + userLoader: createUserLoader(), + versionLoader: createVersionLoader() + } + }; + }, + }) + ); + + // ── Error Handler ──────────────────────────────────────── + app.use( + ( + err: any, + _req: express.Request, + res: express.Response, + _next: express.NextFunction, + ) => { + console.error("Error:", err.message); + res.status(err.status || 500).json({ + success: false, + error: err.message || "Internal Server Error", + }); + }, + ); -import { recalculateAllHeatScores } from "./modules/comments/comments.service"; + // ── 404 Handler ────────────────────────────────────────── + app.use((_req, res) => { + res.status(404).json({ + success: false, + error: "Not Found", + }); + }); + + app.listen(PORT, () => { + console.log(` + ╔═══════════════════════════════════════════════════╗ + ║ ║ + ║ EndGit API Server ║ + ║ Running on http://localhost:${PORT} ║ + ║ GraphQL on http://localhost:${PORT}/api/graphql ║ + ║ ║ + ╚═══════════════════════════════════════════════════╝ + `); + + recalculateAllHeatScores().catch(() => {}); + setInterval(() => recalculateAllHeatScores().catch(() => {}), 60 * 60 * 1000); + }); +} -app.listen(PORT, () => { - console.log(` - ╔═══════════════════════════════════════════════════╗ - ║ ║ - ║ EndGit API Server ║ - ║ Running on http://localhost:${PORT} ║ - ║ ║ - ╚═══════════════════════════════════════════════════╝ - `); - - recalculateAllHeatScores().catch(() => {}); - setInterval(() => recalculateAllHeatScores().catch(() => {}), 60 * 60 * 1000); +startServer().catch((err) => { + console.error("Failed to start server:", err); + process.exit(1); }); export default app; diff --git a/src/modules/github/github.controller.ts b/src/modules/github/github.controller.ts index 59b6a1a..4091cff 100644 --- a/src/modules/github/github.controller.ts +++ b/src/modules/github/github.controller.ts @@ -25,8 +25,9 @@ export class GithubController { const perPage = parseInt(req.query.per_page as string) || 30; const org = req.query.org as string | undefined; const search = (req.query.search as string) || undefined; + const filter = (req.query.filter as string) || undefined; - const cacheKey = `gh:repos:${req.user!.id}:${page}:${perPage}:${org || ""}:${search || ""}`; + const cacheKey = `gh:repos:${req.user!.id}:${page}:${perPage}:${org || ""}:${search || ""}:${filter || ""}`; const cached = await cacheGet(cacheKey); if (cached) { res.set("Cache-Control", "private, max-age=30"); @@ -40,6 +41,7 @@ export class GithubController { perPage, org, search, + filter ); const payload = { diff --git a/src/modules/github/github.service.ts b/src/modules/github/github.service.ts index 7676ffc..2b7d472 100644 --- a/src/modules/github/github.service.ts +++ b/src/modules/github/github.service.ts @@ -144,6 +144,7 @@ export class GithubService { perPage: number, org?: string, search?: string, + filter?: string, ) { const accessToken = await this.getAccessToken(userId); if (!accessToken) throw new Error("GitHub account not linked"); @@ -154,72 +155,65 @@ export class GithubService { "User-Agent": "EndGit-CI", }; - let ghRepos: any[]; - let hasMore = false; - let totalCount = 0; + const cacheKey = `gh:all_repos:${userId}:${org || "all"}`; + let ghRepos = await cacheGet(cacheKey); - if (search) { + if (!ghRepos) { ghRepos = []; - let p = 1; - while (true) { - const url = org - ? `https://api.github.com/orgs/${encodeURIComponent(org)}/repos?sort=updated&per_page=100&page=${p}` - : `https://api.github.com/user/repos?sort=updated&per_page=100&page=${p}&affiliation=owner,collaborator,organization_member`; - const res = await fetch(url, { headers }); - if (!res.ok) throw new Error("Failed to fetch from GitHub"); - const batch = (await res.json()) as any[]; - ghRepos.push(...batch); - const linkHeader = res.headers.get("link"); - if (!linkHeader || !linkHeader.includes('rel="next"')) break; - p++; - } - - const searchLower = search.toLowerCase(); - ghRepos = ghRepos.filter((r: any) => - r.full_name.toLowerCase().includes(searchLower), - ); - - totalCount = ghRepos.length; - const start = (page - 1) * perPage; - ghRepos = ghRepos.slice(start, start + perPage); - hasMore = start + perPage < totalCount; - } else { - let ghRes: Response; - - if (org) { - ghRes = await fetch( - `https://api.github.com/orgs/${encodeURIComponent(org)}/repos?sort=updated&per_page=${perPage}&page=${page}`, - { headers }, - ); - } else { - ghRes = await fetch( - `https://api.github.com/user/repos?sort=updated&per_page=${perPage}&page=${page}&affiliation=owner,collaborator,organization_member`, - { headers }, - ); - } - - if (!ghRes.ok) throw new Error("Failed to fetch from GitHub"); - - const linkHeader = ghRes.headers.get("link"); - if (linkHeader && linkHeader.includes('rel="next"')) hasMore = true; - - ghRepos = (await ghRes.json()) as any[]; - - if (linkHeader) { - const lastMatch = linkHeader.match( - /[?&]page=(\d+)[^>]*>;\s*rel="last"/, - ); + const firstUrl = org + ? `https://api.github.com/orgs/${encodeURIComponent(org)}/repos?sort=updated&per_page=100&page=1` + : `https://api.github.com/user/repos?sort=updated&per_page=100&page=1&affiliation=owner,collaborator,organization_member`; + + const res = await fetch(firstUrl, { headers }); + if (!res.ok) throw new Error("Failed to fetch from GitHub"); + const firstBatch = (await res.json()) as any[]; + ghRepos.push(...firstBatch); + + const linkHeader = res.headers.get("link"); + if (linkHeader && linkHeader.includes('rel="last"')) { + const lastMatch = linkHeader.match(/[?&]page=(\d+)[^>]*>;\s*rel="last"/); if (lastMatch) { const lastPage = parseInt(lastMatch[1], 10); - totalCount = lastPage * perPage; + const urls = []; + for (let p = 2; p <= lastPage; p++) { + urls.push( + org + ? `https://api.github.com/orgs/${encodeURIComponent(org)}/repos?sort=updated&per_page=100&page=${p}` + : `https://api.github.com/user/repos?sort=updated&per_page=100&page=${p}&affiliation=owner,collaborator,organization_member`, + ); + } + + const chunkSize = 5; + for (let i = 0; i < urls.length; i += chunkSize) { + const chunk = urls.slice(i, i + chunkSize); + const responses = await Promise.all( + chunk.map((u) => fetch(u, { headers })), + ); + for (const r of responses) { + if (r.ok) { + const batch = (await r.json()) as any[]; + ghRepos.push(...batch); + } + } + } + } + } else if (linkHeader && linkHeader.includes('rel="next"')) { + let p = 2; + while (true) { + const url = org + ? `https://api.github.com/orgs/${encodeURIComponent(org)}/repos?sort=updated&per_page=100&page=${p}` + : `https://api.github.com/user/repos?sort=updated&per_page=100&page=${p}&affiliation=owner,collaborator,organization_member`; + const r = await fetch(url, { headers }); + if (!r.ok) break; + const batch = (await r.json()) as any[]; + ghRepos.push(...batch); + const link = r.headers.get("link"); + if (!link || !link.includes('rel="next"')) break; + p++; } } - if (!hasMore) { - totalCount = (page - 1) * perPage + ghRepos.length; - } else if (totalCount === 0) { - totalCount = ghRepos.length; - } + await cacheSet(cacheKey, ghRepos, 300); } const existingPlugins = await prisma.plugin.findMany({ @@ -238,10 +232,8 @@ export class GithubService { existingPlugins.map((p: any) => [p.repoUrl, p] as const), ); - const repos = ghRepos.map((repo: any) => { - const linked = repoUrlMap.get(repo.html_url) as - | { webhookId?: string; id?: string; slug?: string; status?: string } - | undefined; + let repos = ghRepos.map((repo: any) => { + const linked = repoUrlMap.get(repo.html_url) as any; return { id: repo.id, name: repo.name, @@ -259,8 +251,25 @@ export class GithubService { }; }); - const totalEnabled = existingPlugins.filter((p: any) => p.webhookId).length; - const totalDisabled = totalCount - totalEnabled; + const totalEnabled = repos.filter((r) => r.ciEnabled).length; + const totalDisabled = repos.length - totalEnabled; + + if (search) { + const searchLower = search.toLowerCase(); + repos = repos.filter((r) => r.fullName.toLowerCase().includes(searchLower)); + } + + if (filter === "enabled") { + repos = repos.filter((r) => r.ciEnabled); + } else if (filter === "disabled") { + repos = repos.filter((r) => !r.ciEnabled); + } + + const totalCount = repos.length; + + const start = (page - 1) * perPage; + repos = repos.slice(start, start + perPage); + const hasMore = start + perPage < totalCount; return { repos, hasMore, totalCount, totalEnabled, totalDisabled }; } diff --git a/src/modules/plugins/plugins.service.ts b/src/modules/plugins/plugins.service.ts index 67d64e9..9611624 100644 --- a/src/modules/plugins/plugins.service.ts +++ b/src/modules/plugins/plugins.service.ts @@ -90,7 +90,7 @@ export class PluginsService { isFeatured: true, createdAt: true, author: { - select: { username: true, displayName: true, avatarUrl: true }, + select: { id: true, username: true, displayName: true, avatarUrl: true }, }, versions: { where: { status: "APPROVED" }, @@ -175,7 +175,7 @@ export class PluginsService { isFeatured: true, createdAt: true, author: { - select: { username: true, displayName: true, avatarUrl: true }, + select: { id: true, username: true, displayName: true, avatarUrl: true }, }, versions: { where: { status: "APPROVED" }, @@ -224,7 +224,7 @@ export class PluginsService { isFeatured: true, createdAt: true, author: { - select: { username: true, displayName: true, avatarUrl: true }, + select: { id: true, username: true, displayName: true, avatarUrl: true }, }, versions: { where: { status: "APPROVED" }, @@ -253,6 +253,13 @@ export class PluginsService { } async getHome() { + try { + const cached = await connection.get("cache:home"); + if (cached) return JSON.parse(cached); + } catch (e) { + console.error("Redis cache error:", e); + } + const cardSelect = { id: true, slug: true, @@ -268,7 +275,7 @@ export class PluginsService { isFeatured: true, createdAt: true, author: { - select: { username: true, displayName: true, avatarUrl: true }, + select: { id: true, username: true, displayName: true, avatarUrl: true }, }, versions: { where: { status: "APPROVED" as const }, @@ -315,12 +322,20 @@ export class PluginsService { versions: undefined, }); - return { + const result = { hotPlugins: hot.map(mapPlugin), newPlugins: newest.map(mapPlugin), topPlugins: top.map(mapPlugin), featuredPlugins: featured.map(mapPlugin), }; + + try { + await connection.set("cache:home", JSON.stringify(result), "EX", 60); + } catch (e) { + console.error("Redis cache set error:", e); + } + + return result; } async getGlobalStats() { @@ -365,6 +380,7 @@ export class PluginsService { status: true, author: { select: { + id: true, username: true, displayName: true, avatarUrl: true, @@ -554,7 +570,7 @@ export class PluginsService { }, include: { author: { - select: { username: true, displayName: true, avatarUrl: true }, + select: { id: true, username: true, displayName: true, avatarUrl: true }, }, }, }); @@ -603,7 +619,7 @@ export class PluginsService { }, include: { author: { - select: { username: true, displayName: true, avatarUrl: true }, + select: { id: true, username: true, displayName: true, avatarUrl: true }, }, }, });