From 9b6a486f00a8f3cf13383e061c9178362145a81e Mon Sep 17 00:00:00 2001 From: Zach Manson Date: Fri, 30 Aug 2024 09:49:25 +0800 Subject: [PATCH] Add trpc --- package-lock.json | 242 +++++++++++++++++++++------ package.json | 12 +- src/context/Global/index.tsx | 2 +- src/pages/_app.tsx | 90 +++++----- src/pages/api/scoreboard/[userId].ts | 72 -------- src/pages/api/submitgame.ts | 121 -------------- src/pages/api/trpc/[trpc].ts | 9 + src/pages/index.tsx | 43 ++--- src/pages/stats/[userId].tsx | 32 ++-- src/pages/stats/index.tsx | 2 +- src/server/routers/_app.ts | 21 +++ src/server/routers/game.ts | 146 ++++++++++++++++ src/server/trpc.ts | 11 ++ src/{lib => utils}/prisma.ts | 0 src/{lib => utils}/recipe.ts | 0 src/utils/trpc.ts | 47 ++++++ tsconfig.json | 2 +- 17 files changed, 506 insertions(+), 346 deletions(-) delete mode 100644 src/pages/api/scoreboard/[userId].ts delete mode 100644 src/pages/api/submitgame.ts create mode 100644 src/pages/api/trpc/[trpc].ts create mode 100644 src/server/routers/_app.ts create mode 100644 src/server/routers/game.ts create mode 100644 src/server/trpc.ts rename src/{lib => utils}/prisma.ts (100%) rename src/{lib => utils}/recipe.ts (100%) create mode 100644 src/utils/trpc.ts diff --git a/package-lock.json b/package-lock.json index efb4b15..1a81ebc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,12 +11,18 @@ "dependencies": { "@headlessui/react": "^1.7.17", "@prisma/client": "^5.7.0", - "next": "14.1.1", + "@tanstack/react-query": "^5.52.3", + "@trpc/client": "^11.0.0-rc.490", + "@trpc/next": "^11.0.0-rc.490", + "@trpc/react-query": "^11.0.0-rc.490", + "@trpc/server": "^11.0.0-rc.490", + "next": "^14.2.7", + "nuqs": "^1.17.8", "prisma": "^5.7.0", "react": "^18", "react-dom": "^18", "seedrandom": "^3.0.5", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^20", @@ -253,9 +259,10 @@ } }, "node_modules/@next/env": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.1.tgz", - "integrity": "sha512-7CnQyD5G8shHxQIIg3c7/pSeYFeMhsNbpU/bmvH7ZnDql7mNRgg8O2JZrhrc/soFnfBnKP4/xXNiiSIPn2w8gA==" + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.7.tgz", + "integrity": "sha512-OTx9y6I3xE/eih+qtthppwLytmpJVPM5PPoJxChFsbjIEFXIayG0h/xLzefHGJviAa3Q5+Fd+9uYojKkHDKxoQ==", + "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { "version": "14.0.4", @@ -267,12 +274,13 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.1.1.tgz", - "integrity": "sha512-yDjSFKQKTIjyT7cFv+DqQfW5jsD+tVxXTckSe1KIouKk75t1qZmj/mV3wzdmFb0XHVGtyRjDMulfVG8uCKemOQ==", + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.7.tgz", + "integrity": "sha512-UhZGcOyI9LE/tZL3h9rs/2wMZaaJKwnpAyegUVDGZqwsla6hMfeSj9ssBWQS9yA4UXun3pPhrFLVnw5KXZs3vw==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -282,12 +290,13 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.1.tgz", - "integrity": "sha512-KCQmBL0CmFmN8D64FHIZVD9I4ugQsDBBEJKiblXGgwn7wBCSe8N4Dx47sdzl4JAg39IkSN5NNrr8AniXLMb3aw==", + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.7.tgz", + "integrity": "sha512-ys2cUgZYRc+CbyDeLAaAdZgS7N1Kpyy+wo0b/gAj+SeOeaj0Lw/q+G1hp+DuDiDAVyxLBCJXEY/AkhDmtihUTA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -297,12 +306,13 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.1.tgz", - "integrity": "sha512-YDQfbWyW0JMKhJf/T4eyFr4b3tceTorQ5w2n7I0mNVTFOvu6CGEzfwT3RSAQGTi/FFMTFcuspPec/7dFHuP7Eg==", + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.7.tgz", + "integrity": "sha512-2xoWtE13sUJ3qrC1lwE/HjbDPm+kBQYFkkiVECJWctRASAHQ+NwjMzgrfqqMYHfMxFb5Wws3w9PqzZJqKFdWcQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -312,12 +322,13 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.1.tgz", - "integrity": "sha512-fiuN/OG6sNGRN/bRFxRvV5LyzLB8gaL8cbDH5o3mEiVwfcMzyE5T//ilMmaTrnA8HLMS6hoz4cHOu6Qcp9vxgQ==", + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.7.tgz", + "integrity": "sha512-+zJ1gJdl35BSAGpkCbfyiY6iRTaPrt3KTl4SF/B1NyELkqqnrNX6cp4IjjjxKpd64/7enI0kf6b9O1Uf3cL0pw==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -327,12 +338,13 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.1.tgz", - "integrity": "sha512-rv6AAdEXoezjbdfp3ouMuVqeLjE1Bin0AuE6qxE6V9g3Giz5/R3xpocHoAi7CufRR+lnkuUjRBn05SYJ83oKNQ==", + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.7.tgz", + "integrity": "sha512-m6EBqrskeMUzykBrv0fDX/28lWIBGhMzOYaStp0ihkjzIYJiKUOzVYD1gULHc8XDf5EMSqoH/0/TRAgXqpQwmw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -342,12 +354,13 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.1.tgz", - "integrity": "sha512-YAZLGsaNeChSrpz/G7MxO3TIBLaMN8QWMr3X8bt6rCvKovwU7GqQlDu99WdvF33kI8ZahvcdbFsy4jAFzFX7og==", + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.7.tgz", + "integrity": "sha512-gUu0viOMvMlzFRz1r1eQ7Ql4OE+hPOmA7smfZAhn8vC4+0swMZaZxa9CSIozTYavi+bJNDZ3tgiSdMjmMzRJlQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -357,12 +370,13 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.1.tgz", - "integrity": "sha512-1L4mUYPBMvVDMZg1inUYyPvFSduot0g73hgfD9CODgbr4xiTYe0VOMTZzaRqYJYBA9mana0x4eaAaypmWo1r5A==", + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.7.tgz", + "integrity": "sha512-PGbONHIVIuzWlYmLvuFKcj+8jXnLbx4WrlESYlVnEzDsa3+Q2hI1YHoXaSmbq0k4ZwZ7J6sWNV4UZfx1OeOlbQ==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -372,12 +386,13 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.1.tgz", - "integrity": "sha512-jvIE9tsuj9vpbbXlR5YxrghRfMuG0Qm/nZ/1KDHc+y6FpnZ/apsgh+G6t15vefU0zp3WSpTMIdXRUsNl/7RSuw==", + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.7.tgz", + "integrity": "sha512-BiSY5umlx9ed5RQDoHcdbuKTUkuFORDqzYKPHlLeS+STUWQKWziVOn3Ic41LuTBvqE0TRJPKpio9GSIblNR+0w==", "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -387,12 +402,13 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.1.tgz", - "integrity": "sha512-S6K6EHDU5+1KrBDLko7/c1MNy/Ya73pIAmvKeFwsF4RmBFJSO7/7YeD4FnZ4iBdzE69PpQ4sOMU9ORKeNuxe8A==", + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.7.tgz", + "integrity": "sha512-pxsI23gKWRt/SPHFkDEsP+w+Nd7gK37Hpv0ngc5HpWy2e7cKx9zR/+Q2ptAUqICNTecAaGWvmhway7pj/JLEWA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -509,14 +525,48 @@ "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==", "dev": true }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, "node_modules/@swc/helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", - "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "license": "Apache-2.0", "dependencies": { + "@swc/counter": "^0.1.3", "tslib": "^2.4.0" } }, + "node_modules/@tanstack/query-core": { + "version": "5.52.3", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.52.3.tgz", + "integrity": "sha512-+Gh7lXn+eoAsarvvnndgqBeJ5lOjup8qgQnrTsFuhNTEAo0H934DxEPro4s3TlmvITfDTJ3UDCy7kY8Azm0qsA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.52.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.52.3.tgz", + "integrity": "sha512-1K7l2hkqlWuh5SdaTYPSwMmHJF5dDk5INK+EtiEwUZW4+usWTXZx7QeHuk078oSzTzaVkEFyT3VquK7F0hYkUw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.52.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.5.0.tgz", @@ -542,6 +592,69 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@trpc/client": { + "version": "11.0.0-rc.490", + "resolved": "https://registry.npmjs.org/@trpc/client/-/client-11.0.0-rc.490.tgz", + "integrity": "sha512-rfAzHF27osuP36JWGzq9TgXMnNc5kk1oAIoMafVHkCu3XlEZH3Ym2BpucHBWp/b5JYrtvzM91jskzvE0yO3mYw==", + "funding": [ + "https://trpc.io/sponsor" + ], + "license": "MIT", + "peerDependencies": { + "@trpc/server": "11.0.0-rc.490+b6a851870" + } + }, + "node_modules/@trpc/next": { + "version": "11.0.0-rc.490", + "resolved": "https://registry.npmjs.org/@trpc/next/-/next-11.0.0-rc.490.tgz", + "integrity": "sha512-ie0adjNr78ZvuBCaRL5Yr3u98TUo61ASVPBOgdYNx6/S2XLEUpL7nhY0VHNjSrnh3Ng8IzpdKM4uJsqHvnCFVg==", + "funding": [ + "https://trpc.io/sponsor" + ], + "license": "MIT", + "peerDependencies": { + "@tanstack/react-query": "^5.49.2", + "@trpc/client": "11.0.0-rc.490+b6a851870", + "@trpc/react-query": "11.0.0-rc.490+b6a851870", + "@trpc/server": "11.0.0-rc.490+b6a851870", + "next": "*", + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@tanstack/react-query": { + "optional": true + }, + "@trpc/react-query": { + "optional": true + } + } + }, + "node_modules/@trpc/react-query": { + "version": "11.0.0-rc.490", + "resolved": "https://registry.npmjs.org/@trpc/react-query/-/react-query-11.0.0-rc.490.tgz", + "integrity": "sha512-sM1AlLA5JPjsTRZJmXieAcbxHQXx+TGg1WTpyCAlBAue9rXGu9FoydfYxhpVcSrSbzdZ5HRmg/cXnC77/O7nBg==", + "funding": [ + "https://trpc.io/sponsor" + ], + "license": "MIT", + "peerDependencies": { + "@tanstack/react-query": "^5.49.2", + "@trpc/client": "11.0.0-rc.490+b6a851870", + "@trpc/server": "11.0.0-rc.490+b6a851870", + "react": ">=18.2.0", + "react-dom": ">=18.2.0" + } + }, + "node_modules/@trpc/server": { + "version": "11.0.0-rc.490", + "resolved": "https://registry.npmjs.org/@trpc/server/-/server-11.0.0-rc.490.tgz", + "integrity": "sha512-JkKLvA0QIErqslsgU6uFvpv9xtxe78C59zqUdZFmUI0V9pIAJlxmODV4I9hTDcX8P+B30rP3z+q2+bPozN2MaQ==", + "funding": [ + "https://trpc.io/sponsor" + ], + "license": "MIT" + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -3247,6 +3360,12 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3288,12 +3407,13 @@ "dev": true }, "node_modules/next": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/next/-/next-14.1.1.tgz", - "integrity": "sha512-McrGJqlGSHeaz2yTRPkEucxQKe5Zq7uPwyeHNmJaZNY4wx9E9QdxmTp310agFRoMuIYgQrCrT3petg13fSVOww==", + "version": "14.2.7", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.7.tgz", + "integrity": "sha512-4Qy2aK0LwH4eQiSvQWyKuC7JXE13bIopEQesWE0c/P3uuNRnZCQanI0vsrMLmUQJLAto+A+/8+sve2hd+BQuOQ==", + "license": "MIT", "dependencies": { - "@next/env": "14.1.1", - "@swc/helpers": "0.5.2", + "@next/env": "14.2.7", + "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", @@ -3307,18 +3427,19 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.1.1", - "@next/swc-darwin-x64": "14.1.1", - "@next/swc-linux-arm64-gnu": "14.1.1", - "@next/swc-linux-arm64-musl": "14.1.1", - "@next/swc-linux-x64-gnu": "14.1.1", - "@next/swc-linux-x64-musl": "14.1.1", - "@next/swc-win32-arm64-msvc": "14.1.1", - "@next/swc-win32-ia32-msvc": "14.1.1", - "@next/swc-win32-x64-msvc": "14.1.1" + "@next/swc-darwin-arm64": "14.2.7", + "@next/swc-darwin-x64": "14.2.7", + "@next/swc-linux-arm64-gnu": "14.2.7", + "@next/swc-linux-arm64-musl": "14.2.7", + "@next/swc-linux-x64-gnu": "14.2.7", + "@next/swc-linux-x64-musl": "14.2.7", + "@next/swc-win32-arm64-msvc": "14.2.7", + "@next/swc-win32-ia32-msvc": "14.2.7", + "@next/swc-win32-x64-msvc": "14.2.7" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" @@ -3327,6 +3448,9 @@ "@opentelemetry/api": { "optional": true }, + "@playwright/test": { + "optional": true + }, "sass": { "optional": true } @@ -3383,6 +3507,18 @@ "node": ">=0.10.0" } }, + "node_modules/nuqs": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/nuqs/-/nuqs-1.17.8.tgz", + "integrity": "sha512-JqsnzO+hJyjJE7ebuhpHMLA2iGY48e2xr0oJQFhj7kjUmDABL2XOup47rxF5TL/5b9jEsmU2t0lAKin1VdK1/A==", + "license": "MIT", + "dependencies": { + "mitt": "^3.0.1" + }, + "peerDependencies": { + "next": ">=13.4 <14.0.2 || ^14.0.3" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -4663,9 +4799,10 @@ } }, "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", @@ -5074,6 +5211,7 @@ "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index b800890..16fff39 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", + "dev": "next dev --turbo", "build": "next build", "start": "next start", "lint": "next lint", @@ -14,12 +14,18 @@ "dependencies": { "@headlessui/react": "^1.7.17", "@prisma/client": "^5.7.0", - "next": "14.1.1", + "@tanstack/react-query": "^5.52.3", + "@trpc/client": "^11.0.0-rc.490", + "@trpc/next": "^11.0.0-rc.490", + "@trpc/react-query": "^11.0.0-rc.490", + "@trpc/server": "^11.0.0-rc.490", + "next": "^14.2.7", + "nuqs": "^1.17.8", "prisma": "^5.7.0", "react": "^18", "react-dom": "^18", "seedrandom": "^3.0.5", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^20", diff --git a/src/context/Global/index.tsx b/src/context/Global/index.tsx index 21e2c15..1ff6183 100644 --- a/src/context/Global/index.tsx +++ b/src/context/Global/index.tsx @@ -1,5 +1,5 @@ import { CACHE_VERSION, DEFAULT_OPTIONS, PUBLIC_DIR } from "@/constants"; -import { compareTables, getVariantsWithReflections } from "@/lib/recipe"; +import { compareTables, getVariantsWithReflections } from "@/utils/recipe"; import { ColorTable, GameState, diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 21adc96..cd0683c 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -4,50 +4,50 @@ import type { AppProps } from "next/app"; import Head from "next/head"; import Layout from "./layout"; -export default function App({ Component, pageProps }: AppProps) { - return ( - - - - Minecraftle - - - - - - - - - - - - - - - +import { trpc } from "../utils/trpc"; + +function App({ Component, pageProps }: AppProps) { + const layout = ( + + + Minecraftle + + + + + + + + + + + + + + ); + return {layout}; } + +export default trpc.withTRPC(App); diff --git a/src/pages/api/scoreboard/[userId].ts b/src/pages/api/scoreboard/[userId].ts deleted file mode 100644 index fb587eb..0000000 --- a/src/pages/api/scoreboard/[userId].ts +++ /dev/null @@ -1,72 +0,0 @@ -import prisma from "@/lib/prisma"; -import { GenericApiError, ScoreboardRow } from "@/types"; -import { Prisma } from "@prisma/client"; -import type { NextApiRequest, NextApiResponse } from "next"; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse<{ localLeaderboard: ScoreboardRow[] } | GenericApiError> -) { - switch (req.method) { - case "GET": - if (!req.query.userId) { - return res.status(400).json({ - error: "Missing user_id", - }); - } - const userId = req.query.userId; - let localLeaderboard: ScoreboardRow[] = []; - try { - localLeaderboard = await prisma.$queryRaw` - WITH user_rank AS ( - SELECT dense_rank_number FROM scoreboard WHERE user_id = ${userId} - ) - - SELECT * - FROM scoreboard - WHERE user_id = ${userId} - OR user_id IN ( - SELECT user_id - FROM scoreboard - WHERE dense_rank_number = (SELECT dense_rank_number FROM user_rank) - 1 - LIMIT 1 - ) - OR user_id IN ( - SELECT user_id - FROM scoreboard - WHERE dense_rank_number = (SELECT dense_rank_number FROM user_rank) + 1 - LIMIT 1 - ); - `; - } catch (error) { - console.error(error); - if (error instanceof Prisma.PrismaClientKnownRequestError) { - return res.status(500).json({ - error: "Internal server error", - details: error, - }); - } - - return res.status(500).json({ - error: "Internal server error", - }); - } - - localLeaderboard = JSON.parse( - JSON.stringify(localLeaderboard, (_key, value) => - typeof value === "bigint" ? Number(value) : value - ) - ); - - localLeaderboard = localLeaderboard.map((r) => ({ - ...r, - user_id: r.user_id === userId ? userId : null, - })); - console.log({ localLeaderboard: localLeaderboard }); - res.status(200).json({ localLeaderboard: localLeaderboard }); - - break; - default: - return res.status(405).json({ error: "Invalid method" }); - } -} diff --git a/src/pages/api/submitgame.ts b/src/pages/api/submitgame.ts deleted file mode 100644 index b6d6329..0000000 --- a/src/pages/api/submitgame.ts +++ /dev/null @@ -1,121 +0,0 @@ -import prisma from "@/lib/prisma"; -import { GenericApiError } from "@/types"; -import { Prisma } from "@prisma/client"; -import type { NextApiRequest, NextApiResponse } from "next"; -import { z } from "zod"; - -const schema = z.object({ - user_id: z.string().refine((s) => !Number.isNaN(parseFloat(s))), - attempts: z.number().min(1).max(11), - date: z - .string() - .datetime() - .refine( - (date) => { - const threeDaysAgo = new Date(); - threeDaysAgo.setDate(threeDaysAgo.getDate() - 3); - return new Date(date) >= threeDaysAgo; - }, - { - message: "Date must be within the last 3 days", - } - ), -}); -type SubmitGameSchema = z.infer; - -type Data = { - success: boolean; -}; - -export default async function handler( - req: NextApiRequest, - res: NextApiResponse -) { - switch (req.method) { - case "POST": - const response = schema.safeParse(req.body); - console.log(JSON.stringify(response)); - if (!response.success) { - return res.status(400).json({ - error: response.error, - }); - } - const { user_id, attempts, date } = req.body as SubmitGameSchema; - const flatDate = new Date(date.slice(0, 10) + "T00:00:00.000Z"); - - try { - const user = await prisma.user.findUnique({ - where: { - user_id: user_id, - }, - select: { - last_game_date: true, - }, - }); - console.log( - user?.last_game_date, - flatDate, - user?.last_game_date?.getTime() === flatDate.getTime() - ); - if (user?.last_game_date?.getTime() === flatDate.getTime()) { - return res.status(409).json({ - error: "Already submitted a game today!", - }); - } else { - await prisma.$transaction([ - prisma.user.upsert({ - where: { - user_id: user_id.toString(), - }, - update: { - last_game_date: flatDate, - }, - create: { - user_id: user_id.toString(), - last_game_date: flatDate, - }, - }), - - prisma.game_count.upsert({ - where: { - user_id_attempts: { - user_id: user_id.toString(), - attempts: attempts, - }, - }, - update: { - game_count: { - increment: 1, - }, - }, - create: { - user_id: user_id.toString(), - attempts: attempts, - game_count: 1, - }, - }), - ]); - } - } catch (error: any) { - console.error(error); - if (error instanceof Prisma.PrismaClientKnownRequestError) { - return res.status(500).json({ - error: "Game record insertion failed", - details: error, - }); - } - - return res.status(500).json({ - error: "Game record insertion failed", - }); - } - return res.status(200).json({ success: true }); - break; - case "OPTIONS": - console.log("OPTIONS"); - return res.status(200).json({ success: true }); - break; - default: - return res.status(405).json({ error: "Invalid method" }); - } -} diff --git a/src/pages/api/trpc/[trpc].ts b/src/pages/api/trpc/[trpc].ts new file mode 100644 index 0000000..5540cfe --- /dev/null +++ b/src/pages/api/trpc/[trpc].ts @@ -0,0 +1,9 @@ +import * as trpcNext from "@trpc/server/adapters/next"; +import { appRouter } from "../../../server/routers/_app"; + +// export API handler +// @link https://trpc.io/docs/v11/server/adapters +export default trpcNext.createNextApiHandler({ + router: appRouter, + createContext: () => ({}), +}); diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 4f1e400..6838e05 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -5,6 +5,7 @@ import LoadingSpinner from "@/components/LoadingSpinner.component"; import MCButton from "@/components/MCButton.component"; import Popup from "@/components/Popup.component"; import { useGlobal } from "@/context/Global/context"; +import { trpc } from "@/utils/trpc"; import { Inter } from "next/font/google"; import { useRouter } from "next/router"; import { useEffect, useRef, useState } from "react"; @@ -24,6 +25,8 @@ export default function Home() { } = useGlobal(); const [popupVisible, setPopupVisible] = useState(false); + const submitGame = trpc.game.submitGame.useMutation(); + const divRef = useRef(null); useEffect(() => { @@ -44,39 +47,15 @@ export default function Home() { localStorage.getItem("lastGameDate") !== gameDate.toDateString() && !random ) { - if (gameState === "won") { - fetch("/api/submitgame", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - user_id: userId, - attempts: craftingTables.length, - date: gameDate.toISOString(), // TODO make this based on the start time - }), - }).then((res) => { - if (res.ok) { - localStorage.setItem("lastGameDate", gameDate.toDateString()); - } - }); - } else if (gameState === "lost") { - fetch("/api/submitgame", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - user_id: userId, - attempts: 11, - date: gameDate.toISOString(), // make this based on the start time - }), - }).then((res) => { - if (res.ok) { - localStorage.setItem("lastGameDate", gameDate.toDateString()); - } + submitGame + .mutateAsync({ + user_id: userId, + attempts: gameState === "won" ? craftingTables.length : 11, + date: gameDate.toISOString(), // TODO make this based on the start time + }) + .then((res) => { + localStorage.setItem("lastGameDate", gameDate.toDateString()); }); - } } }, [gameState]); diff --git a/src/pages/stats/[userId].tsx b/src/pages/stats/[userId].tsx index e80d4f7..b018472 100644 --- a/src/pages/stats/[userId].tsx +++ b/src/pages/stats/[userId].tsx @@ -1,34 +1,30 @@ import Row from "@/components/StatRow.component"; -import prisma from "@/lib/prisma"; import { ScoreboardRow } from "@/types"; +import prisma from "@/utils/prisma"; +import { trpc } from "@/utils/trpc"; import { GetServerSideProps } from "next"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { useQueryState } from "nuqs"; export default function Stats({ liveUserScores, // actual user scores - // localLeaderboard, // materialized view scoreboard position and neighbours totalPlayerCount, }: { liveUserScores: ScoreboardRow; - // localLeaderboard: ScoreboardRow[]; totalPlayerCount: number; }) { const router = useRouter(); - const [localLeaderboard, setLocalLeaderboard] = useState([]); - const [isLoading, setIsLoading] = useState(true); - console.log(localLeaderboard); - useEffect(() => { - const fetchLocalLeaderboard = async () => { - const res = await fetch(`/api/scoreboard/${router.query.userId}`); - const data = await res.json(); - console.log(data); - setLocalLeaderboard(data.localLeaderboard); - setIsLoading(false); - }; + const userId = router.query.userId as string; - fetchLocalLeaderboard(); - }, []); + const { data, isLoading } = trpc.game.scoreboard.useQuery( + { + userId: userId ?? "", + }, + { + enabled: !!userId, + } + ); + const localLeaderboard = data?.localLeaderboard; const allAttempts = Object.keys(liveUserScores) .filter((key) => key.match(/^total_\d+$/)) @@ -45,7 +41,7 @@ export default function Stats({ ); const ranking = () => { - if (isLoading) { + if (!localLeaderboard || isLoading) { return row("Global Rank", "Loading..."); } if (localLeaderboard.length === 0) { diff --git a/src/pages/stats/index.tsx b/src/pages/stats/index.tsx index 7dba32a..88e0ca2 100644 --- a/src/pages/stats/index.tsx +++ b/src/pages/stats/index.tsx @@ -1,5 +1,5 @@ import Row from "@/components/StatRow.component"; -import prisma from "@/lib/prisma"; +import prisma from "@/utils/prisma"; import { GetServerSideProps } from "next"; export default function Stats({ diff --git a/src/server/routers/_app.ts b/src/server/routers/_app.ts new file mode 100644 index 0000000..8fc1c4c --- /dev/null +++ b/src/server/routers/_app.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { publicProcedure, createRouter } from "../trpc"; +import { gameRouter } from "./game"; + +export const appRouter = createRouter({ + hello: publicProcedure + .input( + z.object({ + text: z.string(), + }) + ) + .query((opts) => { + return { + greeting: `hello ${opts.input.text}`, + }; + }), + game: gameRouter, +}); + +// export type definition of API +export type AppRouter = typeof appRouter; diff --git a/src/server/routers/game.ts b/src/server/routers/game.ts new file mode 100644 index 0000000..c84130e --- /dev/null +++ b/src/server/routers/game.ts @@ -0,0 +1,146 @@ +import { ScoreboardRow } from "@/types"; +import prisma from "@/utils/prisma"; +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import { createRouter, publicProcedure } from "../trpc"; + +export const gameRouter = createRouter({ + scoreboard: publicProcedure + .input(z.object({ userId: z.string() })) + .query(async ({ ctx, input }) => { + const userId = input.userId; + let localLeaderboard: ScoreboardRow[] = []; + try { + localLeaderboard = await prisma.$queryRaw` + WITH user_rank AS ( + SELECT dense_rank_number FROM scoreboard WHERE user_id = ${userId} + ) + + SELECT * + FROM scoreboard + WHERE user_id = ${userId} + OR user_id IN ( + SELECT user_id + FROM scoreboard + WHERE dense_rank_number = (SELECT dense_rank_number FROM user_rank) - 1 + LIMIT 1 + ) + OR user_id IN ( + SELECT user_id + FROM scoreboard + WHERE dense_rank_number = (SELECT dense_rank_number FROM user_rank) + 1 + LIMIT 1 + ); + `; + } catch (error) { + console.error(error); + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "error", + }); + } + + localLeaderboard = JSON.parse( + JSON.stringify(localLeaderboard, (_key, value) => + typeof value === "bigint" ? Number(value) : value + ) + ); + + localLeaderboard = localLeaderboard.map((r) => ({ + ...r, + user_id: r.user_id === userId ? userId : null, + })); + console.log({ localLeaderboard: localLeaderboard }); + return { localLeaderboard: localLeaderboard }; + }), + submitGame: publicProcedure + .input( + z.object({ + user_id: z.string().refine((s) => !Number.isNaN(parseFloat(s))), + attempts: z.number().min(1).max(11), + date: z + .string() + .datetime() + .refine( + (date) => { + const threeDaysAgo = new Date(); + threeDaysAgo.setDate(threeDaysAgo.getDate() - 3); + return new Date(date) >= threeDaysAgo; + }, + { + message: "Date must be within the last 3 days", + } + ), + }) + ) + .mutation(async ({ ctx, input }) => { + const { user_id, attempts, date } = input; + const flatDate = new Date(date.slice(0, 10) + "T00:00:00.000Z"); + + try { + const user = await prisma.user.findUnique({ + where: { + user_id: user_id, + }, + select: { + last_game_date: true, + }, + }); + console.log( + user?.last_game_date, + flatDate, + user?.last_game_date?.getTime() === flatDate.getTime() + ); + if (user?.last_game_date?.getTime() === flatDate.getTime()) { + return new TRPCError({ + code: "CONFLICT", + message: "Already submitted a game today!", + }); + } else { + await prisma.$transaction([ + prisma.user.upsert({ + where: { + user_id: user_id.toString(), + }, + update: { + last_game_date: flatDate, + }, + create: { + user_id: user_id.toString(), + last_game_date: flatDate, + }, + }), + + prisma.game_count.upsert({ + where: { + user_id_attempts: { + user_id: user_id.toString(), + attempts: attempts, + }, + }, + update: { + game_count: { + increment: 1, + }, + }, + create: { + user_id: user_id.toString(), + attempts: attempts, + game_count: 1, + }, + }), + ]); + } + } catch (error: any) { + console.error(error); + + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Game record insertion failed" + error.toString(), + }); + } + return { success: true }; + }), +}); + +export type GameRouter = typeof gameRouter; diff --git a/src/server/trpc.ts b/src/server/trpc.ts new file mode 100644 index 0000000..30e81be --- /dev/null +++ b/src/server/trpc.ts @@ -0,0 +1,11 @@ +import { initTRPC } from "@trpc/server"; + +// Avoid exporting the entire t-object +// since it's not very descriptive. +// For instance, the use of a t variable +// is common in i18n libraries. +const t = initTRPC.create(); + +// Base router and procedure helpers +export const createRouter = t.router; +export const publicProcedure = t.procedure; diff --git a/src/lib/prisma.ts b/src/utils/prisma.ts similarity index 100% rename from src/lib/prisma.ts rename to src/utils/prisma.ts diff --git a/src/lib/recipe.ts b/src/utils/recipe.ts similarity index 100% rename from src/lib/recipe.ts rename to src/utils/recipe.ts diff --git a/src/utils/trpc.ts b/src/utils/trpc.ts new file mode 100644 index 0000000..b34dd24 --- /dev/null +++ b/src/utils/trpc.ts @@ -0,0 +1,47 @@ +import { httpBatchLink } from "@trpc/client"; +import { createTRPCNext } from "@trpc/next"; +import type { AppRouter } from "../server/routers/_app"; + +function getBaseUrl() { + if (typeof window !== "undefined") + // browser should use relative path + return ""; + + if (process.env.VERCEL_URL) + // reference for vercel.com + return `https://${process.env.VERCEL_URL}`; + + if (process.env.RENDER_INTERNAL_HOSTNAME) + // reference for render.com + return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`; + + // assume localhost + return `http://localhost:${process.env.PORT ?? 3000}`; +} + +export const trpc = createTRPCNext({ + config(opts) { + return { + links: [ + httpBatchLink({ + /** + * If you want to use SSR, you need to use the server's full URL + * @link https://trpc.io/docs/v11/ssr + **/ + url: `${getBaseUrl()}/api/trpc`, + + // You can pass any HTTP headers you wish here + async headers() { + return { + // authorization: getAuthCookie(), + }; + }, + }), + ], + }; + }, + /** + * @link https://trpc.io/docs/v11/ssr + **/ + ssr: false, +}); diff --git a/tsconfig.json b/tsconfig.json index 6049381..07d3117 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,6 @@ "@/*": ["./src/*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "src/lib/crafting.js"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "src/utils/crafting.js"], "exclude": ["node_modules"] }