From 152cd7259354cb1339a0490f769fd40df646a045 Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Wed, 6 Dec 2023 16:30:09 +0100 Subject: [PATCH] [backend] Web API groundwork --- .pnp.cjs | 244 +++++++++++++++-- ...-boom-npm-10.0.1-306fb2f69f-99415b0e2f.zip | 3 + ...-hoek-npm-11.0.4-3a5c13a5d9-0cf1c0dbd2.zip | 3 + ...penapi-npm-2.2.0-b79af14354-a7ff2c0b1a.zip | 3 + ...a-cors-npm-4.0.0-e1649c9262-1509d98b72.zip | 3 + ...outer-npm-12.0.1-6a9764e4df-f9427c8eb2.zip | 3 + ...s-koa-npm-2.13.6-a8726b332e-cba52d2418.zip | 3 - ...elimit-npm-5.0.4-92296aa01b-5233eec465.zip | 3 + ...ator-npm-13.11.7-1eda8f8948-5935d363a9.zip | 3 + ...miter-npm-1.3.12-25c4d3ff00-bf492d85a3.zip | 3 + ...former-npm-0.5.1-96b5161e6c-750327e3e9.zip | 3 + ...dator-npm-0.14.0-b600d5fe72-bf550a4ab7.zip | 3 + ...lebars-npm-4.7.8-25244c2c82-bd528f4dd1.zip | 3 + ...helmet-npm-6.0.1-204284f33b-0eec311e1a.zip | 3 + ...helmet-npm-6.2.0-43622c54ea-f112fcd0d8.zip | 3 + ...helmet-npm-7.0.2-4d457c75ac-a2f8f72f05.zip | 3 + ...elimit-npm-5.1.0-1ab589a18d-09c718b727.zip | 3 + ...er-ui-npm-5.10.0-54bce94261-40575d377d.zip | 3 + ...r-js-npm-1.10.51-4ff79b15f8-925fda2ecb.zip | 3 + ...types-npm-12.1.3-1b8ae4a632-9d1d7ed848.zip | 3 + ...fy-js-npm-3.17.4-58d4ab56aa-4c0b800e0f.zip | 3 + ...rdwrap-npm-1.0.0-ae57a645e8-497d40beb2.zip | 3 + packages/backend/package.json | 6 +- packages/backend/src/server/api/index.ts | 5 + .../src/server/api/web/controllers/user.ts | 29 ++ .../src/server/api/web/entities/note.ts | 4 + .../src/server/api/web/entities/user.ts | 11 + .../src/server/api/web/handlers/user.ts | 56 ++++ packages/backend/src/server/api/web/index.ts | 63 +++++ .../src/server/api/web/middleware/auth.ts | 28 ++ .../api/web/middleware/error-handling.ts | 13 + .../server/api/web/middleware/rate-limit.ts | 32 +++ packages/backend/tsconfig.json | 2 +- yarn.lock | 257 +++++++++++++++--- 34 files changed, 755 insertions(+), 58 deletions(-) create mode 100644 .yarn/cache/@hapi-boom-npm-10.0.1-306fb2f69f-99415b0e2f.zip create mode 100644 .yarn/cache/@hapi-hoek-npm-11.0.4-3a5c13a5d9-0cf1c0dbd2.zip create mode 100644 .yarn/cache/@iceshrimp-koa-openapi-npm-2.2.0-b79af14354-a7ff2c0b1a.zip create mode 100644 .yarn/cache/@koa-cors-npm-4.0.0-e1649c9262-1509d98b72.zip create mode 100644 .yarn/cache/@koa-router-npm-12.0.1-6a9764e4df-f9427c8eb2.zip delete mode 100644 .yarn/cache/@types-koa-npm-2.13.6-a8726b332e-cba52d2418.zip create mode 100644 .yarn/cache/@types-koa-ratelimit-npm-5.0.4-92296aa01b-5233eec465.zip create mode 100644 .yarn/cache/@types-validator-npm-13.11.7-1eda8f8948-5935d363a9.zip create mode 100644 .yarn/cache/async-ratelimiter-npm-1.3.12-25c4d3ff00-bf492d85a3.zip create mode 100644 .yarn/cache/class-transformer-npm-0.5.1-96b5161e6c-750327e3e9.zip create mode 100644 .yarn/cache/class-validator-npm-0.14.0-b600d5fe72-bf550a4ab7.zip create mode 100644 .yarn/cache/handlebars-npm-4.7.8-25244c2c82-bd528f4dd1.zip create mode 100644 .yarn/cache/helmet-npm-6.0.1-204284f33b-0eec311e1a.zip create mode 100644 .yarn/cache/helmet-npm-6.2.0-43622c54ea-f112fcd0d8.zip create mode 100644 .yarn/cache/koa-helmet-npm-7.0.2-4d457c75ac-a2f8f72f05.zip create mode 100644 .yarn/cache/koa-ratelimit-npm-5.1.0-1ab589a18d-09c718b727.zip create mode 100644 .yarn/cache/koa2-swagger-ui-npm-5.10.0-54bce94261-40575d377d.zip create mode 100644 .yarn/cache/libphonenumber-js-npm-1.10.51-4ff79b15f8-925fda2ecb.zip create mode 100644 .yarn/cache/openapi-types-npm-12.1.3-1b8ae4a632-9d1d7ed848.zip create mode 100644 .yarn/cache/uglify-js-npm-3.17.4-58d4ab56aa-4c0b800e0f.zip create mode 100644 .yarn/cache/wordwrap-npm-1.0.0-ae57a645e8-497d40beb2.zip create mode 100644 packages/backend/src/server/api/web/controllers/user.ts create mode 100644 packages/backend/src/server/api/web/entities/note.ts create mode 100644 packages/backend/src/server/api/web/entities/user.ts create mode 100644 packages/backend/src/server/api/web/handlers/user.ts create mode 100644 packages/backend/src/server/api/web/index.ts create mode 100644 packages/backend/src/server/api/web/middleware/auth.ts create mode 100644 packages/backend/src/server/api/web/middleware/error-handling.ts create mode 100644 packages/backend/src/server/api/web/middleware/rate-limit.ts diff --git a/.pnp.cjs b/.pnp.cjs index 1b9edc4b4..cea7b6156 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -1819,7 +1819,24 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@hapi/boom", [\ + ["npm:10.0.1", {\ + "packageLocation": "./.yarn/cache/@hapi-boom-npm-10.0.1-306fb2f69f-99415b0e2f.zip/node_modules/@hapi/boom/",\ + "packageDependencies": [\ + ["@hapi/boom", "npm:10.0.1"],\ + ["@hapi/hoek", "npm:11.0.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@hapi/hoek", [\ + ["npm:11.0.4", {\ + "packageLocation": "./.yarn/cache/@hapi-hoek-npm-11.0.4-3a5c13a5d9-0cf1c0dbd2.zip/node_modules/@hapi/hoek/",\ + "packageDependencies": [\ + ["@hapi/hoek", "npm:11.0.4"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:9.3.0", {\ "packageLocation": "./.yarn/cache/@hapi-hoek-npm-9.3.0-447eb8d274-ad83a22378.zip/node_modules/@hapi/hoek/",\ "packageDependencies": [\ @@ -1868,6 +1885,28 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@iceshrimp/koa-openapi", [\ + ["npm:2.2.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F2.2.0%2Fkoa-openapi-2.2.0.tgz", {\ + "packageLocation": "./.yarn/cache/@iceshrimp-koa-openapi-npm-2.2.0-b79af14354-a7ff2c0b1a.zip/node_modules/@iceshrimp/koa-openapi/",\ + "packageDependencies": [\ + ["@iceshrimp/koa-openapi", "npm:2.2.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F2.2.0%2Fkoa-openapi-2.2.0.tgz"],\ + ["@hapi/boom", "npm:10.0.1"],\ + ["@koa/cors", "npm:4.0.0"],\ + ["@koa/router", "npm:12.0.1"],\ + ["class-transformer", "npm:0.5.1"],\ + ["class-validator", "npm:0.14.0"],\ + ["helmet", "npm:6.0.1"],\ + ["koa", "npm:2.14.2"],\ + ["koa-body", "npm:6.0.1"],\ + ["koa-helmet", "npm:7.0.2"],\ + ["koa2-swagger-ui", "virtual:b79af143542f14e992898e20a1ab22405dbd6893d6a8c813e877de0a455aadcaf1a4faf2f8bc74ede210c0399435357919d77dcd4e83ecce226e0faf31581e3e#npm:5.10.0"],\ + ["lodash", "npm:4.17.21"],\ + ["openapi-types", "npm:12.1.3"],\ + ["reflect-metadata", "npm:0.1.13"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@ioredis/commands", [\ ["npm:1.2.0", {\ "packageLocation": "./.yarn/cache/@ioredis-commands-npm-1.2.0-47541de88b-a8253c9539.zip/node_modules/@ioredis/commands/",\ @@ -2262,6 +2301,14 @@ const RAW_RUNTIME_STATE = ["vary", "npm:1.1.2"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:4.0.0", {\ + "packageLocation": "./.yarn/cache/@koa-cors-npm-4.0.0-e1649c9262-1509d98b72.zip/node_modules/@koa/cors/",\ + "packageDependencies": [\ + ["@koa/cors", "npm:4.0.0"],\ + ["vary", "npm:1.1.2"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["@koa/multer", [\ @@ -2288,6 +2335,18 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@koa/router", [\ + ["npm:12.0.1", {\ + "packageLocation": "./.yarn/cache/@koa-router-npm-12.0.1-6a9764e4df-f9427c8eb2.zip/node_modules/@koa/router/",\ + "packageDependencies": [\ + ["@koa/router", "npm:12.0.1"],\ + ["debug", "virtual:ac3d8e680759ce54399273724d44e041d6c9b73454d191d411a8c44bb27e22f02aaf6ed9d3ad0ac1c298eac4833cff369c9c7b84c573016112c4f84be2cd8543#npm:4.3.4"],\ + ["http-errors", "npm:2.0.0"],\ + ["koa-compose", "npm:4.1.0"],\ + ["methods", "npm:1.1.2"],\ + ["path-to-regexp", "npm:6.2.1"]\ + ],\ + "linkType": "HARD"\ + }],\ ["npm:9.0.1", {\ "packageLocation": "./.yarn/cache/@koa-router-npm-9.0.1-1df2e7d8d0-bd13ffa9e3.zip/node_modules/@koa/router/",\ "packageDependencies": [\ @@ -4107,21 +4166,6 @@ const RAW_RUNTIME_STATE = }]\ ]],\ ["@types/koa", [\ - ["npm:2.13.6", {\ - "packageLocation": "./.yarn/cache/@types-koa-npm-2.13.6-a8726b332e-cba52d2418.zip/node_modules/@types/koa/",\ - "packageDependencies": [\ - ["@types/koa", "npm:2.13.6"],\ - ["@types/accepts", "npm:1.3.5"],\ - ["@types/content-disposition", "npm:0.5.5"],\ - ["@types/cookies", "npm:0.7.7"],\ - ["@types/http-assert", "npm:1.5.3"],\ - ["@types/http-errors", "npm:2.0.1"],\ - ["@types/keygrip", "npm:1.0.2"],\ - ["@types/koa-compose", "npm:3.2.5"],\ - ["@types/node", "npm:20.4.5"]\ - ],\ - "linkType": "HARD"\ - }],\ ["npm:2.13.7", {\ "packageLocation": "./.yarn/cache/@types-koa-npm-2.13.7-240d3cdcf2-4856cb68f7.zip/node_modules/@types/koa/",\ "packageDependencies": [\ @@ -4198,6 +4242,18 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@types/koa-ratelimit", [\ + ["npm:5.0.4", {\ + "packageLocation": "./.yarn/cache/@types-koa-ratelimit-npm-5.0.4-92296aa01b-5233eec465.zip/node_modules/@types/koa-ratelimit/",\ + "packageDependencies": [\ + ["@types/koa-ratelimit", "npm:5.0.4"],\ + ["@types/koa", "npm:2.13.7"],\ + ["@types/node", "npm:20.4.5"],\ + ["ioredis", "npm:5.3.2"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/koa-send", [\ ["npm:4.1.3", {\ "packageLocation": "./.yarn/cache/@types-koa-send-npm-4.1.3-07a2282495-f20f6a0dcc.zip/node_modules/@types/koa-send/",\ @@ -4707,6 +4763,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["@types/validator", [\ + ["npm:13.11.7", {\ + "packageLocation": "./.yarn/cache/@types-validator-npm-13.11.7-1eda8f8948-5935d363a9.zip/node_modules/@types/validator/",\ + "packageDependencies": [\ + ["@types/validator", "npm:13.11.7"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["@types/vinyl", [\ ["npm:2.0.7", {\ "packageLocation": "./.yarn/cache/@types-vinyl-npm-2.0.7-975445872b-c13002667b.zip/node_modules/@types/vinyl/",\ @@ -6556,6 +6621,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["async-ratelimiter", [\ + ["npm:1.3.12", {\ + "packageLocation": "./.yarn/cache/async-ratelimiter-npm-1.3.12-25c4d3ff00-bf492d85a3.zip/node_modules/async-ratelimiter/",\ + "packageDependencies": [\ + ["async-ratelimiter", "npm:1.3.12"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["async-settle", [\ ["npm:1.0.0", {\ "packageLocation": "./.yarn/cache/async-settle-npm-1.0.0-5d08fbf926-d2382ad4b9.zip/node_modules/async-settle/",\ @@ -6932,6 +7006,8 @@ const RAW_RUNTIME_STATE = ["@bull-board/koa", "npm:5.6.0"],\ ["@bull-board/ui", "npm:5.6.0"],\ ["@discordapp/twemoji", "npm:14.1.2"],\ + ["@hapi/boom", "npm:10.0.1"],\ + ["@iceshrimp/koa-openapi", "npm:2.2.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F2.2.0%2Fkoa-openapi-2.2.0.tgz"],\ ["@koa/cors", "npm:3.4.3"],\ ["@koa/multer", "virtual:aa59773ac87791c4813d53447077fcf8a847d6de5a301d34dc31286584b1dbb26d30d3adb5b4c41c1e8aea04371e926fda05c09c6253647c432e11d872a304ba#npm:3.0.2"],\ ["@koa/router", "npm:9.0.1"],\ @@ -6954,12 +7030,13 @@ const RAW_RUNTIME_STATE = ["@types/jsdom", "npm:21.1.1"],\ ["@types/jsonld", "npm:1.5.9"],\ ["@types/jsrsasign", "npm:10.5.8"],\ - ["@types/koa", "npm:2.13.6"],\ + ["@types/koa", "npm:2.13.7"],\ ["@types/koa-bodyparser", "npm:4.3.10"],\ ["@types/koa-cors", "npm:0.0.2"],\ ["@types/koa-favicon", "npm:2.0.21"],\ ["@types/koa-logger", "npm:3.1.2"],\ ["@types/koa-mount", "npm:4.0.2"],\ + ["@types/koa-ratelimit", "npm:5.0.4"],\ ["@types/koa-send", "npm:4.1.3"],\ ["@types/koa-views", "npm:7.0.0"],\ ["@types/koa__cors", "npm:3.3.0"],\ @@ -7042,6 +7119,7 @@ const RAW_RUNTIME_STATE = ["koa-json-body", "npm:5.3.0"],\ ["koa-logger", "npm:3.2.1"],\ ["koa-mount", "npm:4.0.0"],\ + ["koa-ratelimit", "npm:5.1.0"],\ ["koa-remove-trailing-slashes", "npm:2.0.3"],\ ["koa-send", "npm:5.0.1"],\ ["koa-slow", "npm:2.1.0"],\ @@ -8219,6 +8297,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["class-transformer", [\ + ["npm:0.5.1", {\ + "packageLocation": "./.yarn/cache/class-transformer-npm-0.5.1-96b5161e6c-750327e3e9.zip/node_modules/class-transformer/",\ + "packageDependencies": [\ + ["class-transformer", "npm:0.5.1"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["class-utils", [\ ["npm:0.3.6", {\ "packageLocation": "./.yarn/cache/class-utils-npm-0.3.6-2c691ad006-b236d9deb6.zip/node_modules/class-utils/",\ @@ -8232,6 +8319,18 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["class-validator", [\ + ["npm:0.14.0", {\ + "packageLocation": "./.yarn/cache/class-validator-npm-0.14.0-b600d5fe72-bf550a4ab7.zip/node_modules/class-validator/",\ + "packageDependencies": [\ + ["class-validator", "npm:0.14.0"],\ + ["@types/validator", "npm:13.11.7"],\ + ["libphonenumber-js", "npm:1.10.51"],\ + ["validator", "npm:13.9.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["clean-regexp", [\ ["npm:1.0.0", {\ "packageLocation": "./.yarn/cache/clean-regexp-npm-1.0.0-f349f98f15-0b1ce281b0.zip/node_modules/clean-regexp/",\ @@ -13444,6 +13543,20 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["handlebars", [\ + ["npm:4.7.8", {\ + "packageLocation": "./.yarn/cache/handlebars-npm-4.7.8-25244c2c82-bd528f4dd1.zip/node_modules/handlebars/",\ + "packageDependencies": [\ + ["handlebars", "npm:4.7.8"],\ + ["minimist", "npm:1.2.8"],\ + ["neo-async", "npm:2.6.2"],\ + ["source-map", "npm:0.6.1"],\ + ["uglify-js", "npm:3.17.4"],\ + ["wordwrap", "npm:1.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["happy-dom", [\ ["npm:12.10.3", {\ "packageLocation": "./.yarn/cache/happy-dom-npm-12.10.3-5cca1a5e23-de82ddd1c9.zip/node_modules/happy-dom/",\ @@ -13616,6 +13729,22 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["helmet", [\ + ["npm:6.0.1", {\ + "packageLocation": "./.yarn/cache/helmet-npm-6.0.1-204284f33b-0eec311e1a.zip/node_modules/helmet/",\ + "packageDependencies": [\ + ["helmet", "npm:6.0.1"]\ + ],\ + "linkType": "HARD"\ + }],\ + ["npm:6.2.0", {\ + "packageLocation": "./.yarn/cache/helmet-npm-6.2.0-43622c54ea-f112fcd0d8.zip/node_modules/helmet/",\ + "packageDependencies": [\ + ["helmet", "npm:6.2.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["hexoid", [\ ["npm:1.0.0", {\ "packageLocation": "./.yarn/cache/hexoid-npm-1.0.0-2274609209-f2271b8b6b.zip/node_modules/hexoid/",\ @@ -16545,6 +16674,16 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["koa-helmet", [\ + ["npm:7.0.2", {\ + "packageLocation": "./.yarn/cache/koa-helmet-npm-7.0.2-4d457c75ac-a2f8f72f05.zip/node_modules/koa-helmet/",\ + "packageDependencies": [\ + ["koa-helmet", "npm:7.0.2"],\ + ["helmet", "npm:6.2.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["koa-json-body", [\ ["npm:5.3.0", {\ "packageLocation": "./.yarn/cache/koa-json-body-npm-5.3.0-599f1cafdb-d5b3cef1b1.zip/node_modules/koa-json-body/",\ @@ -16579,6 +16718,18 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["koa-ratelimit", [\ + ["npm:5.1.0", {\ + "packageLocation": "./.yarn/cache/koa-ratelimit-npm-5.1.0-1ab589a18d-09c718b727.zip/node_modules/koa-ratelimit/",\ + "packageDependencies": [\ + ["koa-ratelimit", "npm:5.1.0"],\ + ["async-ratelimiter", "npm:1.3.12"],\ + ["debug", "virtual:ac3d8e680759ce54399273724d44e041d6c9b73454d191d411a8c44bb27e22f02aaf6ed9d3ad0ac1c298eac4833cff369c9c7b84c573016112c4f84be2cd8543#npm:4.3.4"],\ + ["ms", "npm:2.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["koa-remove-trailing-slashes", [\ ["npm:2.0.3", {\ "packageLocation": "./.yarn/cache/koa-remove-trailing-slashes-npm-2.0.3-2b7f265a71-43336f9792.zip/node_modules/koa-remove-trailing-slashes/",\ @@ -16673,7 +16824,7 @@ const RAW_RUNTIME_STATE = "packageLocation": "./.yarn/__virtual__/koa-views-virtual-bf8ad99819/0/cache/koa-views-npm-7.0.2-f4a5c0091b-edff754c9f.zip/node_modules/koa-views/",\ "packageDependencies": [\ ["koa-views", "virtual:aa59773ac87791c4813d53447077fcf8a847d6de5a301d34dc31286584b1dbb26d30d3adb5b4c41c1e8aea04371e926fda05c09c6253647c432e11d872a304ba#npm:7.0.2"],\ - ["@types/koa", "npm:2.13.6"],\ + ["@types/koa", "npm:2.13.7"],\ ["consolidate", "virtual:92dc05b84fde30e8037028575c990319f08cb08b0698e442e4c8d1ac4aceb7d666cb7b6454e308d767b637ef0226cb4e16e0c82a358c214878c7185041270440#npm:0.16.0"],\ ["debug", "virtual:ac3d8e680759ce54399273724d44e041d6c9b73454d191d411a8c44bb27e22f02aaf6ed9d3ad0ac1c298eac4833cff369c9c7b84c573016112c4f84be2cd8543#npm:4.3.4"],\ ["get-paths", "npm:0.0.7"],\ @@ -16706,6 +16857,29 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["koa2-swagger-ui", [\ + ["npm:5.10.0", {\ + "packageLocation": "./.yarn/cache/koa2-swagger-ui-npm-5.10.0-54bce94261-40575d377d.zip/node_modules/koa2-swagger-ui/",\ + "packageDependencies": [\ + ["koa2-swagger-ui", "npm:5.10.0"]\ + ],\ + "linkType": "SOFT"\ + }],\ + ["virtual:b79af143542f14e992898e20a1ab22405dbd6893d6a8c813e877de0a455aadcaf1a4faf2f8bc74ede210c0399435357919d77dcd4e83ecce226e0faf31581e3e#npm:5.10.0", {\ + "packageLocation": "./.yarn/__virtual__/koa2-swagger-ui-virtual-7468e59b3e/0/cache/koa2-swagger-ui-npm-5.10.0-54bce94261-40575d377d.zip/node_modules/koa2-swagger-ui/",\ + "packageDependencies": [\ + ["koa2-swagger-ui", "virtual:b79af143542f14e992898e20a1ab22405dbd6893d6a8c813e877de0a455aadcaf1a4faf2f8bc74ede210c0399435357919d77dcd4e83ecce226e0faf31581e3e#npm:5.10.0"],\ + ["@types/koa", null],\ + ["handlebars", "npm:4.7.8"],\ + ["lodash", "npm:4.17.21"],\ + ["read-pkg-up", "npm:7.0.1"]\ + ],\ + "packagePeers": [\ + "@types/koa"\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["ky", [\ ["npm:0.33.3", {\ "packageLocation": "./.yarn/cache/ky-npm-0.33.3-7d1cbfa9f4-556b2241fe.zip/node_modules/ky/",\ @@ -16813,6 +16987,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["libphonenumber-js", [\ + ["npm:1.10.51", {\ + "packageLocation": "./.yarn/cache/libphonenumber-js-npm-1.10.51-4ff79b15f8-925fda2ecb.zip/node_modules/libphonenumber-js/",\ + "packageDependencies": [\ + ["libphonenumber-js", "npm:1.10.51"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["liftoff", [\ ["npm:3.1.0", {\ "packageLocation": "./.yarn/cache/liftoff-npm-3.1.0-6dd0a868bd-af0ea7c51c.zip/node_modules/liftoff/",\ @@ -18738,6 +18921,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["openapi-types", [\ + ["npm:12.1.3", {\ + "packageLocation": "./.yarn/cache/openapi-types-npm-12.1.3-1b8ae4a632-9d1d7ed848.zip/node_modules/openapi-types/",\ + "packageDependencies": [\ + ["openapi-types", "npm:12.1.3"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["opencollective-postinstall", [\ ["npm:2.0.3", {\ "packageLocation": "./.yarn/cache/opencollective-postinstall-npm-2.0.3-954643c36b-69d6377808.zip/node_modules/opencollective-postinstall/",\ @@ -24205,6 +24397,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["uglify-js", [\ + ["npm:3.17.4", {\ + "packageLocation": "./.yarn/cache/uglify-js-npm-3.17.4-58d4ab56aa-4c0b800e0f.zip/node_modules/uglify-js/",\ + "packageDependencies": [\ + ["uglify-js", "npm:3.17.4"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["ulid", [\ ["npm:2.3.0", {\ "packageLocation": "./.yarn/cache/ulid-npm-2.3.0-2dd679bbd3-11d7dd3507.zip/node_modules/ulid/",\ @@ -25467,6 +25668,15 @@ const RAW_RUNTIME_STATE = "linkType": "HARD"\ }]\ ]],\ + ["wordwrap", [\ + ["npm:1.0.0", {\ + "packageLocation": "./.yarn/cache/wordwrap-npm-1.0.0-ae57a645e8-497d40beb2.zip/node_modules/wordwrap/",\ + "packageDependencies": [\ + ["wordwrap", "npm:1.0.0"]\ + ],\ + "linkType": "HARD"\ + }]\ + ]],\ ["workerpool", [\ ["npm:6.2.1", {\ "packageLocation": "./.yarn/cache/workerpool-npm-6.2.1-1486cb2056-3e637f7632.zip/node_modules/workerpool/",\ diff --git a/.yarn/cache/@hapi-boom-npm-10.0.1-306fb2f69f-99415b0e2f.zip b/.yarn/cache/@hapi-boom-npm-10.0.1-306fb2f69f-99415b0e2f.zip new file mode 100644 index 000000000..307682cc5 --- /dev/null +++ b/.yarn/cache/@hapi-boom-npm-10.0.1-306fb2f69f-99415b0e2f.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5b709e2a5e5674875e997f23fff0d746a3c109f8836f8af16a76a752cdebcacd +size 8091 diff --git a/.yarn/cache/@hapi-hoek-npm-11.0.4-3a5c13a5d9-0cf1c0dbd2.zip b/.yarn/cache/@hapi-hoek-npm-11.0.4-3a5c13a5d9-0cf1c0dbd2.zip new file mode 100644 index 000000000..a04d98536 --- /dev/null +++ b/.yarn/cache/@hapi-hoek-npm-11.0.4-3a5c13a5d9-0cf1c0dbd2.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:175dd0223d2803d5bae1c94af2160c177725e6f2a37d1add35aba74b8acc27f4 +size 26735 diff --git a/.yarn/cache/@iceshrimp-koa-openapi-npm-2.2.0-b79af14354-a7ff2c0b1a.zip b/.yarn/cache/@iceshrimp-koa-openapi-npm-2.2.0-b79af14354-a7ff2c0b1a.zip new file mode 100644 index 000000000..9dd46a278 --- /dev/null +++ b/.yarn/cache/@iceshrimp-koa-openapi-npm-2.2.0-b79af14354-a7ff2c0b1a.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cbce7314ef75227caeac016c06f2282916ab5d5754c825a8f595c1e407dab52c +size 100168 diff --git a/.yarn/cache/@koa-cors-npm-4.0.0-e1649c9262-1509d98b72.zip b/.yarn/cache/@koa-cors-npm-4.0.0-e1649c9262-1509d98b72.zip new file mode 100644 index 000000000..83bf22f93 --- /dev/null +++ b/.yarn/cache/@koa-cors-npm-4.0.0-e1649c9262-1509d98b72.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d203abc82cb17503814d9c8b81e1c45960eaf14af440b3ad5ad389436f703c92 +size 7390 diff --git a/.yarn/cache/@koa-router-npm-12.0.1-6a9764e4df-f9427c8eb2.zip b/.yarn/cache/@koa-router-npm-12.0.1-6a9764e4df-f9427c8eb2.zip new file mode 100644 index 000000000..a1e8ddab2 --- /dev/null +++ b/.yarn/cache/@koa-router-npm-12.0.1-6a9764e4df-f9427c8eb2.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb2b50e784a6e40cb89b5f3657f146e473e7d370faf3339368f824152cc37b20 +size 11826 diff --git a/.yarn/cache/@types-koa-npm-2.13.6-a8726b332e-cba52d2418.zip b/.yarn/cache/@types-koa-npm-2.13.6-a8726b332e-cba52d2418.zip deleted file mode 100644 index 830d56a59..000000000 --- a/.yarn/cache/@types-koa-npm-2.13.6-a8726b332e-cba52d2418.zip +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:091742880874a5f03e22ce6967e0e11b602980ab53ba491b2ffe127448e5c6e9 -size 8224 diff --git a/.yarn/cache/@types-koa-ratelimit-npm-5.0.4-92296aa01b-5233eec465.zip b/.yarn/cache/@types-koa-ratelimit-npm-5.0.4-92296aa01b-5233eec465.zip new file mode 100644 index 000000000..8a212d7bd --- /dev/null +++ b/.yarn/cache/@types-koa-ratelimit-npm-5.0.4-92296aa01b-5233eec465.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ebce4ff26ab73e14315806c314ad03ffa09b8ace4a8d41c2b1aaef689a2f165c +size 3396 diff --git a/.yarn/cache/@types-validator-npm-13.11.7-1eda8f8948-5935d363a9.zip b/.yarn/cache/@types-validator-npm-13.11.7-1eda8f8948-5935d363a9.zip new file mode 100644 index 000000000..0eac70bef --- /dev/null +++ b/.yarn/cache/@types-validator-npm-13.11.7-1eda8f8948-5935d363a9.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:328b1e4099ecd813b4638c1d42e07e8d0586e1760b53052465eda2cef760d7a9 +size 59148 diff --git a/.yarn/cache/async-ratelimiter-npm-1.3.12-25c4d3ff00-bf492d85a3.zip b/.yarn/cache/async-ratelimiter-npm-1.3.12-25c4d3ff00-bf492d85a3.zip new file mode 100644 index 000000000..485edadb2 --- /dev/null +++ b/.yarn/cache/async-ratelimiter-npm-1.3.12-25c4d3ff00-bf492d85a3.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:009591b9d737a753890dc6dceac9691d3317d3cd5928f73560fc5ac44a7188de +size 5424 diff --git a/.yarn/cache/class-transformer-npm-0.5.1-96b5161e6c-750327e3e9.zip b/.yarn/cache/class-transformer-npm-0.5.1-96b5161e6c-750327e3e9.zip new file mode 100644 index 000000000..df6a76aac --- /dev/null +++ b/.yarn/cache/class-transformer-npm-0.5.1-96b5161e6c-750327e3e9.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:280149f618cf89ff147b8ba1a79076434d602fff02ea513f80b1fad59015874a +size 245809 diff --git a/.yarn/cache/class-validator-npm-0.14.0-b600d5fe72-bf550a4ab7.zip b/.yarn/cache/class-validator-npm-0.14.0-b600d5fe72-bf550a4ab7.zip new file mode 100644 index 000000000..ce42574b7 --- /dev/null +++ b/.yarn/cache/class-validator-npm-0.14.0-b600d5fe72-bf550a4ab7.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:afbfc98db5cb71d75934d0f14b899cd932da03f4eb8f053eceecbd119357cb13 +size 1363491 diff --git a/.yarn/cache/handlebars-npm-4.7.8-25244c2c82-bd528f4dd1.zip b/.yarn/cache/handlebars-npm-4.7.8-25244c2c82-bd528f4dd1.zip new file mode 100644 index 000000000..d02e63640 --- /dev/null +++ b/.yarn/cache/handlebars-npm-4.7.8-25244c2c82-bd528f4dd1.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a12226dba584a5bacad74056cea3cceed866b63905f5ec5a082a1f6743d1c9b +size 746644 diff --git a/.yarn/cache/helmet-npm-6.0.1-204284f33b-0eec311e1a.zip b/.yarn/cache/helmet-npm-6.0.1-204284f33b-0eec311e1a.zip new file mode 100644 index 000000000..d59dffe2e --- /dev/null +++ b/.yarn/cache/helmet-npm-6.0.1-204284f33b-0eec311e1a.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4bd83012df855cc8eaba498b0e6ba85bd7dd861139c46aec16679d9e7faf9210 +size 34311 diff --git a/.yarn/cache/helmet-npm-6.2.0-43622c54ea-f112fcd0d8.zip b/.yarn/cache/helmet-npm-6.2.0-43622c54ea-f112fcd0d8.zip new file mode 100644 index 000000000..51b42e966 --- /dev/null +++ b/.yarn/cache/helmet-npm-6.2.0-43622c54ea-f112fcd0d8.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31699b7cf9fed0741cb3d7a1af171757afc9f2d6eb1371aad63913fbdb0d00af +size 26925 diff --git a/.yarn/cache/koa-helmet-npm-7.0.2-4d457c75ac-a2f8f72f05.zip b/.yarn/cache/koa-helmet-npm-7.0.2-4d457c75ac-a2f8f72f05.zip new file mode 100644 index 000000000..e62df1272 --- /dev/null +++ b/.yarn/cache/koa-helmet-npm-7.0.2-4d457c75ac-a2f8f72f05.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:08fe966ed1800a409321bcafb42d9dd054347260c819f499030a930d3b19d621 +size 4062 diff --git a/.yarn/cache/koa-ratelimit-npm-5.1.0-1ab589a18d-09c718b727.zip b/.yarn/cache/koa-ratelimit-npm-5.1.0-1ab589a18d-09c718b727.zip new file mode 100644 index 000000000..82e447362 --- /dev/null +++ b/.yarn/cache/koa-ratelimit-npm-5.1.0-1ab589a18d-09c718b727.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d4c8533035a35ff73c2d8cdca58cc6248364182a31759960e5d4539ba20ca8a +size 5755 diff --git a/.yarn/cache/koa2-swagger-ui-npm-5.10.0-54bce94261-40575d377d.zip b/.yarn/cache/koa2-swagger-ui-npm-5.10.0-54bce94261-40575d377d.zip new file mode 100644 index 000000000..fc2160a9a --- /dev/null +++ b/.yarn/cache/koa2-swagger-ui-npm-5.10.0-54bce94261-40575d377d.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f76bad8900a1ebc9890523105176444b6c8776a26b5476551e7e5265a339acc8 +size 10365 diff --git a/.yarn/cache/libphonenumber-js-npm-1.10.51-4ff79b15f8-925fda2ecb.zip b/.yarn/cache/libphonenumber-js-npm-1.10.51-4ff79b15f8-925fda2ecb.zip new file mode 100644 index 000000000..c4cfd527b --- /dev/null +++ b/.yarn/cache/libphonenumber-js-npm-1.10.51-4ff79b15f8-925fda2ecb.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ddabf82e42dbf3e6051e93af37e0db198c4d3b367a889acb1f20c1eda6270413 +size 2294737 diff --git a/.yarn/cache/openapi-types-npm-12.1.3-1b8ae4a632-9d1d7ed848.zip b/.yarn/cache/openapi-types-npm-12.1.3-1b8ae4a632-9d1d7ed848.zip new file mode 100644 index 000000000..56f6b23cd --- /dev/null +++ b/.yarn/cache/openapi-types-npm-12.1.3-1b8ae4a632-9d1d7ed848.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb7e7b3e356555e8cb99058ea2377cb2a31a72e419acc97eca60f50b5ca443b1 +size 8525 diff --git a/.yarn/cache/uglify-js-npm-3.17.4-58d4ab56aa-4c0b800e0f.zip b/.yarn/cache/uglify-js-npm-3.17.4-58d4ab56aa-4c0b800e0f.zip new file mode 100644 index 000000000..df5e84a0b --- /dev/null +++ b/.yarn/cache/uglify-js-npm-3.17.4-58d4ab56aa-4c0b800e0f.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16f8664428c801f25ae1182cd7fcab03de96c397452138ff4070f16cd9aa894f +size 243799 diff --git a/.yarn/cache/wordwrap-npm-1.0.0-ae57a645e8-497d40beb2.zip b/.yarn/cache/wordwrap-npm-1.0.0-ae57a645e8-497d40beb2.zip new file mode 100644 index 000000000..1c9582e20 --- /dev/null +++ b/.yarn/cache/wordwrap-npm-1.0.0-ae57a645e8-497d40beb2.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:61274545f56606562f2a7fbf1bc26fa1c67baefd0fd6ba73e71b4ec23f39ac0f +size 16430 diff --git a/packages/backend/package.json b/packages/backend/package.json index 7ff83b54d..e4414e1e6 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -27,6 +27,8 @@ "@bull-board/koa": "5.6.0", "@bull-board/ui": "5.6.0", "@discordapp/twemoji": "14.1.2", + "@hapi/boom": "^10.0.1", + "@iceshrimp/koa-openapi": "^2.2.0", "@koa/cors": "3.4.3", "@koa/multer": "3.0.2", "@koa/router": "9.0.1", @@ -84,6 +86,7 @@ "koa-json-body": "5.3.0", "koa-logger": "3.2.1", "koa-mount": "4.0.0", + "koa-ratelimit": "^5.1.0", "koa-remove-trailing-slashes": "2.0.3", "koa-send": "5.0.1", "koa-slow": "2.1.0", @@ -150,12 +153,13 @@ "@types/jsdom": "21.1.1", "@types/jsonld": "1.5.9", "@types/jsrsasign": "10.5.8", - "@types/koa": "2.13.6", + "@types/koa": "2.13.7", "@types/koa-bodyparser": "4.3.10", "@types/koa-cors": "0.0.2", "@types/koa-favicon": "2.0.21", "@types/koa-logger": "3.1.2", "@types/koa-mount": "4.0.2", + "@types/koa-ratelimit": "^5.0.4", "@types/koa-send": "4.1.3", "@types/koa-views": "7.0.0", "@types/koa__cors": "3.3.0", diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 3d09d4d40..ae3301214 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -19,6 +19,8 @@ import signupPending from "./private/signup-pending.js"; import verifyEmail from "./private/verify-email.js"; import discord from "./service/discord.js"; import github from "./service/github.js"; +import { WebAPI } from "./web/index.js"; +import mount from "koa-mount"; // Init app const app = new Koa(); @@ -138,6 +140,9 @@ errorRouter.all("(.*)", async (ctx) => { ctx.status = 404; }); +// Register Web API +await new WebAPI('/iceshrimp').setup(app); + // Register router app.use(mastoRouter.routes()); app.use(mastoRouter.allowedMethods()); diff --git a/packages/backend/src/server/api/web/controllers/user.ts b/packages/backend/src/server/api/web/controllers/user.ts new file mode 100644 index 000000000..d294001ea --- /dev/null +++ b/packages/backend/src/server/api/web/controllers/user.ts @@ -0,0 +1,29 @@ +import { Controller, CurrentUser, Get, Params, Query } from "@iceshrimp/koa-openapi"; +import { UserDetailedResponse, UserResponse } from "@/server/api/web/entities/user.js"; +import { TimelineResponse } from "@/server/api/web/entities/note.js"; +import type { ILocalUser } from "@/models/entities/user.js"; +import { UserHandler } from "@/server/api/web/handlers/user.js"; + +@Controller('/user') +export class UserController { + @Get('/:id') + async getUser( + @CurrentUser() me: ILocalUser | null, + @Params('id') id: string, + @Query('detail') detail: boolean + ): Promise { + return detail + ? UserHandler.getUser(me, id) + : UserHandler.getUserDetailed(me, id); + } + + @Get('/:id/notes') + async getUserNotes( + @CurrentUser() me: ILocalUser | null, + @Params('id') id: string, + @Query('limit') limit: number = 20, + @Query('replies') replies: boolean = false, + ): Promise { + return UserHandler.getUserNotes(me, id, limit, replies); + } +} diff --git a/packages/backend/src/server/api/web/entities/note.ts b/packages/backend/src/server/api/web/entities/note.ts new file mode 100644 index 000000000..f56abc87c --- /dev/null +++ b/packages/backend/src/server/api/web/entities/note.ts @@ -0,0 +1,4 @@ +import { Note } from "@/models/entities/note.js"; + +export type NoteResponse = {} & Note; +export type TimelineResponse = NoteResponse[]; diff --git a/packages/backend/src/server/api/web/entities/user.ts b/packages/backend/src/server/api/web/entities/user.ts new file mode 100644 index 000000000..459efeeb1 --- /dev/null +++ b/packages/backend/src/server/api/web/entities/user.ts @@ -0,0 +1,11 @@ +export type UserResponse = { + id: string; + username: string; + avatarUrl?: string; + bannerUrl?: string; +} + +export type UserDetailedResponse = UserResponse & { + followers: number; + following: number; +} diff --git a/packages/backend/src/server/api/web/handlers/user.ts b/packages/backend/src/server/api/web/handlers/user.ts new file mode 100644 index 000000000..595f3bd9f --- /dev/null +++ b/packages/backend/src/server/api/web/handlers/user.ts @@ -0,0 +1,56 @@ +import { TimelineResponse } from "@/server/api/web/entities/note.js"; +import { UserDetailedResponse, UserResponse } from "@/server/api/web/entities/user.js"; +import { Notes, UserProfiles, Users } from "@/models/index.js"; +import { makePaginationQuery } from "@/server/api/common/make-pagination-query.js"; +import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js"; +import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js"; +import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js"; +import { ILocalUser } from "@/models/entities/user.js"; +import { notFound } from "@hapi/boom"; + +export class UserHandler { + public static async getUserNotes(me: ILocalUser | null, id: string, limit: number, replies: boolean): Promise { + const user = await Users.findOneBy({ id }); + if (!user) throw notFound('No such user'); + + const query = makePaginationQuery(Notes.createQueryBuilder('note')) + .andWhere("note.userId = :userId", { userId: id }) + .innerJoinAndSelect("note.user", "user") + .leftJoinAndSelect("note.reply", "reply") + .leftJoinAndSelect("note.renote", "renote") + .leftJoinAndSelect("reply.user", "replyUser") + .leftJoinAndSelect("renote.user", "renoteUser"); + + generateVisibilityQuery(query, me); + if (me) { + generateMutedUserQuery(query, me, user); + generateBlockedUserQuery(query, me); + } + + if (!replies) { + query.andWhere("note.replyId IS NULL"); + } + + return query.take(Math.min(limit, 100)).getMany(); + } + + public static async getUser(me: ILocalUser | null, id: string): Promise { + const user = await Users.findOneBy({ id }); + if (!user) throw notFound('No such user'); + return { + id: user.id, + username: user.username, + avatarUrl: user.avatarUrl ?? undefined, + bannerUrl: user.bannerUrl ?? undefined, + }; + } + + public static async getUserDetailed(me: ILocalUser | null, id: string): Promise { + const profile = await UserProfiles.findOneBy({ userId: id }); + return { + followers: 0, + following: 0, + ...await this.getUser(me, id), + } + } +} diff --git a/packages/backend/src/server/api/web/index.ts b/packages/backend/src/server/api/web/index.ts new file mode 100644 index 000000000..bf0bc55be --- /dev/null +++ b/packages/backend/src/server/api/web/index.ts @@ -0,0 +1,63 @@ +import Router from "@koa/router"; +import Koa, { DefaultState, Context, Middleware } from "koa"; +import { bootstrapControllers, Ctx } from "@iceshrimp/koa-openapi"; +import { ILocalUser } from "@/models/entities/user.js"; +import { AccessToken } from "@/models/entities/access-token.js"; +import { UserController } from "@/server/api/web/controllers/user.js"; +import { RatelimitMiddleware } from "@/server/api/web/middleware/rate-limit.js"; +import { AuthenticationMiddleware } from "@/server/api/web/middleware/auth.js"; +import { ErrorHandlingMiddleware } from "@/server/api/web/middleware/error-handling.js"; + +export type WebRouter = Router; +export type WebMiddleware = Middleware; + +export interface WebState extends DefaultState {} + +export interface WebContext extends Context { + user: ILocalUser | null; + token: AccessToken | null; +} + +export class WebAPI { + private readonly router: WebRouter; + + constructor(prefix: string) { + this.router = new Router({ prefix }); + } + + public async setup(app: Koa): Promise { + await bootstrapControllers({ + app: app, + router: this.router, + attachRoutes: true, + errorHandler: ErrorHandlingMiddleware, + controllers: [ + UserController, + ], + flow: [ + AuthenticationMiddleware, + RatelimitMiddleware, + ], + versions: { + 1: true + }, + bodyParser: { + multipart: true, + }, + openAPI: { + enabled: true, + publicURL: '/api/iceshrimp', + options: { + title: "Iceshrimp Web API documentation" + }, + spec: { + info: { + title: "Iceshrimp Web API", + description: "Documentation for using Iceshrimp's Web API", + version: "1.0.0" + } + } + }, + }); + } +} diff --git a/packages/backend/src/server/api/web/middleware/auth.ts b/packages/backend/src/server/api/web/middleware/auth.ts new file mode 100644 index 000000000..278f3e75d --- /dev/null +++ b/packages/backend/src/server/api/web/middleware/auth.ts @@ -0,0 +1,28 @@ +import { WebMiddleware, WebContext, WebState } from "@/server/api/web/index.js"; +import { Next } from "koa"; +import authenticate from "@/server/api/authenticate.js"; + +export const AuthenticationMiddleware: WebMiddleware = async (ctx: WebContext, next: Next) => { + try { + const [ user, token ] = await authenticate(ctx.headers.authorization, null, false); + + //FIXME we shouldn't need to cast this + (ctx.state as WebState).user = user ?? null; + (ctx.state as WebState).token = token ?? null; + + } catch {} + + await next(); +} + +export function AuthorizationMiddleware(required: boolean, scopes: string[] = []): WebMiddleware { + return async (ctx: WebContext, next: Next) => { + try { + if (required && !(ctx.state as WebState).user) { + throw new Error(); //FIXME + } + } catch {} + + await next(); + } +} diff --git a/packages/backend/src/server/api/web/middleware/error-handling.ts b/packages/backend/src/server/api/web/middleware/error-handling.ts new file mode 100644 index 000000000..9f618f9d1 --- /dev/null +++ b/packages/backend/src/server/api/web/middleware/error-handling.ts @@ -0,0 +1,13 @@ +export const ErrorHandlingMiddleware = async (err: any, ctx: any) => { + if (err.isBoom) { + const error = err.output.payload; + error.errorDetails = error.statusCode >= 500 ? undefined : err.data; + ctx.body = error; + ctx.status = error.statusCode; + if (error.statusCode >= 500) console.error(err); + } else { + ctx.body = {error: 'Internal Server Error'}; + ctx.status = 500; + console.error(err); + } +} diff --git a/packages/backend/src/server/api/web/middleware/rate-limit.ts b/packages/backend/src/server/api/web/middleware/rate-limit.ts new file mode 100644 index 000000000..5abb86b92 --- /dev/null +++ b/packages/backend/src/server/api/web/middleware/rate-limit.ts @@ -0,0 +1,32 @@ +import koaRatelimit from "koa-ratelimit"; +import { WebContext, WebMiddleware } from "@/server/api/web/index.js"; +import { Next } from "koa"; +import { redisClient } from "@/db/redis.js"; +import { tooManyRequests } from "@hapi/boom"; + +export const RatelimitMiddleware: WebMiddleware = async (ctx: WebContext, next: Next) => { + // We can't assign limiter directly if we want to preserve type hints for WebContext and WebState + //TODO: server config options (disable limiter entirely, set max/duration, set different rate limits for auth/noauth, bypass rate limit for admins) + const limiter = koaRatelimit({ + driver: "redis", + db: redisClient, + max: 500, + duration: 60000, + id: () => ctx.state.user?.id ?? ctx.request.ip, + headers: { + remaining: 'X-RateLimit-Remaining', + total: 'X-RateLimit-Limit', + reset: 'X-RateLimit-Reset', + }, + throw: true, + }); + + try { + await limiter(ctx, next); + } + catch (e: any) { + if (e.name === 'TooManyRequestsError') + throw tooManyRequests(e.message); + throw e; + } +}; diff --git a/packages/backend/tsconfig.json b/packages/backend/tsconfig.json index 692d7b95b..64c07da6b 100644 --- a/packages/backend/tsconfig.json +++ b/packages/backend/tsconfig.json @@ -10,7 +10,7 @@ "declaration": false, "sourceMap": false, "target": "es2021", - "module": "es2020", + "module": "es2022", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "removeComments": false, diff --git a/yarn.lock b/yarn.lock index 420eb33c4..39b445875 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1140,6 +1140,22 @@ __metadata: languageName: node linkType: hard +"@hapi/boom@npm:^10.0.1": + version: 10.0.1 + resolution: "@hapi/boom@npm:10.0.1" + dependencies: + "@hapi/hoek": "npm:^11.0.2" + checksum: 99415b0e2f6aeefae91475e9215620d6cb0cc9f16b836e052c006cffb59cf39c45f724e545c2c50ba036d786c6c7935bffc97ae1cb504c65d2540d200bd40fff + languageName: node + linkType: hard + +"@hapi/hoek@npm:^11.0.2": + version: 11.0.4 + resolution: "@hapi/hoek@npm:11.0.4" + checksum: 0cf1c0dbd203701baccae2a78fc2381a77a2f5d2e46722a99c004ddb7eb7440da1bdeb58605acb1afd3287c940e88eec54ec5a35296bcda11e789092b0e58c36 + languageName: node + linkType: hard + "@hapi/hoek@npm:^9.0.0": version: 9.3.0 resolution: "@hapi/hoek@npm:9.3.0" @@ -1181,6 +1197,27 @@ __metadata: languageName: node linkType: hard +"@iceshrimp/koa-openapi@npm:^2.2.0": + version: 2.2.0 + resolution: "@iceshrimp/koa-openapi@npm:2.2.0::__archiveUrl=https%3A%2F%2Ficeshrimp.dev%2Fapi%2Fpackages%2Ficeshrimp%2Fnpm%2F%2540iceshrimp%252Fkoa-openapi%2F-%2F2.2.0%2Fkoa-openapi-2.2.0.tgz" + dependencies: + "@hapi/boom": "npm:^10.0.1" + "@koa/cors": "npm:^4.0.0" + "@koa/router": "npm:^12.0.1" + class-transformer: "npm:*" + class-validator: "npm:*" + helmet: "npm:6.0.1" + koa: "npm:^2.14.2" + koa-body: "npm:*" + koa-helmet: "npm:^7.0.2" + koa2-swagger-ui: "npm:^5.9.1" + lodash: "npm:^4.17.21" + openapi-types: "npm:^12.1.3" + reflect-metadata: "npm:*" + checksum: a7ff2c0b1a6fa54dacb9cc9a84d6a3008928f76396e47be42ddf93456f98c55bca42b027db378035b7f9d336ee703e44deb2a0e7d55167ddb081ff066c27f0a0 + languageName: node + linkType: hard + "@ioredis/commands@npm:^1.1.1": version: 1.2.0 resolution: "@ioredis/commands@npm:1.2.0" @@ -1526,6 +1563,15 @@ __metadata: languageName: node linkType: hard +"@koa/cors@npm:^4.0.0": + version: 4.0.0 + resolution: "@koa/cors@npm:4.0.0" + dependencies: + vary: "npm:^1.1.2" + checksum: 1509d98b72e321dda99a78b28626142a473533c84bde72b4ee7a76716bd91243765ec3d1790a214e4c5b4c398e72a6abace77f04dbcc7c499c955eed943494f0 + languageName: node + linkType: hard + "@koa/multer@npm:3.0.2": version: 3.0.2 resolution: "@koa/multer@npm:3.0.2" @@ -1550,6 +1596,19 @@ __metadata: languageName: node linkType: hard +"@koa/router@npm:^12.0.1": + version: 12.0.1 + resolution: "@koa/router@npm:12.0.1" + dependencies: + debug: "npm:^4.3.4" + http-errors: "npm:^2.0.0" + koa-compose: "npm:^4.1.0" + methods: "npm:^1.1.2" + path-to-regexp: "npm:^6.2.1" + checksum: f9427c8eb21a2d7c880dae192b7298ecb243af32a8270b5e3e32c4420da157111bc326ab0bc75ff0f17320b9d9ab9df235295fc2d53d8681b5a14103c27ad22d + languageName: node + linkType: hard + "@kurkle/color@npm:^0.3.0": version: 0.3.2 resolution: "@kurkle/color@npm:0.3.2" @@ -3053,6 +3112,17 @@ __metadata: languageName: node linkType: hard +"@types/koa-ratelimit@npm:^5.0.4": + version: 5.0.4 + resolution: "@types/koa-ratelimit@npm:5.0.4" + dependencies: + "@types/koa": "npm:*" + "@types/node": "npm:*" + ioredis: "npm:^5.2.3" + checksum: 5233eec4658d1300656cb5ecd8d55fa2ea7bed8310409c225ff975782844478d2f1bf722bdf87b81e0b270c010d9771287774f87e9fc2d766e908be82a69f398 + languageName: node + linkType: hard + "@types/koa-send@npm:4.1.3": version: 4.1.3 resolution: "@types/koa-send@npm:4.1.3" @@ -3071,7 +3141,7 @@ __metadata: languageName: node linkType: hard -"@types/koa@npm:*, @types/koa@npm:^2.13.5": +"@types/koa@npm:*, @types/koa@npm:2.13.7, @types/koa@npm:^2.13.5": version: 2.13.7 resolution: "@types/koa@npm:2.13.7" dependencies: @@ -3087,22 +3157,6 @@ __metadata: languageName: node linkType: hard -"@types/koa@npm:2.13.6": - version: 2.13.6 - resolution: "@types/koa@npm:2.13.6" - dependencies: - "@types/accepts": "npm:*" - "@types/content-disposition": "npm:*" - "@types/cookies": "npm:*" - "@types/http-assert": "npm:*" - "@types/http-errors": "npm:*" - "@types/keygrip": "npm:*" - "@types/koa-compose": "npm:*" - "@types/node": "npm:*" - checksum: cba52d2418668a2b7b6e8da791faab019584b50f7820130082cd97d46386f2a4c4a7bc59eecd734b98ca73ee8df9cda22485eb7e72738da223f54632dd0a020e - languageName: node - linkType: hard - "@types/koa__cors@npm:3.3.0": version: 3.3.0 resolution: "@types/koa__cors@npm:3.3.0" @@ -3518,6 +3572,13 @@ __metadata: languageName: node linkType: hard +"@types/validator@npm:^13.7.10": + version: 13.11.7 + resolution: "@types/validator@npm:13.11.7" + checksum: 5935d363a9a6099ee6a2100ae3be46d9d1f113dbea16e03b627b48b2f746f01100535febc564d215a7054dd9a632ec0cda10ecd6a75d088e3e38834290fb76f6 + languageName: node + linkType: hard + "@types/vinyl-fs@npm:*": version: 3.0.2 resolution: "@types/vinyl-fs@npm:3.0.2" @@ -4961,6 +5022,13 @@ __metadata: languageName: node linkType: hard +"async-ratelimiter@npm:^1.3.0": + version: 1.3.12 + resolution: "async-ratelimiter@npm:1.3.12" + checksum: bf492d85a3d9caa6a1a36be80f17c85afba4e2911228a50c73ad2bd14e775e7a140d690fba20948d994f4d33e8aaa4b5de4feabb618b971531bce4fe2b6764ce + languageName: node + linkType: hard + "async-settle@npm:^1.0.0": version: 1.0.0 resolution: "async-settle@npm:1.0.0" @@ -5248,6 +5316,8 @@ __metadata: "@bull-board/koa": "npm:5.6.0" "@bull-board/ui": "npm:5.6.0" "@discordapp/twemoji": "npm:14.1.2" + "@hapi/boom": "npm:^10.0.1" + "@iceshrimp/koa-openapi": "npm:^2.2.0" "@koa/cors": "npm:3.4.3" "@koa/multer": "npm:3.0.2" "@koa/router": "npm:9.0.1" @@ -5270,12 +5340,13 @@ __metadata: "@types/jsdom": "npm:21.1.1" "@types/jsonld": "npm:1.5.9" "@types/jsrsasign": "npm:10.5.8" - "@types/koa": "npm:2.13.6" + "@types/koa": "npm:2.13.7" "@types/koa-bodyparser": "npm:4.3.10" "@types/koa-cors": "npm:0.0.2" "@types/koa-favicon": "npm:2.0.21" "@types/koa-logger": "npm:3.1.2" "@types/koa-mount": "npm:4.0.2" + "@types/koa-ratelimit": "npm:^5.0.4" "@types/koa-send": "npm:4.1.3" "@types/koa-views": "npm:7.0.0" "@types/koa__cors": "npm:3.3.0" @@ -5358,6 +5429,7 @@ __metadata: koa-json-body: "npm:5.3.0" koa-logger: "npm:3.2.1" koa-mount: "npm:4.0.0" + koa-ratelimit: "npm:^5.1.0" koa-remove-trailing-slashes: "npm:2.0.3" koa-send: "npm:5.0.1" koa-slow: "npm:2.1.0" @@ -6381,6 +6453,13 @@ __metadata: languageName: node linkType: hard +"class-transformer@npm:*": + version: 0.5.1 + resolution: "class-transformer@npm:0.5.1" + checksum: 750327e3e9a5cf233c5234252f4caf6b06c437bf68a24acbdcfb06c8e0bfff7aa97c30428184813e38e08111b42871f20c5cf669ea4490f8ae837c09f08b31e7 + languageName: node + linkType: hard + "class-utils@npm:^0.3.5": version: 0.3.6 resolution: "class-utils@npm:0.3.6" @@ -6393,6 +6472,17 @@ __metadata: languageName: node linkType: hard +"class-validator@npm:*": + version: 0.14.0 + resolution: "class-validator@npm:0.14.0" + dependencies: + "@types/validator": "npm:^13.7.10" + libphonenumber-js: "npm:^1.10.14" + validator: "npm:^13.7.0" + checksum: bf550a4ab7b424467737c75e7e6171a45fe010ccab6af50793539d9d2e1f5cce1a35fbc8b11f9a5de4f049852f768232acca16f05cf80993a36369b494560f44 + languageName: node + linkType: hard + "clean-regexp@npm:^1.0.0": version: 1.0.0 resolution: "clean-regexp@npm:1.0.0" @@ -10761,6 +10851,24 @@ __metadata: languageName: node linkType: hard +"handlebars@npm:^4.7.8": + version: 4.7.8 + resolution: "handlebars@npm:4.7.8" + dependencies: + minimist: "npm:^1.2.5" + neo-async: "npm:^2.6.2" + source-map: "npm:^0.6.1" + uglify-js: "npm:^3.1.4" + wordwrap: "npm:^1.0.0" + dependenciesMeta: + uglify-js: + optional: true + bin: + handlebars: bin/handlebars + checksum: bd528f4dd150adf67f3f857118ef0fa43ff79a153b1d943fa0a770f2599e38b25a7a0dbac1a3611a4ec86970fd2325a81310fb788b5c892308c9f8743bd02e11 + languageName: node + linkType: hard + "happy-dom@npm:^12.10.3": version: 12.10.3 resolution: "happy-dom@npm:12.10.3" @@ -10915,6 +11023,20 @@ __metadata: languageName: node linkType: hard +"helmet@npm:6.0.1": + version: 6.0.1 + resolution: "helmet@npm:6.0.1" + checksum: 0eec311e1a3820e206226563e55d7115fa66e9a34922229472c1dac407c5d00f0d5c4f5f7afed714fba40287aac1c30dcbfa9b3c096f1146f9d6a55c4527d65d + languageName: node + linkType: hard + +"helmet@npm:^6.0.1": + version: 6.2.0 + resolution: "helmet@npm:6.2.0" + checksum: f112fcd0d8494e6c8ad10e9307e182f1be9c9c4917a3f9a3718c13ae120d4c4e1f251e735297d6a9266e068dcc0463ab101c8d7f2b809c0ceabcef4681f81a2a + languageName: node + linkType: hard + "hexoid@npm:^1.0.0": version: 1.0.0 resolution: "hexoid@npm:1.0.0" @@ -11043,7 +11165,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:2.0.0": +"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": version: 2.0.0 resolution: "http-errors@npm:2.0.0" dependencies: @@ -11501,7 +11623,7 @@ __metadata: languageName: node linkType: hard -"ioredis@npm:5.3.2, ioredis@npm:^5.0.0": +"ioredis@npm:5.3.2, ioredis@npm:^5.0.0, ioredis@npm:^5.2.3": version: 5.3.2 resolution: "ioredis@npm:5.3.2" dependencies: @@ -13474,7 +13596,7 @@ __metadata: languageName: node linkType: hard -"koa-body@npm:^6.0.1": +"koa-body@npm:*, koa-body@npm:^6.0.1": version: 6.0.1 resolution: "koa-body@npm:6.0.1" dependencies: @@ -13525,6 +13647,15 @@ __metadata: languageName: node linkType: hard +"koa-helmet@npm:^7.0.2": + version: 7.0.2 + resolution: "koa-helmet@npm:7.0.2" + dependencies: + helmet: "npm:^6.0.1" + checksum: a2f8f72f053402fd8b70deb219ab1535cb6dbc8975ae9bd84d2a976a88a2349f47289485d5db718f21c4d493a9555e8b4bb962119f47d2b0ff95877edc232004 + languageName: node + linkType: hard + "koa-json-body@npm:5.3.0": version: 5.3.0 resolution: "koa-json-body@npm:5.3.0" @@ -13556,6 +13687,17 @@ __metadata: languageName: node linkType: hard +"koa-ratelimit@npm:^5.1.0": + version: 5.1.0 + resolution: "koa-ratelimit@npm:5.1.0" + dependencies: + async-ratelimiter: "npm:^1.3.0" + debug: "npm:^4.1.1" + ms: "npm:^2.1.2" + checksum: 09c718b72750c590eef55b59236457e6c9cfdbe7e5b07a955549e421f08ba92c41c3c32e538700dc932c8be628814ec6a4cac9e79f53a3243fda92915412c838 + languageName: node + linkType: hard + "koa-remove-trailing-slashes@npm:2.0.3": version: 2.0.3 resolution: "koa-remove-trailing-slashes@npm:2.0.3" @@ -13647,6 +13789,19 @@ __metadata: languageName: node linkType: hard +"koa2-swagger-ui@npm:^5.9.1": + version: 5.10.0 + resolution: "koa2-swagger-ui@npm:5.10.0" + dependencies: + handlebars: "npm:^4.7.8" + lodash: "npm:^4.17.21" + read-pkg-up: "npm:7.0.1" + peerDependencies: + "@types/koa": "*" + checksum: 40575d377ddb264ec6f2d19290d02dd52cd9e6d392437b2fdfa208975559c6ebdf4b0938f46ded32aea0bb91263f7a7676df49d191d7e07a1cc0ce5ab1e34fe0 + languageName: node + linkType: hard + "koa@npm:2.13.4": version: 2.13.4 resolution: "koa@npm:2.13.4" @@ -13678,7 +13833,7 @@ __metadata: languageName: node linkType: hard -"koa@npm:2.14.2, koa@npm:^2.13.1": +"koa@npm:2.14.2, koa@npm:^2.13.1, koa@npm:^2.14.2": version: 2.14.2 resolution: "koa@npm:2.14.2" dependencies: @@ -13793,6 +13948,13 @@ __metadata: languageName: node linkType: hard +"libphonenumber-js@npm:^1.10.14": + version: 1.10.51 + resolution: "libphonenumber-js@npm:1.10.51" + checksum: 925fda2ecba5d1e655f9d6e43cd15b2d41f7fe9db38828f4159442bc263ae959a26f212223336831c6f64e58b82e91477f7ac814d66b48c80e5852529545032b + languageName: node + linkType: hard + "liftoff@npm:^3.1.0": version: 3.1.0 resolution: "liftoff@npm:3.1.0" @@ -14796,7 +14958,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.2": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -15555,6 +15717,13 @@ __metadata: languageName: node linkType: hard +"openapi-types@npm:^12.1.3": + version: 12.1.3 + resolution: "openapi-types@npm:12.1.3" + checksum: 9d1d7ed848622b63d0a4c3f881689161b99427133054e46b8e3241e137f1c78bb0031c5d80b420ee79ac2e91d2e727ffd6fc13c553d1b0488ddc8ad389dcbef8 + languageName: node + linkType: hard + "opencollective-postinstall@npm:^2.0.2": version: 2.0.3 resolution: "opencollective-postinstall@npm:2.0.3" @@ -15995,7 +16164,7 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^6.1.0": +"path-to-regexp@npm:^6.1.0, path-to-regexp@npm:^6.2.1": version: 6.2.1 resolution: "path-to-regexp@npm:6.2.1" checksum: 1e266be712d1a08086ee77beab12a1804842ec635dfed44f9ee1ba960a0e01cec8063fb8c92561115cdc0ce73158cdc7766e353ffa039340b4a85b370084c4d4 @@ -17408,17 +17577,7 @@ __metadata: languageName: node linkType: hard -"read-pkg-up@npm:^1.0.1": - version: 1.0.1 - resolution: "read-pkg-up@npm:1.0.1" - dependencies: - find-up: "npm:^1.0.0" - read-pkg: "npm:^1.0.0" - checksum: d18399a0f46e2da32beb2f041edd0cda49d2f2cc30195a05c759ef3ed9b5e6e19ba1ad1bae2362bdec8c6a9f2c3d18f4d5e8c369e808b03d498d5781cb9122c7 - languageName: node - linkType: hard - -"read-pkg-up@npm:^7.0.0, read-pkg-up@npm:^7.0.1": +"read-pkg-up@npm:7.0.1, read-pkg-up@npm:^7.0.0, read-pkg-up@npm:^7.0.1": version: 7.0.1 resolution: "read-pkg-up@npm:7.0.1" dependencies: @@ -17429,6 +17588,16 @@ __metadata: languageName: node linkType: hard +"read-pkg-up@npm:^1.0.1": + version: 1.0.1 + resolution: "read-pkg-up@npm:1.0.1" + dependencies: + find-up: "npm:^1.0.0" + read-pkg: "npm:^1.0.0" + checksum: d18399a0f46e2da32beb2f041edd0cda49d2f2cc30195a05c759ef3ed9b5e6e19ba1ad1bae2362bdec8c6a9f2c3d18f4d5e8c369e808b03d498d5781cb9122c7 + languageName: node + linkType: hard + "read-pkg@npm:^1.0.0": version: 1.1.0 resolution: "read-pkg@npm:1.1.0" @@ -17617,7 +17786,7 @@ __metadata: languageName: node linkType: hard -"reflect-metadata@npm:0.1.13, reflect-metadata@npm:^0.1.13": +"reflect-metadata@npm:*, reflect-metadata@npm:0.1.13, reflect-metadata@npm:^0.1.13": version: 0.1.13 resolution: "reflect-metadata@npm:0.1.13" checksum: 732570da35d2d96f8fdd5aac60fb263aa92f6512eaded5962b052bd9e90f22a9dec5aaf0d7ff4bfe97646c9530e8444e8435c2d80b24d0bdf938b5d47f6f5b83 @@ -20398,6 +20567,15 @@ __metadata: languageName: node linkType: hard +"uglify-js@npm:^3.1.4": + version: 3.17.4 + resolution: "uglify-js@npm:3.17.4" + bin: + uglifyjs: bin/uglifyjs + checksum: 4c0b800e0ff192079d2c3ce8414fd3b656a570028c7c79af5c29c53d5c532b68bbcae4ad47307f89c2ee124d11826fff7a136b59d5c5bb18422bcdf5568afe1e + languageName: node + linkType: hard + "ulid@npm:2.3.0": version: 2.3.0 resolution: "ulid@npm:2.3.0" @@ -21438,6 +21616,13 @@ __metadata: languageName: node linkType: hard +"wordwrap@npm:^1.0.0": + version: 1.0.0 + resolution: "wordwrap@npm:1.0.0" + checksum: 497d40beb2bdb08e6d38754faa17ce20b0bf1306327f80cb777927edb23f461ee1f6bc659b3c3c93f26b08e1cf4b46acc5bae8fda1f0be3b5ab9a1a0211034cd + languageName: node + linkType: hard + "workerpool@npm:6.2.1": version: 6.2.1 resolution: "workerpool@npm:6.2.1"