diff --git a/package-lock.json b/package-lock.json index 379531d..0b03cf2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,11 @@ "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@sentry/nextjs": "^7.84.0", - "@tanstack/react-query": "^5.17.9", + "@tanstack/react-query": "^5.28.4", + "@trpc/client": "^11.0.0-next-beta.318", + "@trpc/next": "^11.0.0-next-beta.318", + "@trpc/react-query": "^11.0.0-next-beta.318", + "@trpc/server": "^11.0.0-next-beta.318", "ajv": "^8.12.0", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", @@ -79,24 +83,6 @@ "node": ">=18.11.0" } }, - "content-schema": { - "name": "@core/content-schema", - "version": "1.0.0", - "extraneous": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.12.0", - "chokidar": "^3.5.3", - "cuid": "^3.0.0", - "json-schema-to-typescript": "^13.1.2", - "yaml": "^2.3.4" - }, - "devDependencies": { - "concurrently": "^8.2.2", - "ts-node": "^10.9.2", - "typescript": "^5.3.3" - } - }, "node_modules/@aashutoshrathi/word-wrap": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", @@ -1687,81 +1673,6 @@ "esbuild": "*" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/darwin-x64": { "version": "0.19.12", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", @@ -1777,261 +1688,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", - "cpu": [ - "loong64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", - "cpu": [ - "mips64el" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", - "cpu": [ - "ppc64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", - "cpu": [ - "riscv64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", - "cpu": [ - "s390x" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2738,21 +2394,6 @@ "glob": "7.1.7" } }, - "node_modules/@next/swc-darwin-arm64": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.3.tgz", - "integrity": "sha512-64JbSvi3nbbcEtyitNn2LEDS/hcleAFpHdykpcnrstITFlzFgB/bW0ER5/SJJwUPj+ZPY+z3e+1jAfcczRLVGw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-darwin-x64": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.3.tgz", @@ -2768,111 +2409,6 @@ "node": ">= 10" } }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.3.tgz", - "integrity": "sha512-3tBWGgz7M9RKLO6sPWC6c4pAw4geujSwQ7q7Si4d6bo0l6cLs4tmO+lnSwFp1Tm3lxwfMk0SgkJT7EdwYSJvcg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.3.tgz", - "integrity": "sha512-v0v8Kb8j8T23jvVUWZeA2D8+izWspeyeDGNaT2/mTHWp7+37fiNfL8bmBWiOmeumXkacM/AB0XOUQvEbncSnHA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.3.tgz", - "integrity": "sha512-VM1aE1tJKLBwMGtyBR21yy+STfl0MapMQnNrXkxeyLs0GFv/kZqXS5Jw/TQ3TSUnbv0QPDf/X8sDXuMtSgG6eg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.3.tgz", - "integrity": "sha512-64EnmKy18MYFL5CzLaSuUn561hbO1Gk16jM/KHznYP3iCIfF9e3yULtHaMy0D8zbHfxset9LTOv6cuYKJgcOxg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.3.tgz", - "integrity": "sha512-WRDp8QrmsL1bbGtsh5GqQ/KWulmrnMBgbnb+59qNTW1kVi1nG/2ndZLkcbs2GX7NpFLlToLRMWSQXmPzQm4tog==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.3.tgz", - "integrity": "sha512-EKffQeqCrj+t6qFFhIFTRoqb2QwX1mU7iTOvMyLbYw3QtqTw9sMwjykyiMlZlrfm2a4fA84+/aeW+PMg1MjuTg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.3.tgz", - "integrity": "sha512-ERhKPSJ1vQrPiwrs15Pjz/rvDHZmkmvbf/BjPN/UCOI++ODftT0GtasDPi0j+y6PPJi5HsXw+dpRaXUaw4vjuQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4593,20 +4129,20 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.17.9", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.17.9.tgz", - "integrity": "sha512-8xcvpWIPaRMDNLMvG9ugcUJMgFK316ZsqkPPbsI+TMZsb10N9jk0B6XgPk4/kgWC2ziHyWR7n7wUhxmD0pChQw==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.28.4.tgz", + "integrity": "sha512-uQZqOFqLWUvXNIQZ63XdKzg22NtHzgCBUfDmjDHi3BoF+nUYeBNvMi/xFPtFrMhqRzG2Ir4mYaGsWZzmiEjXpA==", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" } }, "node_modules/@tanstack/react-query": { - "version": "5.17.9", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.17.9.tgz", - "integrity": "sha512-M5E9gwUq1Stby/pdlYjBlL24euIVuGbWKIFCbtnQxSdXI4PgzjTSdXdV3QE6fc+itF+TUvX/JPTKIwq8yuBXcg==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.28.4.tgz", + "integrity": "sha512-BErcoB/QQG6YwLSUKnaGxF+lSc270RH2w3kMBpG0i4YzDCsFs2pdxPX1WVknQvFk9bNgukMb158hc2Zb4SdwSA==", "dependencies": { - "@tanstack/query-core": "5.17.9" + "@tanstack/query-core": "5.28.4" }, "funding": { "type": "github", @@ -4738,6 +4274,65 @@ "node": ">= 10" } }, + "node_modules/@trpc/client": { + "version": "11.0.0-next-beta.318", + "resolved": "https://registry.npmjs.org/@trpc/client/-/client-11.0.0-next-beta.318.tgz", + "integrity": "sha512-R3IlUZqN3WKNNWsayMiVP6JqWVdyNSuwQmBQY7VqVepUBV210uo4GoFLv2vmmegOlHzgx9IUZG7u1grN1v1nAg==", + "funding": [ + "https://trpc.io/sponsor" + ], + "peerDependencies": { + "@trpc/server": "11.0.0-next-beta.318+e9899d002" + } + }, + "node_modules/@trpc/next": { + "version": "11.0.0-next-beta.318", + "resolved": "https://registry.npmjs.org/@trpc/next/-/next-11.0.0-next-beta.318.tgz", + "integrity": "sha512-qeWfJ1vPm7GchLmMZz5Gj+mBka0CRci0bCKEhGoG8RSvI/+9GbbhZHKRRDnlsN81CgexJ2e2nULET9ESO6rt+Q==", + "funding": [ + "https://trpc.io/sponsor" + ], + "peerDependencies": { + "@tanstack/react-query": "^5.25.0", + "@trpc/client": "11.0.0-next-beta.318+e9899d002", + "@trpc/react-query": "11.0.0-next-beta.318+e9899d002", + "@trpc/server": "11.0.0-next-beta.318+e9899d002", + "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-next-beta.318", + "resolved": "https://registry.npmjs.org/@trpc/react-query/-/react-query-11.0.0-next-beta.318.tgz", + "integrity": "sha512-T6l4+OuOkE4yylUqtT5EiLXo0M5+XW6YaKqnTCMkELQrrPmq3Ok0eKfhWy1Xk+ayx02POO9fTzNhITz3BID21Q==", + "funding": [ + "https://trpc.io/sponsor" + ], + "peerDependencies": { + "@tanstack/react-query": "^5.25.0", + "@trpc/client": "11.0.0-next-beta.318+e9899d002", + "@trpc/server": "11.0.0-next-beta.318+e9899d002", + "react": ">=18.2.0", + "react-dom": ">=18.2.0" + } + }, + "node_modules/@trpc/server": { + "version": "11.0.0-next-beta.318", + "resolved": "https://registry.npmjs.org/@trpc/server/-/server-11.0.0-next-beta.318.tgz", + "integrity": "sha512-lxwWfqgv3LvIfhagCElDtNEY6C2sQU3o43OH0vn9hQ+7o9j+JHwEZjZz1ixvm3wUZvwZ68LheXuKL9RdVn1d4w==", + "funding": [ + "https://trpc.io/sponsor" + ] + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -14214,9 +13809,9 @@ } }, "node_modules/typescript": { - "version": "5.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", - "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", + "integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==", "devOptional": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index bc77222..ac98482 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,9 @@ { "name": "core", "version": "1.0.0", - "workspaces": ["./packages/*"], + "workspaces": [ + "./packages/*" + ], "private": true, "scripts": { "postinstall": "patch-package", @@ -13,7 +15,7 @@ "test": "jest", "prettier": "prettier -w .", "test:e2e": "npx prisma migrate reset --force && playwright test", - "upload-content-schema": "ts-node --project tsconfig.script.json ./scripts/upload-content-schema packages/content-schema/schemas/ ./src/shared/api/content/schemas", + "upload-content-schema": "ts-node --project tsconfig.script.json ./scripts/upload-content-schema packages/content-schema/schemas/ ./src/shared/api/content/_schemas", "dev:content-schema": "npm run dev --workspace=@core/content-schema" }, "prisma": { @@ -32,7 +34,11 @@ "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", "@sentry/nextjs": "^7.84.0", - "@tanstack/react-query": "^5.17.9", + "@tanstack/react-query": "^5.28.4", + "@trpc/client": "^11.0.0-next-beta.318", + "@trpc/next": "^11.0.0-next-beta.318", + "@trpc/react-query": "^11.0.0-next-beta.318", + "@trpc/server": "^11.0.0-next-beta.318", "ajv": "^8.12.0", "class-variance-authority": "^0.7.0", "clsx": "^2.0.0", diff --git a/packages/content-schema/scripts/generate-schema-types.ts b/packages/content-schema/scripts/generate-schema-types.ts index 5717995..4a1dfa2 100644 --- a/packages/content-schema/scripts/generate-schema-types.ts +++ b/packages/content-schema/scripts/generate-schema-types.ts @@ -8,7 +8,7 @@ async function convertSchemaToType(file: string): Promise { const ts: string = await compileFromFile(file); fs.writeFileSync( path.join(path.dirname(file), path.basename(file, ".json") + ".d.ts"), - ts + ts, ); console.log(`Converted: ${file}`); } catch (error) { diff --git a/packages/content-schema/scripts/validate.ts b/packages/content-schema/scripts/validate.ts index d2d5c87..aa58180 100644 --- a/packages/content-schema/scripts/validate.ts +++ b/packages/content-schema/scripts/validate.ts @@ -34,14 +34,14 @@ const checkIds = (obj: any, context: string, path = "") => { if (obj.id) { if (ids.has(obj.id)) { console.error( - `Duplicate ID found: ${obj.id} in ${context} path: ${path}` + `Duplicate ID found: ${obj.id} in ${context} path: ${path}`, ); } else { ids.add(obj.id); } } Object.entries(obj).forEach(([key, value]) => - checkIds(value, `${path}.${key}`, context) + checkIds(value, `${path}.${key}`, context), ); } }; @@ -51,23 +51,23 @@ const checkIds = (obj: any, context: string, path = "") => { const lessonSchema = JSON.parse( fs.readFileSync(path.resolve(__dirname, "../schemas/lesson.schema.json"), { encoding: "utf8", - }) + }), ); const courseSchema = JSON.parse( fs.readFileSync(path.resolve(__dirname, "../schemas/course.schema.json"), { encoding: "utf8", - }) + }), ); const manifestSchema = JSON.parse( fs.readFileSync(path.resolve(__dirname, "../schemas/manifest.schema.json"), { encoding: "utf8", - }) + }), ); async function validateLesson(file: string) { const lesson = await contentParser.parse( fs.readFileSync(file, "utf8"), - lessonSchema + lessonSchema, ); checkIds(lesson, file); @@ -77,7 +77,7 @@ async function validateLesson(file: string) { async function validateCourse(file: string) { const course = await contentParser.parse( fs.readFileSync(file, "utf8"), - courseSchema + courseSchema, ); checkIds(course, file); return course; @@ -86,7 +86,7 @@ async function validateCourse(file: string) { async function validateManifest(file: string) { const manifest = await contentParser.parse( fs.readFileSync(file, "utf8"), - manifestSchema + manifestSchema, ); return manifest; } @@ -98,12 +98,12 @@ async function scanDirectory(directoryPath: string) { for (const coursePath of manifest.courses) { const course = await validateCourse( - `${directoryPath}/courses/${coursePath}/course.yaml` + `${directoryPath}/courses/${coursePath}/course.yaml`, ); for (const lessonPath of course.lessons) { await validateLesson( - `${directoryPath}/courses/${coursePath}/lessons/${lessonPath}/lesson.yaml` + `${directoryPath}/courses/${coursePath}/lessons/${lessonPath}/lesson.yaml`, ); } } diff --git a/packages/content-schema/tsconfig.json b/packages/content-schema/tsconfig.json index cd6f29b..4bb3d27 100644 --- a/packages/content-schema/tsconfig.json +++ b/packages/content-schema/tsconfig.json @@ -3,19 +3,19 @@ /* Visit https://aka.ms/tsconfig to read more about this file */ /* Language and Environment */ - "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, /* Modules */ - "module": "commonjs", /* Specify what module code is generated. */ + "module": "commonjs" /* Specify what module code is generated. */, /* Interop Constraints */ - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */ - "strict": true, /* Enable all strict type-checking options. */ + "strict": true /* Enable all strict type-checking options. */, /* Completeness */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ } } diff --git a/src/app/(auth)/auth/new-user/page.tsx b/src/app/(auth)/auth/new-user/page.tsx index 92e696a..ea2df8a 100644 --- a/src/app/(auth)/auth/new-user/page.tsx +++ b/src/app/(auth)/auth/new-user/page.tsx @@ -1,4 +1,4 @@ -import { getAppSessionServer } from "@/entities/user/session.server"; +import { getAppSessionServer } from "@/kernel/lib/next-auth/server"; import { UpdateProfileForm } from "@/features/update-profile/update-profile-form"; import { Separator } from "@/shared/ui/separator"; import { redirect } from "next/navigation"; diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx index 3651cac..b4efe71 100644 --- a/src/app/(auth)/layout.tsx +++ b/src/app/(auth)/layout.tsx @@ -1,4 +1,4 @@ -import { AppHeader } from "@/widgets/app-header/app-header"; +import { AppHeader } from "../_widgets/app-header/app-header"; export default async function Layout({ children, diff --git a/src/app/(private)/layout.tsx b/src/app/(private)/layout.tsx index b6e7e1b..c3255fc 100644 --- a/src/app/(private)/layout.tsx +++ b/src/app/(private)/layout.tsx @@ -1,5 +1,5 @@ import AuthorizedGuard from "@/features/auth/authorized-guard"; -import { AppHeader } from "@/widgets/app-header/app-header"; +import { AppHeader } from "../_widgets/app-header/app-header"; export default async function Layout({ children, diff --git a/src/app/(public)/layout.tsx b/src/app/(public)/layout.tsx index cba0b0d..341d2af 100644 --- a/src/app/(public)/layout.tsx +++ b/src/app/(public)/layout.tsx @@ -1,4 +1,4 @@ -import { AppHeader } from "@/widgets/app-header/app-header"; +import { AppHeader } from "../_widgets/app-header/app-header"; export default async function Layout({ children, diff --git a/src/app/_providers/app-provider.tsx b/src/app/_providers/app-provider.tsx index ee89d2c..e62b8c8 100644 --- a/src/app/_providers/app-provider.tsx +++ b/src/app/_providers/app-provider.tsx @@ -1,18 +1,35 @@ "use client"; -import { AppSessionProvider } from "@/entities/user/session"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import React, { useState } from "react"; import { ThemeProvider } from "@/features/theme/theme-provider"; -import { queryClient } from "@/shared/api/query-client"; +import { AppSessionProvider } from "@/kernel/lib/next-auth/client"; import { ComposeChildren } from "@/shared/lib/react"; -import { QueryClientProvider } from "@tanstack/react-query"; -import React from "react"; +import { sharedApi } from "@/kernel/lib/trpc/client"; +import { TRPCUntypedClient, httpBatchLink } from "@trpc/client"; +import { publicConfig } from "@/shared/config/public"; +import { AnyRouter } from "@trpc/server"; export function AppProvider({ children }: { children: React.ReactNode }) { + const [queryClient] = useState(() => new QueryClient()); + const [trpcClient] = useState>(() => + sharedApi.createClient({ + links: [ + httpBatchLink({ + url: `${publicConfig.PUBLIC_URL}/api/trpc`, + }), + ], + }), + ); + return ( + + <> + + - {children} ); diff --git a/src/widgets/app-header/_ui/layout.tsx b/src/app/_widgets/app-header/_ui/layout.tsx similarity index 100% rename from src/widgets/app-header/_ui/layout.tsx rename to src/app/_widgets/app-header/_ui/layout.tsx diff --git a/src/widgets/app-header/_ui/logo.tsx b/src/app/_widgets/app-header/_ui/logo.tsx similarity index 100% rename from src/widgets/app-header/_ui/logo.tsx rename to src/app/_widgets/app-header/_ui/logo.tsx diff --git a/src/widgets/app-header/_ui/main-nav.tsx b/src/app/_widgets/app-header/_ui/main-nav.tsx similarity index 100% rename from src/widgets/app-header/_ui/main-nav.tsx rename to src/app/_widgets/app-header/_ui/main-nav.tsx diff --git a/src/widgets/app-header/_ui/profile.tsx b/src/app/_widgets/app-header/_ui/profile.tsx similarity index 97% rename from src/widgets/app-header/_ui/profile.tsx rename to src/app/_widgets/app-header/_ui/profile.tsx index 2c1f7e5..d4f8a51 100644 --- a/src/widgets/app-header/_ui/profile.tsx +++ b/src/app/_widgets/app-header/_ui/profile.tsx @@ -11,11 +11,11 @@ import { import { LogOut, User } from "lucide-react"; import { Button } from "@/shared/ui/button"; import Link from "next/link"; -import { useAppSession } from "@/entities/user/session"; import { Skeleton } from "@/shared/ui/skeleton"; import { useSignOut } from "@/features/auth/use-sign-out"; import { SignInButton } from "@/features/auth/sign-in-button"; import { ProfileAvatar, getProfileDisplayName } from "@/entities/user/profile"; +import { useAppSession } from "@/kernel/lib/next-auth/client"; export function Profile() { const session = useAppSession(); diff --git a/src/widgets/app-header/app-header.tsx b/src/app/_widgets/app-header/app-header.tsx similarity index 100% rename from src/widgets/app-header/app-header.tsx rename to src/app/_widgets/app-header/app-header.tsx diff --git a/src/app/api/auth/[...nextauth]/route.ts b/src/app/api/auth/[...nextauth]/route.ts index ce39ce9..4ea9cf7 100644 --- a/src/app/api/auth/[...nextauth]/route.ts +++ b/src/app/api/auth/[...nextauth]/route.ts @@ -1,4 +1,4 @@ -import { nextAuthConfig } from "@/entities/user/next-auth-config"; +import { nextAuthConfig } from "@/kernel/lib/next-auth/next-auth-config"; import NextAuth from "next-auth/next"; const authHandler = NextAuth(nextAuthConfig); diff --git a/src/app/api/trpc/[trpc]/route.ts b/src/app/api/trpc/[trpc]/route.ts new file mode 100644 index 0000000..0539f2b --- /dev/null +++ b/src/app/api/trpc/[trpc]/route.ts @@ -0,0 +1,13 @@ +import { coursesListController } from "@/features/courses-list/controller"; +import { createContext, sharedRouter, t } from "@/kernel/lib/trpc/server"; +import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; + +const handler = (req: Request) => + fetchRequestHandler({ + endpoint: "/api/trpc", + req, + router: t.mergeRouters(sharedRouter, coursesListController), + createContext: createContext, + }); + +export { handler as GET, handler as POST }; diff --git a/src/entities/course/_use-cases/get-courses-list.ts b/src/entities/course/_services/get-courses-list.ts similarity index 64% rename from src/entities/course/_use-cases/get-courses-list.ts rename to src/entities/course/_services/get-courses-list.ts index 2a343ff..7af6070 100644 --- a/src/entities/course/_use-cases/get-courses-list.ts +++ b/src/entities/course/_services/get-courses-list.ts @@ -2,10 +2,10 @@ import { coursesRepository } from "../_repositories/course"; type GetCoursesList = {}; -export class GetCoursesListUseCase { +export class GetCoursesListService { async exec(data?: GetCoursesList) { return coursesRepository.getCoursesList(); } } -export const getCoursesListUseCase = new GetCoursesListUseCase(); +export const getCoursesListService = new GetCoursesListService(); diff --git a/src/entities/course/course.server.ts b/src/entities/course/course.server.ts index 54e4807..6ad1b32 100644 --- a/src/entities/course/course.server.ts +++ b/src/entities/course/course.server.ts @@ -1 +1 @@ -export { getCoursesListUseCase } from "./_use-cases/get-courses-list"; +export { getCoursesListService } from "./_services/get-courses-list"; diff --git a/src/entities/user/_actions/get-user-profile.ts b/src/entities/user/_actions/get-user-profile.ts index 4d1aa30..440ac01 100644 --- a/src/entities/user/_actions/get-user-profile.ts +++ b/src/entities/user/_actions/get-user-profile.ts @@ -1,7 +1,7 @@ "use server"; import { z } from "zod"; -import { getUserUseCase } from "../_use-cases/get-user"; -import { getAppSessionStrictServer } from "../session.server"; +import { getUserService } from "../_services/get-user"; +import { getAppSessionStrictServer } from "../../../kernel/lib/next-auth/server"; import { profileSchema } from "../_domain/schema"; const propsSchema = z.object({ @@ -19,7 +19,7 @@ export const getUserProfileAction = async ( const session = await getAppSessionStrictServer(); - const user = await getUserUseCase.exec({ + const user = await getUserService.exec({ session, userId, }); diff --git a/src/entities/user/_domain/ability.ts b/src/entities/user/_domain/ability.ts index a82a6bd..96fa2af 100644 --- a/src/entities/user/_domain/ability.ts +++ b/src/entities/user/_domain/ability.ts @@ -1,11 +1,11 @@ -import { ROLES, SessionEntity, UserId } from "./types"; +import { SharedSession, UserId, ROLES } from "@/kernel/domain/user"; -export const createUserAbility = (session: SessionEntity) => ({ +export const createUserAbility = (session: SharedSession) => ({ canGetUser: (userId: UserId) => session.user.id === userId || session.user.role === ROLES.ADMIN, }); -export const createProfileAbility = (session: SessionEntity) => ({ +export const createProfileAbility = (session: SharedSession) => ({ canUpdateProfile: (userId: UserId) => session.user.id === userId || session.user.role === ROLES.ADMIN, }); diff --git a/src/entities/user/_domain/types.ts b/src/entities/user/_domain/types.ts index bf7c001..5e46422 100644 --- a/src/entities/user/_domain/types.ts +++ b/src/entities/user/_domain/types.ts @@ -1,33 +1,3 @@ -export type UserId = string; -export type Role = "ADMIN" | "USER"; - -export const ROLES: Record = { - ADMIN: "ADMIN", - USER: "USER", -}; - -export type UserEntity = { - id: UserId; - email: string; - role: Role; - emailVerified?: Date | null; - name?: string | null; - image?: string | null; -}; - -export type SessionEntity = { - user: { - id: UserId; - email: string; - role: Role; - name?: string | null; - image?: string | null; - }; - expires: string; -}; - -// Projections - export type Profile = { email: string; name?: string | null; diff --git a/src/entities/user/_next-auth.d.ts b/src/entities/user/_next-auth.d.ts deleted file mode 100644 index 27bd9f9..0000000 --- a/src/entities/user/_next-auth.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -import NextAuth from "next-auth"; -import { SessionEntity, UserEntity } from "./_domain/types"; - -declare module "next-auth" { - interface Session { - user: SessionEntity["user"]; - } - interface User extends UserEntity {} -} diff --git a/src/entities/user/_queries/index.ts b/src/entities/user/_queries/index.ts index 8d24e49..5bee703 100644 --- a/src/entities/user/_queries/index.ts +++ b/src/entities/user/_queries/index.ts @@ -1,4 +1,4 @@ -import { UserId } from "../_domain/types"; +import { UserId } from "@/kernel/domain/user"; import { getUserProfileAction } from "../_actions/get-user-profile"; import { useQueryClient } from "@tanstack/react-query"; diff --git a/src/entities/user/_repositories/profile.ts b/src/entities/user/_repositories/profile.ts index 8644ea2..cb93536 100644 --- a/src/entities/user/_repositories/profile.ts +++ b/src/entities/user/_repositories/profile.ts @@ -1,5 +1,6 @@ import { dbClient } from "@/shared/lib/db"; -import { Profile, UserId } from "../_domain/types"; +import { Profile } from "../_domain/types"; +import { UserId } from "@/kernel/domain/user"; export class ProfileRepository { async update(userId: UserId, data: Partial): Promise { diff --git a/src/entities/user/_repositories/user.ts b/src/entities/user/_repositories/user.ts index 7f48cd3..5e271f1 100644 --- a/src/entities/user/_repositories/user.ts +++ b/src/entities/user/_repositories/user.ts @@ -1,20 +1,14 @@ +import { UserId, SharedUser } from "@/kernel/domain/user"; import { dbClient } from "@/shared/lib/db"; -import { UserEntity, UserId } from "../_domain/types"; export class UserRepository { - async getUserById(userId: UserId): Promise { + async getUserById(userId: UserId): Promise { return dbClient.user.findUniqueOrThrow({ where: { id: userId, }, }); } - - async createUser(user: UserEntity): Promise { - return await dbClient.user.create({ - data: user, - }); - } } export const userRepository = new UserRepository(); diff --git a/src/entities/user/_use-cases/get-user.ts b/src/entities/user/_services/get-user.ts similarity index 62% rename from src/entities/user/_use-cases/get-user.ts rename to src/entities/user/_services/get-user.ts index 767b02f..868213f 100644 --- a/src/entities/user/_use-cases/get-user.ts +++ b/src/entities/user/_services/get-user.ts @@ -1,15 +1,15 @@ -import { SessionEntity, UserEntity, UserId } from "../_domain/types"; import { userRepository } from "../_repositories/user"; import { createUserAbility } from "../_domain/ability"; import { AuthorizationError } from "@/shared/lib/errors"; +import { UserId, SharedSession, SharedUser } from "@/kernel/domain/user"; type GetUser = { userId: UserId; - session: SessionEntity; + session: SharedSession; }; -export class GetUserUseCase { - async exec({ userId, session }: GetUser): Promise { +export class GetUserService { + async exec({ userId, session }: GetUser): Promise { const userAbility = createUserAbility(session); if (!userAbility.canGetUser(userId)) { @@ -20,4 +20,4 @@ export class GetUserUseCase { } } -export const getUserUseCase = new GetUserUseCase(); +export const getUserService = new GetUserService(); diff --git a/src/entities/user/_use-cases/update-profile.ts b/src/entities/user/_services/update-profile.ts similarity index 70% rename from src/entities/user/_use-cases/update-profile.ts rename to src/entities/user/_services/update-profile.ts index 47815c5..a59c0f5 100644 --- a/src/entities/user/_use-cases/update-profile.ts +++ b/src/entities/user/_services/update-profile.ts @@ -1,15 +1,16 @@ -import { Profile, SessionEntity, UserId } from "../_domain/types"; +import { Profile } from "../_domain/types"; import { createProfileAbility } from "../_domain/ability"; import { AuthorizationError } from "@/shared/lib/errors"; import { profileRepository } from "../_repositories/profile"; +import { UserId, SharedSession } from "@/kernel/domain/user"; type UpdateProfile = { userId: UserId; data: Partial; - session: SessionEntity; + session: SharedSession; }; -export class UpdateProfileUseCase { +export class UpdateProfileService { async exec({ userId, session, data }: UpdateProfile): Promise { const profileAbility = createProfileAbility(session); @@ -21,4 +22,4 @@ export class UpdateProfileUseCase { } } -export const updateProfileUseCase = new UpdateProfileUseCase(); +export const updateProfileService = new UpdateProfileService(); diff --git a/src/entities/user/_ui/app-session-provider.tsx b/src/entities/user/_ui/app-session-provider.tsx deleted file mode 100644 index e2baebd..0000000 --- a/src/entities/user/_ui/app-session-provider.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { SessionProvider as NextAuthSessionProvider } from "next-auth/react"; - -export function AppSessionProvider({ - children, -}: { - children?: React.ReactNode; -}) { - return {children}; -} diff --git a/src/entities/user/_use-cases/create-user.ts b/src/entities/user/_use-cases/create-user.ts deleted file mode 100644 index b5629b4..0000000 --- a/src/entities/user/_use-cases/create-user.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ROLES, UserEntity } from "../_domain/types"; -import { createId } from "@/shared/lib/id"; -import { userRepository } from "../_repositories/user"; -import { privateConfig } from "@/shared/config/private"; - -type CreateUser = { - email: string; - name?: string | null; - image?: string | null; - emailVerified?: Date | null; -}; - -export class CreateUserUseCase { - async exec(data: CreateUser) { - const adminEmails = privateConfig.ADMIN_EMAILS?.split(",") ?? []; - const role = adminEmails.includes(data.email) ? ROLES.ADMIN : ROLES.USER; - - const user: UserEntity = { - id: createId(), - role, - ...data, - }; - - return await userRepository.createUser(user); - } -} - -export const createUserUseCase = new CreateUserUseCase(); diff --git a/src/entities/user/_vm/use-app-session.ts b/src/entities/user/_vm/use-app-session.ts deleted file mode 100644 index 681b69d..0000000 --- a/src/entities/user/_vm/use-app-session.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { useSession } from "next-auth/react"; - -export const useAppSession = useSession; diff --git a/src/entities/user/_vm/use-role.ts b/src/entities/user/_vm/use-role.ts deleted file mode 100644 index 93d30c2..0000000 --- a/src/entities/user/_vm/use-role.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { useAppSession } from "./use-app-session"; - -export const useRole = () => { - const session = useAppSession(); - return session?.data?.user?.role; -}; diff --git a/src/entities/user/profile.server.ts b/src/entities/user/profile.server.ts index 749dd37..8dc2a24 100644 --- a/src/entities/user/profile.server.ts +++ b/src/entities/user/profile.server.ts @@ -1 +1 @@ -export { updateProfileUseCase } from "./_use-cases/update-profile"; +export { updateProfileService } from "./_services/update-profile"; diff --git a/src/entities/user/user.ts b/src/entities/user/user.ts deleted file mode 100644 index be109fc..0000000 --- a/src/entities/user/user.ts +++ /dev/null @@ -1 +0,0 @@ -export type { UserId } from "./_domain/types"; diff --git a/src/features/auth/authorized-guard.tsx b/src/features/auth/authorized-guard.tsx index 14ad754..523d3d2 100644 --- a/src/features/auth/authorized-guard.tsx +++ b/src/features/auth/authorized-guard.tsx @@ -1,6 +1,6 @@ "use client"; -import { useAppSession } from "@/entities/user/session"; +import { useAppSession } from "@/kernel/lib/next-auth/client"; import { FullPageSpinner } from "@/shared/ui/full-page-spinner"; import { signIn } from "next-auth/react"; import { useEffect } from "react"; diff --git a/src/features/courses-list/_ui/courses-list.tsx b/src/features/courses-list/_ui/courses-list.tsx new file mode 100644 index 0000000..7acc844 --- /dev/null +++ b/src/features/courses-list/_ui/courses-list.tsx @@ -0,0 +1,25 @@ +"use client"; +import { CourseEntity } from "@/entities/course/course"; +import { CourseItem } from "./course-item"; +import { coursesListApi } from "../api"; + +export function CoursesListClient({ + defaultList, +}: { + defaultList: CourseEntity[]; +}) { + const { data: coursesList } = coursesListApi.courseList.get.useQuery( + undefined, + { + initialData: defaultList, + }, + ); + + return ( +
+ {coursesList.map((course) => ( + + ))} +
+ ); +} diff --git a/src/features/courses-list/api.ts b/src/features/courses-list/api.ts new file mode 100644 index 0000000..9945541 --- /dev/null +++ b/src/features/courses-list/api.ts @@ -0,0 +1,4 @@ +import { createApi } from "@/kernel/lib/trpc/client"; +import { CoursesListController } from "./controller"; + +export const coursesListApi = createApi(); diff --git a/src/features/courses-list/controller.ts b/src/features/courses-list/controller.ts new file mode 100644 index 0000000..320eaa0 --- /dev/null +++ b/src/features/courses-list/controller.ts @@ -0,0 +1,30 @@ +import { + createPublicServerApi, + publicProcedure, + router, +} from "@/kernel/lib/trpc/server"; +import { compileMDX } from "@/shared/lib/mdx/server"; +import { getCoursesListService } from "@/entities/course/course.server"; + +export const coursesListController = router({ + courseList: router({ + get: publicProcedure.query(async () => { + const coursesList = await getCoursesListService.exec(); + + const compiledCourses = await Promise.all( + coursesList.map(async (course) => ({ + ...course, + description: await compileMDX(course.description).then((r) => r.code), + })), + ); + + return compiledCourses; + }), + }), +}); + +export type CoursesListController = typeof coursesListController; + +export const coursesListServerApi = createPublicServerApi( + coursesListController, +); diff --git a/src/features/courses-list/courses-list.tsx b/src/features/courses-list/courses-list.tsx index 6c2238c..14002b1 100644 --- a/src/features/courses-list/courses-list.tsx +++ b/src/features/courses-list/courses-list.tsx @@ -1,22 +1,8 @@ -import { compileMDX } from "@/shared/lib/mdx/server"; -import { coursesRepository } from "@/entities/course/_repositories/course"; -import { CourseItem } from "./_ui/course-item"; +import { CoursesListClient } from "./_ui/courses-list"; +import { coursesListServerApi } from "./controller"; export async function CoursesList() { - const coursesList = await coursesRepository.getCoursesList(); + const coursesList = await coursesListServerApi.courseList.get.fetch(); - const compiledCourses = await Promise.all( - coursesList.map(async (course) => ({ - ...course, - description: await compileMDX(course.description).then((r) => r.code), - })), - ); - - return ( -
- {compiledCourses.map((course) => ( - - ))} -
- ); + return ; } diff --git a/src/features/update-profile/_actions/update-profile.ts b/src/features/update-profile/_actions/update-profile.ts index 6725989..7545a3d 100644 --- a/src/features/update-profile/_actions/update-profile.ts +++ b/src/features/update-profile/_actions/update-profile.ts @@ -2,8 +2,8 @@ import { z } from "zod"; import { profileSchema } from "@/entities/user/profile"; -import { getAppSessionStrictServer } from "@/entities/user/session.server"; -import { updateProfileUseCase } from "@/entities/user/profile.server"; +import { updateProfileService } from "@/entities/user/profile.server"; +import { getAppSessionStrictServer } from "@/kernel/lib/next-auth/server"; const propsSchema = z.object({ userId: z.string(), @@ -21,7 +21,7 @@ export const updateProfileAction = async ( const session = await getAppSessionStrictServer(); - const user = await updateProfileUseCase.exec({ + const user = await updateProfileService.exec({ session, data, userId, diff --git a/src/features/update-profile/_ui/profile-form.tsx b/src/features/update-profile/_ui/profile-form.tsx index 3166e3c..9e02d57 100644 --- a/src/features/update-profile/_ui/profile-form.tsx +++ b/src/features/update-profile/_ui/profile-form.tsx @@ -17,7 +17,7 @@ import { Input } from "@/shared/ui/input"; import { Spinner } from "@/shared/ui/spinner"; import { AvatarField } from "./avatar-field"; import { Profile } from "@/entities/user/profile"; -import { UserId } from "@/entities/user/user"; +import { UserId } from "@/kernel/domain/user"; import { useUpdateProfile } from "../_vm/use-update-profile"; const profileFormSchema = z.object({ diff --git a/src/features/update-profile/_vm/use-update-profile.ts b/src/features/update-profile/_vm/use-update-profile.ts index bf4fb1f..5b25af6 100644 --- a/src/features/update-profile/_vm/use-update-profile.ts +++ b/src/features/update-profile/_vm/use-update-profile.ts @@ -1,7 +1,7 @@ import { useMutation } from "@tanstack/react-query"; import { updateProfileAction } from "../_actions/update-profile"; -import { useAppSession } from "@/entities/user/session"; import { useInvalidateProfile } from "@/entities/user/_queries"; +import { useAppSession } from "@/kernel/lib/next-auth/client"; export const useUpdateProfile = () => { const { update: updateSession } = useAppSession(); diff --git a/src/kernel/domain/user.ts b/src/kernel/domain/user.ts new file mode 100644 index 0000000..202f2da --- /dev/null +++ b/src/kernel/domain/user.ts @@ -0,0 +1,27 @@ +export type UserId = string; +export type Role = "ADMIN" | "USER"; + +export const ROLES: Record = { + ADMIN: "ADMIN", + USER: "USER", +}; + +export type SharedUser = { + id: UserId; + email: string; + role: Role; + emailVerified?: Date | null; + name?: string | null; + image?: string | null; +}; + +export type SharedSession = { + user: { + id: UserId; + email: string; + role: Role; + name?: string | null; + image?: string | null; + }; + expires: string; +}; diff --git a/src/kernel/lib/next-auth/_next-auth.d.ts b/src/kernel/lib/next-auth/_next-auth.d.ts new file mode 100644 index 0000000..082282d --- /dev/null +++ b/src/kernel/lib/next-auth/_next-auth.d.ts @@ -0,0 +1,9 @@ +import NextAuth from "next-auth"; +import { SharedSession, SharedUser } from "../../domain/user"; + +declare module "next-auth" { + interface Session { + user: SharedSession["user"]; + } + interface User extends SharedUser {} +} diff --git a/src/entities/user/session.tsx b/src/kernel/lib/next-auth/client.tsx similarity index 76% rename from src/entities/user/session.tsx rename to src/kernel/lib/next-auth/client.tsx index 42f6761..e440234 100644 --- a/src/entities/user/session.tsx +++ b/src/kernel/lib/next-auth/client.tsx @@ -3,12 +3,6 @@ import { useSession } from "next-auth/react"; import { SessionProvider as NextAuthSessionProvider } from "next-auth/react"; export const useAppSession = useSession; - -export const useRole = () => { - const session = useAppSession(); - return session?.data?.user?.role; -}; - export function AppSessionProvider({ children, }: { diff --git a/src/entities/user/next-auth-config.ts b/src/kernel/lib/next-auth/next-auth-config.ts similarity index 78% rename from src/entities/user/next-auth-config.ts rename to src/kernel/lib/next-auth/next-auth-config.ts index cb7dfa6..1c15dc4 100644 --- a/src/entities/user/next-auth-config.ts +++ b/src/kernel/lib/next-auth/next-auth-config.ts @@ -5,7 +5,8 @@ import { PrismaAdapter } from "@auth/prisma-adapter"; import { dbClient } from "@/shared/lib/db"; import { compact } from "lodash-es"; import { privateConfig } from "@/shared/config/private"; -import { createUserUseCase } from "./_use-cases/create-user"; +import { ROLES, SharedUser } from "@/kernel/domain/user"; +import { createId } from "@/shared/lib/id"; const prismaAdapter = PrismaAdapter(dbClient); @@ -20,8 +21,19 @@ const emailToken = privateConfig.TEST_EMAIL_TOKEN export const nextAuthConfig: AuthOptions = { adapter: { ...prismaAdapter, - createUser: (user) => { - return createUserUseCase.exec(user); + createUser: async (data) => { + const adminEmails = privateConfig.ADMIN_EMAILS?.split(",") ?? []; + const role = adminEmails.includes(data.email) ? ROLES.ADMIN : ROLES.USER; + + const user: SharedUser = { + id: createId(), + ...data, + role, + }; + + return await dbClient.user.create({ + data: user, + }); }, } as AuthOptions["adapter"], callbacks: { diff --git a/src/entities/user/session.server.ts b/src/kernel/lib/next-auth/server.ts similarity index 100% rename from src/entities/user/session.server.ts rename to src/kernel/lib/next-auth/server.ts diff --git a/src/kernel/lib/trpc/client.ts b/src/kernel/lib/trpc/client.ts new file mode 100644 index 0000000..73a6c0b --- /dev/null +++ b/src/kernel/lib/trpc/client.ts @@ -0,0 +1,8 @@ +import { CreateTRPCReact, createTRPCReact } from "@trpc/react-query"; +import { SharedRouter } from "./server"; +import { AnyRouter } from "@trpc/server"; + +export const sharedApi = createTRPCReact(); + +export const createApi = () => + sharedApi as CreateTRPCReact; diff --git a/src/kernel/lib/trpc/server.ts b/src/kernel/lib/trpc/server.ts new file mode 100644 index 0000000..9c6075a --- /dev/null +++ b/src/kernel/lib/trpc/server.ts @@ -0,0 +1,84 @@ +import { AnyRouter, TRPCError, initTRPC } from "@trpc/server"; +import { getAppSessionServer } from "../next-auth/server"; +import { SharedSession } from "@/kernel/domain/user"; +import { ZodTypeAny, z } from "zod"; +import { createServerSideHelpers } from "@trpc/react-query/server"; + +export const createContext = async () => { + const session = await getAppSessionServer(); + + return { + session, + }; +}; + +export const t = initTRPC.context().create(); + +export const router = t.router; +export const publicProcedure = t.procedure; + +export const authorizedProcedure = t.procedure.use(({ ctx, next }) => { + if (!ctx.session) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + return next({ + ctx: { + session: ctx.session, + }, + }); +}); + +export const checkAbilityProcedure = ({ + check, + create, +}: { + check?: (ability: Ability) => boolean; + create: (session: SharedSession) => Ability; +}) => + authorizedProcedure.use(({ ctx, next }) => { + const ability = create(ctx.session); + + if (check && !check(ability)) { + throw new TRPCError({ code: "FORBIDDEN" }); + } + + return next({ + ctx: { + session: ctx.session, + ability, + }, + }); + }); + +export const checkAbilityInputProcedure = ({ + check, + create, + input, +}: { + input: Input; + check: (ability: Ability, input: z.infer) => boolean; + create: (session: SharedSession) => Ability; +}) => + authorizedProcedure.input(input).use(({ ctx, next, input: params }) => { + const ability = create(ctx.session); + + if (!check(ability, params)) { + throw new TRPCError({ code: "FORBIDDEN" }); + } + + return next({ + ctx: { + session: ctx.session, + ability, + }, + }); + }); + +export const sharedRouter = router({}); +export type SharedRouter = typeof sharedRouter; + +export const createPublicServerApi = (router: T) => + createServerSideHelpers({ + router: router, + ctx: () => ({}), + } as any); diff --git a/src/shared/api/content/_schemas/course.schema.d.ts b/src/shared/api/content/_schemas/course.schema.d.ts index e374358..edd6514 100644 --- a/src/shared/api/content/_schemas/course.schema.d.ts +++ b/src/shared/api/content/_schemas/course.schema.d.ts @@ -5,12 +5,24 @@ * and run json-schema-to-typescript to regenerate this file. */ -export type Uuid = string; -export type LessonPath = string; +export type Cuid = string; +export type Product = + | { + access: "free"; + } + | { + access: "paid"; + price: number; + }; export interface Course { - id: Uuid; + id: Cuid; title: string; description: string; - lessons: LessonPath[]; + shortDescription?: string; + thumbnail: string; + image: string; + dependencies?: Cuid[]; + lessons: Cuid[]; + product: Product; } diff --git a/src/shared/api/content/_schemas/course.schema.json b/src/shared/api/content/_schemas/course.schema.json index 1711664..3da662e 100644 --- a/src/shared/api/content/_schemas/course.schema.json +++ b/src/shared/api/content/_schemas/course.schema.json @@ -1,22 +1,85 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", - "required": ["id", "title", "description", "lessons"], + "required": [ + "id", + "title", + "description", + "lessons", + "thumbnail", + "image", + "product" + ], "additionalProperties": false, "properties": { - "id": { "type": "string", "title": "Uuid" }, + "id": { + "$ref": "#/definitions/cuid" + }, "title": { - "type": "string" + "type": "string", + "maxLength": 100 }, "description": { "type": "string" }, + "shortDescription": { + "type": "string" + }, + "thumbnail": { + "type": "string", + "$comment": "url to image sizes 600x300" + }, + "image": { + "type": "string", + "$comment": "url to image sizes 1920x600" + }, + "dependencies": { + "type": "array", + "items": { + "$ref": "#/definitions/cuid" + } + }, "lessons": { "type": "array", "items": { - "type": "string", - "title": "LessonPath" + "$ref": "#/definitions/cuid" } + }, + "product": { + "$ref": "#/definitions/product" + } + }, + "definitions": { + "cuid": { + "type": "string", + "title": "cuid" + }, + "product": { + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "required": ["access"], + "properties": { + "access": { + "const": "free" + } + } + }, + { + "type": "object", + "additionalProperties": false, + "required": ["access", "price"], + "properties": { + "access": { + "const": "paid" + }, + "price": { + "type": "number" + } + } + } + ] } } } diff --git a/src/shared/api/content/_schemas/lesson.schema.d.ts b/src/shared/api/content/_schemas/lesson.schema.d.ts index 1ed56b8..0c0d9a5 100644 --- a/src/shared/api/content/_schemas/lesson.schema.d.ts +++ b/src/shared/api/content/_schemas/lesson.schema.d.ts @@ -10,6 +10,7 @@ export type Cuid = string; export interface Lesson { id: Cuid; title: string; + shortDescription?: string; blocks: (TextBlock | VideoBlock | QuestionBlock)[]; } export interface TextBlock { diff --git a/src/shared/api/content/_schemas/lesson.schema.json b/src/shared/api/content/_schemas/lesson.schema.json index 08bc17c..e3a6e09 100644 --- a/src/shared/api/content/_schemas/lesson.schema.json +++ b/src/shared/api/content/_schemas/lesson.schema.json @@ -9,6 +9,9 @@ "title": { "type": "string" }, + "shortDescription": { + "type": "string" + }, "blocks": { "type": "array", "items": { diff --git a/src/shared/api/query-client.ts b/src/shared/api/query-client.ts deleted file mode 100644 index 6c7b9de..0000000 --- a/src/shared/api/query-client.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { QueryClient } from "@tanstack/react-query"; - -export const queryClient = new QueryClient(); diff --git a/src/shared/config/public.ts b/src/shared/config/public.ts new file mode 100644 index 0000000..47a128f --- /dev/null +++ b/src/shared/config/public.ts @@ -0,0 +1,11 @@ +import { z } from "zod"; + +const publicConfigSchema = z.object({ + isDev: z.boolean(), + PUBLIC_URL: z.string(), +}); + +export const publicConfig = publicConfigSchema.parse({ + isDev: process.env.NODE_ENV === "development", + PUBLIC_URL: process.env.NEXT_PUBLIC_PUBLIC_URL, +});