From 091505b900608b45c338efd3e7b6f6e627e6a506 Mon Sep 17 00:00:00 2001 From: Atridad Lahiji Date: Tue, 1 Aug 2023 00:31:48 -0600 Subject: [PATCH] tRPC overhaul! --- package.json | 2 + pnpm-lock.yaml | 273 +++++++++++++++++- src/pages/api/[...trpc].ts | 19 ++ src/pages/api/openapi.json.ts | 10 + .../api/trpc/{[trpc].ts => [...trpc].ts} | 1 - src/server/api/routers/room.ts | 19 +- src/server/api/routers/session.ts | 10 +- src/server/api/routers/user.ts | 21 +- src/server/api/routers/vote.ts | 34 ++- src/server/api/trpc.ts | 52 +++- src/server/openapi.ts | 12 + 11 files changed, 404 insertions(+), 49 deletions(-) create mode 100644 src/pages/api/[...trpc].ts create mode 100644 src/pages/api/openapi.json.ts rename src/pages/api/trpc/{[trpc].ts => [...trpc].ts} (99%) create mode 100644 src/server/openapi.ts diff --git a/package.json b/package.json index 9e51ea6..75d4380 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "json2csv": "6.0.0-alpha.2", "next": "^13.4.12", "next-auth": "^4.22.3", + "nextjs-cors": "^2.1.2", "postcss": "^8.4.27", "react": "18.2.0", "react-dom": "18.2.0", @@ -36,6 +37,7 @@ "resend": "^0.17.2", "sharp": "^0.32.4", "superjson": "1.13.1", + "trpc-openapi": "^1.2.0", "zod": "^3.21.4" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e518d24..b2815ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,9 @@ dependencies: next-auth: specifier: ^4.22.3 version: 4.22.3(next@13.4.12)(react-dom@18.2.0)(react@18.2.0) + nextjs-cors: + specifier: ^2.1.2 + version: 2.1.2(next@13.4.12) postcss: specifier: ^8.4.27 version: 8.4.27 @@ -77,6 +80,9 @@ dependencies: superjson: specifier: 1.13.1 version: 1.13.1 + trpc-openapi: + specifier: ^1.2.0 + version: 1.2.0(@trpc/server@10.36.0)(zod@3.21.4) zod: specifier: ^3.21.4 version: 3.21.4 @@ -1394,6 +1400,14 @@ packages: - utf-8-validate dev: false + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + /acorn-jsx@5.3.2(acorn@8.10.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1733,6 +1747,11 @@ packages: streamsearch: 1.1.0 dev: false + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false + /cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} engines: {node: '>=10.6.0'} @@ -1756,7 +1775,6 @@ packages: dependencies: function-bind: 1.1.1 get-intrinsic: 1.2.1 - dev: true /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} @@ -1838,6 +1856,15 @@ packages: engines: {node: '>=0.8'} dev: false + /co-body@6.1.0: + resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==} + dependencies: + inflation: 2.0.0 + qs: 6.11.2 + raw-body: 2.5.2 + type-is: 1.6.18 + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -1927,6 +1954,17 @@ packages: proto-list: 1.2.4 dev: false + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /cookie-es@1.0.0: + resolution: {integrity: sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ==} + dev: false + /cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} @@ -1943,6 +1981,14 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: false + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2074,11 +2120,25 @@ packages: resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} dev: false + /defu@6.1.2: + resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==} + dev: false + /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} dev: false + /depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + dev: false + + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + /deprecation@2.3.1: resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} dev: false @@ -2088,6 +2148,10 @@ packages: engines: {node: '>=6'} dev: true + /destr@2.0.0: + resolution: {integrity: sha512-FJ9RDpf3GicEBvzI3jxc2XhHzbqD8p4ANw/1kPsFBfTvP1b7Gn/Lg1vO7R9J4IVgoMbyUmFrFGZafJ1hPZpvlg==} + dev: false + /detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} @@ -2795,6 +2859,11 @@ packages: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: false + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: false + /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} dev: false @@ -2861,7 +2930,6 @@ packages: has: 1.0.3 has-proto: 1.0.1 has-symbols: 1.0.3 - dev: true /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} @@ -3028,6 +3096,18 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /h3@1.7.1: + resolution: {integrity: sha512-A9V2NEDNHet7v1gCg7CMwerSigLi0SRbhTy7C3lGb0N4YKIpPmLDjedTUopqp4dnn7COHfqUjjaz3zbtz4QduA==} + dependencies: + cookie-es: 1.0.0 + defu: 6.1.2 + destr: 2.0.0 + iron-webcrypto: 0.7.1 + radix3: 1.0.1 + ufo: 1.2.0 + uncrypto: 0.1.3 + dev: false + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -3050,12 +3130,10 @@ packages: /has-proto@1.0.1: resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} engines: {node: '>= 0.4'} - dev: true /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - dev: true /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} @@ -3126,6 +3204,17 @@ packages: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} dev: false + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + /http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} engines: {node: '>=10.19.0'} @@ -3143,6 +3232,13 @@ packages: engines: {node: '>=14.18.0'} dev: true + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false @@ -3164,6 +3260,11 @@ packages: engines: {node: '>=0.8.19'} dev: true + /inflation@2.0.0: + resolution: {integrity: sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw==} + engines: {node: '>= 0.8.0'} + dev: false + /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -3195,6 +3296,10 @@ packages: engines: {node: '>= 0.10'} dev: false + /iron-webcrypto@0.7.1: + resolution: {integrity: sha512-K/UmlEhPCPXEHV5hAtH5C0tI5JnFuOrv4yO/j7ODPl3HaiiHBLbOLTde+ieUaAyfCATe4LoAnclyF+hmSCOVmQ==} + dev: false + /is-array-buffer@3.0.2: resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} dependencies: @@ -3569,6 +3674,10 @@ packages: p-locate: 5.0.0 dev: true + /lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + dev: false + /lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} dev: false @@ -3602,6 +3711,15 @@ packages: dependencies: yallist: 4.0.0 + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: false + + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: false + /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -3609,6 +3727,11 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -3628,6 +3751,12 @@ packages: mime-db: 1.52.0 dev: false + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: false + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -3713,6 +3842,11 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + dev: false + /next-auth@4.22.3(next@13.4.12)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-XAgy9xV3J2eJOXrQhmxdjV6MLM29ibm6WtMXc3KY6IPZeApf+SuBuPvlqCUfbu5YsAzlg9WSw6u01dChTfeZOA==} peerDependencies: @@ -3781,6 +3915,15 @@ packages: - babel-plugin-macros dev: false + /nextjs-cors@2.1.2(next@13.4.12): + resolution: {integrity: sha512-2yOVivaaf2ILe4f/qY32hnj3oC77VCOsUQJQfhVMGsXE/YMEWUY2zy78sH9FKUCM7eG42/l3pDofIzMD781XGA==} + peerDependencies: + next: ^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0 + dependencies: + cors: 2.8.5 + next: 13.4.12(react-dom@18.2.0)(react@18.2.0) + dev: false + /node-abi@3.45.0: resolution: {integrity: sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==} engines: {node: '>=10'} @@ -3804,6 +3947,22 @@ packages: whatwg-url: 5.0.0 dev: false + /node-mocks-http@1.12.2: + resolution: {integrity: sha512-xhWwC0dh35R9rf0j3bRZXuISXdHxxtMx0ywZQBwjrg3yl7KpRETzogfeCamUIjltpn0Fxvs/ZhGJul1vPLrdJQ==} + engines: {node: '>=0.6'} + dependencies: + accepts: 1.3.8 + content-disposition: 0.5.4 + depd: 1.1.2 + fresh: 0.5.2 + merge-descriptors: 1.0.1 + methods: 1.1.2 + mime: 1.6.0 + parseurl: 1.3.3 + range-parser: 1.2.1 + type-is: 1.6.18 + dev: false + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: false @@ -3863,7 +4022,6 @@ packages: /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: true /object-hash@2.2.0: resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} @@ -3876,7 +4034,6 @@ packages: /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - dev: true /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} @@ -3969,6 +4126,10 @@ packages: is-wsl: 2.2.0 dev: true + /openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + dev: false + /openid-client@5.4.3: resolution: {integrity: sha512-sVQOvjsT/sbSfYsQI/9liWQGVZH/Pp3rrtlGEwgk/bbHfrUDZ24DN57lAagIwFtuEu+FM9Ev7r85s8S/yPjimQ==} dependencies: @@ -4067,6 +4228,11 @@ packages: peberminta: 0.8.0 dev: false + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4369,6 +4535,13 @@ packages: engines: {node: '>=6'} dev: true + /qs@6.11.2: + resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4381,6 +4554,25 @@ packages: engines: {node: '>=10'} dev: false + /radix3@1.0.1: + resolution: {integrity: sha512-y+AcwZ3HcUIGc9zGsNVf5+BY/LxL+z+4h4J3/pp8jxSmy1STaCocPS3qrj4tA5ehUSzqtqK+0Aygvz/r/8vy4g==} + dev: false + + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: false + + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + /rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -4647,6 +4839,10 @@ packages: is-regex: 1.1.4 dev: true + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + dev: false + /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -4680,6 +4876,10 @@ packages: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} dev: false + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + /sharp@0.32.4: resolution: {integrity: sha512-exUnZewqVZC6UXqXuQ8fyJJv0M968feBi04jb9GcUHrWtkRoAKnbJt8IfwT4NJs7FskArbJ14JAFGVuooszoGg==} engines: {node: '>=14.15.0'} @@ -4721,7 +4921,6 @@ packages: call-bind: 1.0.2 get-intrinsic: 1.2.1 object-inspect: 1.12.3 - dev: true /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4783,6 +4982,11 @@ packages: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: false + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -5087,6 +5291,11 @@ packages: resolution: {integrity: sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==} dev: false + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false @@ -5104,6 +5313,22 @@ packages: pretty-bytes: 5.6.0 dev: false + /trpc-openapi@1.2.0(@trpc/server@10.36.0)(zod@3.21.4): + resolution: {integrity: sha512-pfYoCd/3KYXWXvUPZBKJw455OOwngKN/6SIcj7Yit19OMLJ+8yVZkEvGEeg5wUSwfsiTdRsKuvqkRPXVSwV7ew==} + peerDependencies: + '@trpc/server': ^10.0.0 + zod: ^3.14.4 + dependencies: + '@trpc/server': 10.36.0 + co-body: 6.1.0 + h3: 1.7.1 + lodash.clonedeep: 4.5.0 + node-mocks-http: 1.12.2 + openapi-types: 12.1.3 + zod: 3.21.4 + zod-to-json-schema: 3.21.4(zod@3.21.4) + dev: false + /ts-api-utils@1.0.1(typescript@5.1.6): resolution: {integrity: sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==} engines: {node: '>=16.13.0'} @@ -5182,6 +5407,14 @@ packages: engines: {node: '>=14.16'} dev: false + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + /typed-array-buffer@1.0.0: resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} engines: {node: '>= 0.4'} @@ -5226,6 +5459,10 @@ packages: hasBin: true dev: true + /ufo@1.2.0: + resolution: {integrity: sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg==} + dev: false + /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} dependencies: @@ -5235,6 +5472,10 @@ packages: which-boxed-primitive: 1.0.2 dev: true + /uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + dev: false + /universal-user-agent@6.0.0: resolution: {integrity: sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==} dev: false @@ -5249,6 +5490,11 @@ packages: engines: {node: '>= 10.0.0'} dev: false + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: false + /untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -5309,6 +5555,11 @@ packages: spdx-expression-parse: 3.0.1 dev: false + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false + /watchpack@2.4.0: resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} engines: {node: '>=10.13.0'} @@ -5406,6 +5657,14 @@ packages: engines: {node: '>=10'} dev: true + /zod-to-json-schema@3.21.4(zod@3.21.4): + resolution: {integrity: sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==} + peerDependencies: + zod: ^3.21.4 + dependencies: + zod: 3.21.4 + dev: false + /zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} dev: false diff --git a/src/pages/api/[...trpc].ts b/src/pages/api/[...trpc].ts new file mode 100644 index 0000000..edb5b1a --- /dev/null +++ b/src/pages/api/[...trpc].ts @@ -0,0 +1,19 @@ +import { NextApiRequest, NextApiResponse } from "next"; +import { createOpenApiNextHandler } from "trpc-openapi"; +import cors from "nextjs-cors"; + +import { appRouter } from "~/server/api/root"; +import { createTRPCContext } from "~/server/api/trpc"; + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + // Setup CORS + cors(req, res); + + // Handle incoming OpenAPI requests + return createOpenApiNextHandler({ + router: appRouter, + createContext: createTRPCContext, + })(req, res); +}; + +export default handler; diff --git a/src/pages/api/openapi.json.ts b/src/pages/api/openapi.json.ts new file mode 100644 index 0000000..0e19e7e --- /dev/null +++ b/src/pages/api/openapi.json.ts @@ -0,0 +1,10 @@ +import { NextApiRequest, NextApiResponse } from 'next'; + +import { openApiDocument } from '../../server/openapi'; + +// Respond with our OpenAPI schema +const handler = (req: NextApiRequest, res: NextApiResponse) => { + res.status(200).send(openApiDocument); +}; + +export default handler; \ No newline at end of file diff --git a/src/pages/api/trpc/[trpc].ts b/src/pages/api/trpc/[...trpc].ts similarity index 99% rename from src/pages/api/trpc/[trpc].ts rename to src/pages/api/trpc/[...trpc].ts index 43deb52..630df52 100644 --- a/src/pages/api/trpc/[trpc].ts +++ b/src/pages/api/trpc/[...trpc].ts @@ -1,5 +1,4 @@ import { createNextApiHandler } from "@trpc/server/adapters/next"; - import { env } from "~/env.mjs"; import { appRouter } from "~/server/api/root"; import { createTRPCContext } from "~/server/api/trpc"; diff --git a/src/server/api/routers/room.ts b/src/server/api/routers/room.ts index abbfa9a..db79023 100644 --- a/src/server/api/routers/room.ts +++ b/src/server/api/routers/room.ts @@ -1,12 +1,17 @@ import { z } from "zod"; import { publishToChannel } from "~/server/ably"; -import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; +import { + adminRateLimitedProcedure, + createTRPCRouter, + protectedProcedure, + protectedRateLimitedProcedure, +} from "~/server/api/trpc"; import { fetchCache, invalidateCache, setCache } from "~/server/redis"; export const roomRouter = createTRPCRouter({ // Create - create: protectedProcedure + create: protectedRateLimitedProcedure .input( z.object({ name: z.string(), @@ -42,7 +47,7 @@ export const roomRouter = createTRPCRouter({ }), // Get One - get: protectedProcedure + get: protectedRateLimitedProcedure .input(z.object({ id: z.string() })) .query(({ ctx, input }) => { return ctx.prisma.room.findUnique({ @@ -63,7 +68,7 @@ export const roomRouter = createTRPCRouter({ }), // Get All - getAll: protectedProcedure.query(async ({ ctx }) => { + getAll: protectedRateLimitedProcedure.query(async ({ ctx }) => { const cachedResult = await fetchCache< { id: string; @@ -92,7 +97,7 @@ export const roomRouter = createTRPCRouter({ } }), - countAll: protectedProcedure.query(async ({ ctx }) => { + countAll: adminRateLimitedProcedure.query(async ({ ctx }) => { const cachedResult = await fetchCache(`kv_roomcount_admin`); if (cachedResult) { @@ -107,7 +112,7 @@ export const roomRouter = createTRPCRouter({ }), // Update One - set: protectedProcedure + set: protectedRateLimitedProcedure .input( z.object({ name: z.string(), @@ -208,7 +213,7 @@ export const roomRouter = createTRPCRouter({ }), // Delete One - delete: protectedProcedure + delete: protectedRateLimitedProcedure .input(z.object({ id: z.string() })) .mutation(async ({ ctx, input }) => { const deletedRoom = await ctx.prisma.room.delete({ diff --git a/src/server/api/routers/session.ts b/src/server/api/routers/session.ts index 4b302a3..87354fb 100644 --- a/src/server/api/routers/session.ts +++ b/src/server/api/routers/session.ts @@ -1,9 +1,13 @@ import { z } from "zod"; -import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; +import { + adminRateLimitedProcedure, + createTRPCRouter, + protectedProcedure, +} from "~/server/api/trpc"; import { invalidateCache } from "~/server/redis"; export const sessionRouter = createTRPCRouter({ - deleteAllByUserId: protectedProcedure + deleteAllByUserId: adminRateLimitedProcedure .input( z.object({ userId: z.string(), @@ -22,7 +26,7 @@ export const sessionRouter = createTRPCRouter({ return !!sessions; }), - deleteAll: protectedProcedure.mutation(async ({ ctx }) => { + deleteAll: adminRateLimitedProcedure.mutation(async ({ ctx }) => { const sessions = await ctx.prisma.session.deleteMany(); if (!!sessions) { diff --git a/src/server/api/routers/user.ts b/src/server/api/routers/user.ts index 0d92fe9..a5bfb7a 100644 --- a/src/server/api/routers/user.ts +++ b/src/server/api/routers/user.ts @@ -3,14 +3,19 @@ import { Resend } from "resend"; import { z } from "zod"; import { Goodbye } from "~/components/templates/Goodbye"; import { env } from "~/env.mjs"; -import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; +import { + adminRateLimitedProcedure, + createTRPCRouter, + protectedProcedure, + protectedRateLimitedProcedure, +} from "~/server/api/trpc"; import { fetchCache, invalidateCache, setCache } from "~/server/redis"; const resend = new Resend(process.env.RESEND_API_KEY); export const userRouter = createTRPCRouter({ - countAll: protectedProcedure.query(async ({ ctx }) => { + countAll: adminRateLimitedProcedure.query(async ({ ctx }) => { const cachedResult = await fetchCache(`kv_usercount_admin`); if (cachedResult) { @@ -24,7 +29,7 @@ export const userRouter = createTRPCRouter({ } }), - getProviders: protectedProcedure.query(async ({ ctx }) => { + getProviders: protectedRateLimitedProcedure.query(async ({ ctx }) => { const providers = await ctx.prisma.user.findUnique({ where: { id: ctx.session.user.id, @@ -42,7 +47,7 @@ export const userRouter = createTRPCRouter({ return account.provider; }); }), - getAll: protectedProcedure.query(async ({ ctx }) => { + getAll: protectedRateLimitedProcedure.query(async ({ ctx }) => { const cachedResult = await fetchCache< { accounts: { @@ -94,7 +99,7 @@ export const userRouter = createTRPCRouter({ return users; } }), - delete: protectedProcedure + delete: protectedRateLimitedProcedure .input( z .object({ @@ -132,7 +137,7 @@ export const userRouter = createTRPCRouter({ return !!user; }), - save: protectedProcedure + save: protectedRateLimitedProcedure .input( z.object({ name: z.string(), @@ -150,7 +155,7 @@ export const userRouter = createTRPCRouter({ return !!user; }), - setAdmin: protectedProcedure + setAdmin: adminRateLimitedProcedure .input( z.object({ userId: z.string(), @@ -172,7 +177,7 @@ export const userRouter = createTRPCRouter({ return !!user; }), - setVIP: protectedProcedure + setVIP: adminRateLimitedProcedure .input( z.object({ userId: z.string(), diff --git a/src/server/api/routers/vote.ts b/src/server/api/routers/vote.ts index 388d1f9..3a577df 100644 --- a/src/server/api/routers/vote.ts +++ b/src/server/api/routers/vote.ts @@ -2,24 +2,32 @@ import { z } from "zod"; import { publishToChannel } from "~/server/ably"; import type { Room } from "@prisma/client"; -import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; +import { + adminRateLimitedProcedure, + createTRPCRouter, + protectedRateLimitedProcedure, +} from "~/server/api/trpc"; import { fetchCache, invalidateCache, setCache } from "~/server/redis"; export const voteRouter = createTRPCRouter({ - countAll: protectedProcedure.query(async ({ ctx }) => { - const cachedResult = await fetchCache(`kv_votecount_admin`); + countAll: adminRateLimitedProcedure + .input(z.void()) + .output(z.number()) + .meta({ openapi: { method: "GET", path: "/votes/count" } }) + .query(async ({ ctx }) => { + const cachedResult = await fetchCache(`kv_votecount_admin`); - if (cachedResult) { - return cachedResult; - } else { - const votesCount = await ctx.prisma.vote.count(); + if (cachedResult) { + return cachedResult; + } else { + const votesCount = await ctx.prisma.vote.count(); - await setCache(`kv_votecount_admin`, votesCount); + await setCache(`kv_votecount_admin`, votesCount); - return votesCount; - } - }), - getAllByRoomId: protectedProcedure + return votesCount; + } + }), + getAllByRoomId: protectedRateLimitedProcedure .input(z.object({ roomId: z.string() })) .query(async ({ ctx, input }) => { const cachedResult = await fetchCache< @@ -63,7 +71,7 @@ export const voteRouter = createTRPCRouter({ return votesByRoomId; } }), - set: protectedProcedure + set: protectedRateLimitedProcedure .input(z.object({ value: z.string(), roomId: z.string() })) .mutation(async ({ ctx, input }) => { const vote = await ctx.prisma.vote.upsert({ diff --git a/src/server/api/trpc.ts b/src/server/api/trpc.ts index 79a7f16..d5be428 100644 --- a/src/server/api/trpc.ts +++ b/src/server/api/trpc.ts @@ -22,6 +22,7 @@ import { prisma } from "~/server/db"; type CreateContextOptions = { session: Session | null; + ip: string | undefined; }; /** @@ -37,6 +38,7 @@ type CreateContextOptions = { const createInnerTRPCContext = (opts: CreateContextOptions) => { return { session: opts.session, + ip: opts.ip, prisma, }; }; @@ -54,6 +56,7 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => { const session = await getServerAuthSession({ req, res }); return createInnerTRPCContext({ + ip: req.socket.remoteAddress, session, }); }; @@ -64,17 +67,21 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => { * This is where the tRPC API is initialized, connecting the context and transformer. */ import { initTRPC, TRPCError } from "@trpc/server"; +import { OpenApiMeta } from "trpc-openapi"; import { Ratelimit } from "@upstash/ratelimit"; import superjson from "superjson"; import { env } from "~/env.mjs"; import { Redis } from "@upstash/redis"; -const t = initTRPC.context().create({ - transformer: superjson, - errorFormatter({ shape }) { - return shape; - }, -}); +const t = initTRPC + .meta() + .context() + .create({ + transformer: superjson, + errorFormatter({ shape }) { + return shape; + }, + }); /** * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) @@ -100,12 +107,20 @@ export const createTRPCRouter = t.router; export const publicProcedure = t.procedure; /** Reusable middleware that enforces users are logged in before running the procedure. */ -const enforceRouteProtection = t.middleware(async ({ ctx, next }) => { +const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => { // Auth if (!ctx.session || !ctx.session.user) { throw new TRPCError({ code: "UNAUTHORIZED" }); } + return next({ + ctx: { + session: { ...ctx.session, user: ctx.session.user }, + }, + }); +}); + +const enforceRateLimit = t.middleware(async ({ ctx, next }) => { const rateLimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow( @@ -114,13 +129,21 @@ const enforceRouteProtection = t.middleware(async ({ ctx, next }) => { ), analytics: true, }); - + console.log(`${env.APP_ENV}_${ctx.session?.user.id || ctx.ip}`); + console.log(ctx.ip); const { success } = await rateLimit.limit( - `${env.APP_ENV}_${ctx.session.user.id}` + `${env.APP_ENV}_${ctx.session?.user.id || ctx.ip}` ); if (!success) throw new TRPCError({ code: "TOO_MANY_REQUESTS" }); + return next(); +}); + +const enforceAdminRole = t.middleware(async ({ ctx, next }) => { + if (!ctx.session || !ctx.session.user || !ctx.session?.user.isAdmin) + throw new TRPCError({ code: "UNAUTHORIZED" }); + return next({ ctx: { session: { ...ctx.session, user: ctx.session.user }, @@ -136,4 +159,13 @@ const enforceRouteProtection = t.middleware(async ({ ctx, next }) => { * * @see https://trpc.io/docs/procedures */ -export const protectedProcedure = t.procedure.use(enforceRouteProtection); +export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); + +export const protectedRateLimitedProcedure = + protectedProcedure.use(enforceRateLimit); + +export const publicRateLimitedProcedure = publicProcedure.use(enforceRateLimit); + +export const adminProcedure = t.procedure.use(enforceAdminRole); + +export const adminRateLimitedProcedure = adminProcedure.use(enforceAdminRole); diff --git a/src/server/openapi.ts b/src/server/openapi.ts new file mode 100644 index 0000000..f0ee72f --- /dev/null +++ b/src/server/openapi.ts @@ -0,0 +1,12 @@ +import { generateOpenApiDocument } from 'trpc-openapi'; + +import { appRouter } from './api/root'; + +// Generate OpenAPI schema document +export const openApiDocument = generateOpenApiDocument(appRouter, { + title: 'Example CRUD API', + description: 'OpenAPI compliant REST API built using tRPC with Next.js', + version: '1.0.0', + baseUrl: 'http://localhost:3000/api', + docsUrl: 'https://github.com/jlalmes/trpc-openapi', +}); \ No newline at end of file