diff --git a/package-lock.json b/package-lock.json index 8374813..48fd550 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,11 @@ "name": "symposia", "version": "0.1.0", "dependencies": { - "@google-cloud/storage": "^7.0.1", + "@google-cloud/storage": "^7.1.0", "@mdx-js/mdx": "^2.3.0", - "@mdxeditor/editor": "^0.15.1", + "@mdxeditor/editor": "^1.0.0", "@next-auth/prisma-adapter": "^1.0.7", - "@prisma/client": "^5.1.1", + "@prisma/client": "^5.3.1", "@tailwindcss/typography": "^0.5.9", "@tanstack/react-query": "^4.33.0", "@trpc/client": "^10.37.1", @@ -27,16 +27,17 @@ "clsx": "^2.0.0", "encoding": "^0.1.13", "eslint": "8.47.0", - "eslint-config-next": "13.4.19", - "next": "13.4.19", + "eslint-config-next": "^13.5.2", + "next": "^13.5.2", "next-auth": "^4.23.1", "postcss": "8.4.28", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-hook-form": "^7.45.4", "react-hot-toast": "^2.4.1", "react-textarea-autosize": "^8.5.3", "remark-gfm": "^3.0.1", + "request": "^2.88.2", "superjson": "^1.13.1", "tailwindcss": "3.3.3", "typescript": "5.1.6", @@ -44,7 +45,7 @@ }, "devDependencies": { "daisyui": "^3.6.1", - "prisma": "^5.1.1", + "prisma": "^5.3.1", "ts-node": "^10.9.1" } }, @@ -366,41 +367,41 @@ "integrity": "sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==" }, "node_modules/@google-cloud/paginator": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", - "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.0.tgz", + "integrity": "sha512-87aeg6QQcEPxGCOthnpUjvw4xAZ57G7pL8FS0C4e/81fr3FjkpUpibf1s2v5XGyGhUVGF4Jfg7yEcxqn2iUw1w==", "dependencies": { "arrify": "^2.0.0", "extend": "^3.0.2" }, "engines": { - "node": ">=10" + "node": ">=14.0.0" } }, "node_modules/@google-cloud/projectify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", - "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/@google-cloud/promisify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", - "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/@google-cloud/storage": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.0.1.tgz", - "integrity": "sha512-YBJ8HaDZvbeVDgEGWuC6sCsfZNCooVfKg1J+CJ4iXwRejIWbKFjl8laWz8w+/+ucJHM9qOdGkB95Q/mhh2CX/A==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.1.0.tgz", + "integrity": "sha512-kAtniePZT5Ms9wayYcbT44H+1jwkYvRaA+E3IGnmBLG+aGwMTM0q9Xn0CCIez4D8toeBYczNkhQsQfRT1TDy7A==", "dependencies": { - "@google-cloud/paginator": "^3.0.7", - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", "abort-controller": "^3.0.0", "async-retry": "^1.3.3", "compressible": "^2.0.12", @@ -816,9 +817,9 @@ } }, "node_modules/@mdxeditor/editor": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@mdxeditor/editor/-/editor-0.15.1.tgz", - "integrity": "sha512-FBLmbiBW1LDR+Yc/1fsGdfS/JbH2IKUqosHip8C+bRC650y+ddjOkKkTogHLrUJfTTkRX09Z/H1oIVjrKcKBzw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@mdxeditor/editor/-/editor-1.0.0.tgz", + "integrity": "sha512-/vsEfPYKpN1LCNirq3D+tWw4ACVxH93thIk5TzxX+w6uvBLKxm12L/n/BCEoa7zxyhINmeOEqJtxrySzB7Pu6w==", "dependencies": { "@codemirror/lang-markdown": "^6.1.1", "@codemirror/state": "^6.2.1", @@ -883,22 +884,22 @@ } }, "node_modules/@next/env": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.19.tgz", - "integrity": "sha512-FsAT5x0jF2kkhNkKkukhsyYOrRqtSxrEhfliniIq0bwWbuXLgyt3Gv0Ml+b91XwjwArmuP7NxCiGd++GGKdNMQ==" + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.2.tgz", + "integrity": "sha512-dUseBIQVax+XtdJPzhwww4GetTjlkRSsXeQnisIJWBaHsnxYcN2RGzsPHi58D6qnkATjnhuAtQTJmR1hKYQQPg==" }, "node_modules/@next/eslint-plugin-next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.4.19.tgz", - "integrity": "sha512-N/O+zGb6wZQdwu6atMZHbR7T9Np5SUFUjZqCbj0sXm+MwQO35M8TazVB4otm87GkXYs2l6OPwARd3/PUWhZBVQ==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.5.2.tgz", + "integrity": "sha512-Ew8DOUerJYGRo8pI84SVwn9wxxx8sH92AanCXSkkLJM2W0RJEWy+BqWSCfrlA/3ZIczEl4l4o4lOeTGBPYfBJg==", "dependencies": { "glob": "7.1.7" } }, "node_modules/@next/swc-darwin-arm64": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.4.19.tgz", - "integrity": "sha512-vv1qrjXeGbuF2mOkhkdxMDtv9np7W4mcBtaDnHU+yJG+bBwa6rYsYSCI/9Xm5+TuF5SbZbrWO6G1NfTh1TMjvQ==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.2.tgz", + "integrity": "sha512-7eAyunAWq6yFwdSQliWMmGhObPpHTesiKxMw4DWVxhm5yLotBj8FCR4PXGkpRP2tf8QhaWuVba+/fyAYggqfQg==", "cpu": [ "arm64" ], @@ -911,9 +912,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.4.19.tgz", - "integrity": "sha512-jyzO6wwYhx6F+7gD8ddZfuqO4TtpJdw3wyOduR4fxTUCm3aLw7YmHGYNjS0xRSYGAkLpBkH1E0RcelyId6lNsw==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.2.tgz", + "integrity": "sha512-WxXYWE7zF1ch8rrNh5xbIWzhMVas6Vbw+9BCSyZvu7gZC5EEiyZNJsafsC89qlaSA7BnmsDXVWQmc+s1feSYbQ==", "cpu": [ "x64" ], @@ -926,9 +927,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.4.19.tgz", - "integrity": "sha512-vdlnIlaAEh6H+G6HrKZB9c2zJKnpPVKnA6LBwjwT2BTjxI7e0Hx30+FoWCgi50e+YO49p6oPOtesP9mXDRiiUg==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.2.tgz", + "integrity": "sha512-URSwhRYrbj/4MSBjLlefPTK3/tvg95TTm6mRaiZWBB6Za3hpHKi8vSdnCMw5D2aP6k0sQQIEG6Pzcfwm+C5vrg==", "cpu": [ "arm64" ], @@ -941,9 +942,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.4.19.tgz", - "integrity": "sha512-aU0HkH2XPgxqrbNRBFb3si9Ahu/CpaR5RPmN2s9GiM9qJCiBBlZtRTiEca+DC+xRPyCThTtWYgxjWHgU7ZkyvA==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.2.tgz", + "integrity": "sha512-HefiwAdIygFyNmyVsQeiJp+j8vPKpIRYDlmTlF9/tLdcd3qEL/UEBswa1M7cvO8nHcr27ZTKXz5m7dkd56/Esg==", "cpu": [ "arm64" ], @@ -956,9 +957,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.4.19.tgz", - "integrity": "sha512-htwOEagMa/CXNykFFeAHHvMJeqZfNQEoQvHfsA4wgg5QqGNqD5soeCer4oGlCol6NGUxknrQO6VEustcv+Md+g==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.2.tgz", + "integrity": "sha512-htGVVroW0tdHgMYwKWkxWvVoG2RlAdDXRO1RQxYDvOBQsaV0nZsgKkw0EJJJ3urTYnwKskn/MXm305cOgRxD2w==", "cpu": [ "x64" ], @@ -971,9 +972,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.4.19.tgz", - "integrity": "sha512-4Gj4vvtbK1JH8ApWTT214b3GwUh9EKKQjY41hH/t+u55Knxi/0wesMzwQRhppK6Ddalhu0TEttbiJ+wRcoEj5Q==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.2.tgz", + "integrity": "sha512-UBD333GxbHVGi7VDJPPDD1bKnx30gn2clifNJbla7vo5nmBV+x5adyARg05RiT9amIpda6yzAEEUu+s774ldkw==", "cpu": [ "x64" ], @@ -986,9 +987,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.4.19.tgz", - "integrity": "sha512-bUfDevQK4NsIAHXs3/JNgnvEY+LRyneDN788W2NYiRIIzmILjba7LaQTfihuFawZDhRtkYCv3JDC3B4TwnmRJw==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.2.tgz", + "integrity": "sha512-Em9ApaSFIQnWXRT3K6iFnr9uBXymixLc65Xw4eNt7glgH0eiXpg+QhjmgI2BFyc7k4ZIjglfukt9saNpEyolWA==", "cpu": [ "arm64" ], @@ -1001,9 +1002,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.4.19.tgz", - "integrity": "sha512-Y5kikILFAr81LYIFaw6j/NrOtmiM4Sf3GtOc0pn50ez2GCkr+oejYuKGcwAwq3jiTKuzF6OF4iT2INPoxRycEA==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.2.tgz", + "integrity": "sha512-TBACBvvNYU+87X0yklSuAseqdpua8m/P79P0SG1fWUvWDDA14jASIg7kr86AuY5qix47nZLEJ5WWS0L20jAUNw==", "cpu": [ "ia32" ], @@ -1016,9 +1017,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.4.19.tgz", - "integrity": "sha512-YzA78jBDXMYiINdPdJJwGgPNT3YqBNNGhsthsDoWHL9p24tEJn9ViQf/ZqTbwSpX/RrkPupLfuuTH2sf73JBAw==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.2.tgz", + "integrity": "sha512-LfTHt+hTL8w7F9hnB3H4nRasCzLD/fP+h4/GUVBTxrkMJOnh/7OZ0XbYDKO/uuWwryJS9kZjhxcruBiYwc5UDw==", "cpu": [ "x64" ], @@ -1076,12 +1077,12 @@ } }, "node_modules/@prisma/client": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.1.1.tgz", - "integrity": "sha512-fxcCeK5pMQGcgCqCrWsi+I2rpIbk0rAhdrN+ke7f34tIrgPwA68ensrpin+9+fZvuV2OtzHmuipwduSY6HswdA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.3.1.tgz", + "integrity": "sha512-ArOKjHwdFZIe1cGU56oIfy7wRuTn0FfZjGuU/AjgEBOQh+4rDkB6nF+AGHP8KaVpkBIiHGPQh3IpwQ3xDMdO0Q==", "hasInstallScript": true, "dependencies": { - "@prisma/engines-version": "5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e" + "@prisma/engines-version": "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59" }, "engines": { "node": ">=16.13" @@ -1096,16 +1097,16 @@ } }, "node_modules/@prisma/engines": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.1.1.tgz", - "integrity": "sha512-NV/4nVNWFZSJCCIA3HIFJbbDKO/NARc9ej0tX5S9k2EVbkrFJC4Xt9b0u4rNZWL4V+F5LAjvta8vzEUw0rw+HA==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.3.1.tgz", + "integrity": "sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==", "devOptional": true, "hasInstallScript": true }, "node_modules/@prisma/engines-version": { - "version": "5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e.tgz", - "integrity": "sha512-owZqbY/wucbr65bXJ/ljrHPgQU5xXTSkmcE/JcbqE1kusuAXV/TLN3/exmz21SZ5rJ7WDkyk70J2G/n68iogbQ==" + "version": "5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59.tgz", + "integrity": "sha512-y5qbUi3ql2Xg7XraqcXEdMHh0MocBfnBzDn5GbV1xk23S3Mq8MGs+VjacTNiBh3dtEdUERCrUUG7Z3QaJ+h79w==" }, "node_modules/@radix-ui/colors": { "version": "0.1.9", @@ -1915,9 +1916,9 @@ "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==" }, "node_modules/@swc/helpers": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz", - "integrity": "sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", + "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", "dependencies": { "tslib": "^2.4.0" } @@ -2516,6 +2517,22 @@ "node": ">=8" } }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/ast-types-flow": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", @@ -2545,6 +2562,11 @@ "has-symbols": "^1.0.3" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, "node_modules/autoprefixer": { "version": "10.4.15", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.15.tgz", @@ -2592,6 +2614,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, "node_modules/axe-core": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", @@ -2641,6 +2676,14 @@ } ] }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/bignumber.js": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", @@ -2794,6 +2837,11 @@ } ] }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, "node_modules/ccount": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", @@ -2941,6 +2989,17 @@ "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "dev": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/comma-separated-tokens": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", @@ -3001,6 +3060,11 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -3085,6 +3149,17 @@ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==" }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3133,6 +3208,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -3234,6 +3317,15 @@ "stream-shift": "^1.0.0" } }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -3517,18 +3609,18 @@ } }, "node_modules/eslint-config-next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.4.19.tgz", - "integrity": "sha512-WE8367sqMnjhWHvR5OivmfwENRQ1ixfNE9hZwQqNCsd+iM3KnuMc1V8Pt6ytgjxjf23D+xbesADv9x3xaKfT3g==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.5.2.tgz", + "integrity": "sha512-kCF7k7fHBtFtxfP6J6AP6Mo0vW3CrFeoIuoZ7NHGIvLFc/RUaIspJ6inO/R33zE1o9t/lbJgTnsqnRB++sxCUQ==", "dependencies": { - "@next/eslint-plugin-next": "13.4.19", - "@rushstack/eslint-patch": "^1.1.3", + "@next/eslint-plugin-next": "13.5.2", + "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.31.7", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jsx-a11y": "^6.7.1", + "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" }, "peerDependencies": { @@ -3952,6 +4044,14 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4102,6 +4202,27 @@ "is-callable": "^1.1.3" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", @@ -4244,6 +4365,14 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/glob": { "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", @@ -4384,6 +4513,27 @@ "node": ">=14.0.0" } }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -4673,6 +4823,20 @@ "node": ">= 6.0.0" } }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, "node_modules/https-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", @@ -5152,6 +5316,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, "node_modules/is-weakmap": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", @@ -5214,6 +5383,11 @@ "url": "https://github.com/sponsors/dmonad" } }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, "node_modules/iterator.prototype": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.0.tgz", @@ -5258,6 +5432,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, "node_modules/json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -5266,6 +5445,11 @@ "bignumber.js": "^9.0.0" } }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -5276,6 +5460,11 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, "node_modules/json5": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", @@ -5287,6 +5476,20 @@ "json5": "lib/cli.js" } }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -6629,12 +6832,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" }, "node_modules/next": { - "version": "13.4.19", - "resolved": "https://registry.npmjs.org/next/-/next-13.4.19.tgz", - "integrity": "sha512-HuPSzzAbJ1T4BD8e0bs6B9C1kWQ6gv8ykZoRWs5AQoiIuqbGHHdQO7Ljuvg05Q0Z24E2ABozHe6FxDvI6HfyAw==", + "version": "13.5.2", + "resolved": "https://registry.npmjs.org/next/-/next-13.5.2.tgz", + "integrity": "sha512-vog4UhUaMYAzeqfiAAmgB/QWLW7p01/sg+2vn6bqc/CxHFYizMzLv6gjxKzl31EVFkfl/F+GbxlKizlkTE9RdA==", "dependencies": { - "@next/env": "13.4.19", - "@swc/helpers": "0.5.1", + "@next/env": "13.5.2", + "@swc/helpers": "0.5.2", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", "postcss": "8.4.14", @@ -6646,18 +6849,18 @@ "next": "dist/bin/next" }, "engines": { - "node": ">=16.8.0" + "node": ">=16.14.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "13.4.19", - "@next/swc-darwin-x64": "13.4.19", - "@next/swc-linux-arm64-gnu": "13.4.19", - "@next/swc-linux-arm64-musl": "13.4.19", - "@next/swc-linux-x64-gnu": "13.4.19", - "@next/swc-linux-x64-musl": "13.4.19", - "@next/swc-win32-arm64-msvc": "13.4.19", - "@next/swc-win32-ia32-msvc": "13.4.19", - "@next/swc-win32-x64-msvc": "13.4.19" + "@next/swc-darwin-arm64": "13.5.2", + "@next/swc-darwin-x64": "13.5.2", + "@next/swc-linux-arm64-gnu": "13.5.2", + "@next/swc-linux-arm64-musl": "13.5.2", + "@next/swc-linux-x64-gnu": "13.5.2", + "@next/swc-linux-x64-musl": "13.5.2", + "@next/swc-win32-arm64-msvc": "13.5.2", + "@next/swc-win32-ia32-msvc": "13.5.2", + "@next/swc-win32-x64-msvc": "13.5.2" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -6782,6 +6985,14 @@ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7064,6 +7275,11 @@ "node": ">=8" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, "node_modules/periscopic": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz", @@ -7264,13 +7480,13 @@ "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" }, "node_modules/prisma": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.1.1.tgz", - "integrity": "sha512-WJFG/U7sMmcc6TjJTTifTfpI6Wjoh55xl4AzopVwAdyK68L9/ogNo8QQ2cxuUjJf/Wa82z/uhyh3wMzvRIBphg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.3.1.tgz", + "integrity": "sha512-Wp2msQIlMPHe+5k5Od6xnsI/WNG7UJGgFUJgqv/ygc7kOECZapcSz/iU4NIEzISs3H1W9sFLjAPbg/gOqqtB7A==", "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "5.1.1" + "@prisma/engines": "5.3.1" }, "bin": { "prisma": "build/index.js" @@ -7306,6 +7522,11 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -7314,6 +7535,19 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7641,6 +7875,51 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -7895,6 +8174,30 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/static-browser-server": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz", @@ -8286,6 +8589,20 @@ "node": ">=8.0" } }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -8390,6 +8707,22 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, "node_modules/type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", @@ -8635,6 +8968,14 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", @@ -8672,6 +9013,15 @@ "punycode": "^2.1.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/use-callback-ref": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", @@ -8802,6 +9152,19 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "devOptional": true }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "node_modules/vfile": { "version": "5.3.7", "resolved": "https://registry.npmjs.org/vfile/-/vfile-5.3.7.tgz", diff --git a/package.json b/package.json index 057c27b..79bf620 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,11 @@ "lint": "next lint" }, "dependencies": { - "@google-cloud/storage": "^7.0.1", + "@google-cloud/storage": "^7.1.0", "@mdx-js/mdx": "^2.3.0", - "@mdxeditor/editor": "^0.15.1", + "@mdxeditor/editor": "^1.0.0", "@next-auth/prisma-adapter": "^1.0.7", - "@prisma/client": "^5.1.1", + "@prisma/client": "^5.3.1", "@tailwindcss/typography": "^0.5.9", "@tanstack/react-query": "^4.33.0", "@trpc/client": "^10.37.1", @@ -28,24 +28,28 @@ "clsx": "^2.0.0", "encoding": "^0.1.13", "eslint": "8.47.0", - "eslint-config-next": "13.4.19", - "next": "13.4.19", + "eslint-config-next": "^13.5.2", + "next": "^13.5.2", "next-auth": "^4.23.1", "postcss": "8.4.28", - "react": "18.2.0", - "react-dom": "18.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-hook-form": "^7.45.4", "react-hot-toast": "^2.4.1", "react-textarea-autosize": "^8.5.3", "remark-gfm": "^3.0.1", + "request": "^2.88.2", "superjson": "^1.13.1", "tailwindcss": "3.3.3", "typescript": "5.1.6", "zod": "^3.22.2" }, + "overrides": { + "tough-cookie": "4.1.3" + }, "devDependencies": { "daisyui": "^3.6.1", - "prisma": "^5.1.1", + "prisma": "^5.3.1", "ts-node": "^10.9.1" } } diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 59ee0e2..f42b337 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,7 +10,9 @@ datasource db { url = env("DATABASE_URL") } -// User Access and Permissions (necessary for NextAuth) +// === SECTION 1 ============================================== +// * User Access and Permissions (necessary for NextAuth) +// ============================================================ model Account { id String @id @default(cuid()) userId String @@ -49,8 +51,8 @@ model User { accounts Account[] sessions Session[] - stripeCustomerId String? @unique - stripePurchasedProducts String[] + stripeCustomerId String? @unique + stripePurchasedProducts StripeProduct[] lessonsCompleted UserLessonProgress[] } @@ -68,13 +70,15 @@ model VerificationToken { @@unique([identifier, token]) } -// Course, Lessons and Content related data +// === SECTION 2 ============================================== +// * Course, Lessons and Content related data +// ============================================================ model Course { id String @id @default(cuid()) name String description String slug String @unique - stripeProductId String? @unique + stripeProductId StripeProduct? imageUrl String? parts Part[] lessons Lesson[] @@ -83,20 +87,27 @@ model Course { published Boolean @default(false) } +enum MdxCategory { + CONTENT + TRANSCRIPT + DETAILS +} + model CourseDetails { - id String @id @default(cuid()) - course Course @relation(fields: [courseId], references: [id]) - courseId String @unique - content Bytes - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) + courseId String @unique + mdxCategory MdxCategory @default(DETAILS) + mdx Bytes + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Part { id String @id @default(cuid()) name String slug String - course Course @relation(fields: [courseId], references: [id]) + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) courseId String lessons Lesson[] } @@ -108,7 +119,7 @@ model Lesson { slug String part Part? @relation(fields: [partId], references: [id]) partId String? - course Course @relation(fields: [courseId], references: [id]) + course Course @relation(fields: [courseId], references: [id], onDelete: Cascade) courseId String content LessonContent? transcript LessonTranscript? @@ -117,26 +128,28 @@ model Lesson { } model LessonContent { - id String @id @default(cuid()) - lesson Lesson @relation(fields: [lessonId], references: [id]) - lessonId String @unique - content Bytes - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade) + lessonId String @unique + mdxCategory MdxCategory @default(CONTENT) + mdx Bytes + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model LessonTranscript { - id String @id @default(cuid()) - lesson Lesson @relation(fields: [lessonId], references: [id]) - lessonId String @unique - transcript Bytes - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade) + lessonId String @unique + mdxCategory MdxCategory @default(TRANSCRIPT) + mdx Bytes + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt } model Video { id String @id @default(cuid()) - lesson Lesson? @relation(fields: [lessonId], references: [id]) + lesson Lesson? @relation(fields: [lessonId], references: [id], onDelete: Cascade) lessonId String? @unique fileName String duration Float? @@ -144,11 +157,49 @@ model Video { } model UserLessonProgress { - user User @relation(fields: [userId], references: [id]) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId String - lesson Lesson @relation(fields: [lessonId], references: [id]) + lesson Lesson @relation(fields: [lessonId], references: [id], onDelete: Cascade) lessonId String completedAt DateTime @default(now()) @@id([userId, lessonId]) } + +// === SECTION 3 ============================================== +// * Stripe related data +// ============================================================ +model StripeProduct { + id String @id @default(cuid()) + productId String @unique + interlinkedModel Course? @relation(fields: [interlinkedModelId], references: [id], onDelete: Cascade) + interlinkedModelId String? @unique + users User[] + prices StripePrice[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model StripePrice { + id String @id @default(cuid()) + priceId String @unique + amount Float + currency String + product StripeProduct @relation(fields: [productId], references: [id], onDelete: Cascade) + productId String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model StripeEvent { + id String @id @unique + api_version String? + data Json + request Json? + type String + object String + account String? + created DateTime + livemode Boolean + pending_webhooks Int +} diff --git a/src/app/(admin)/admin/courses/[courseId]/course-details/[courseDetailsId]/page.tsx b/src/app/(admin)/admin/courses/[courseId]/course-details/[courseDetailsId]/page.tsx new file mode 100644 index 0000000..f6acb96 --- /dev/null +++ b/src/app/(admin)/admin/courses/[courseId]/course-details/[courseDetailsId]/page.tsx @@ -0,0 +1,29 @@ +import Editor from "@/components/Editor"; +import { dbGetCourseAndDetailsAndLessonsById, dbGetMdxByModelId } from "@/server/controllers/coursesController"; + +/** + * Fetches data for CourseDetails and renders the MDX Editor to the UI. + */ +export default async function AdminLessonMaterialEdit ({ + params +}: { + params: { courseId: string, courseDetailsId: string } +}) { + const courseId = params.courseId; + const courseDetailsId = params.courseDetailsId; + if (typeof courseDetailsId !== "string") { throw new Error("missing lessonContent id") }; + + const editorMaterial = await dbGetMdxByModelId(courseDetailsId); + const course = await dbGetCourseAndDetailsAndLessonsById(courseId); + + if (!editorMaterial) { + throw new Error("CourseDetails not found"); + } + if (!course) { + throw new Error("Course not found"); + } + + return( + + ) +} \ No newline at end of file diff --git a/src/app/(admin)/admin/courses/[courseId]/course-details/new/page.tsx b/src/app/(admin)/admin/courses/[courseId]/course-details/new/page.tsx new file mode 100644 index 0000000..f0409e2 --- /dev/null +++ b/src/app/(admin)/admin/courses/[courseId]/course-details/new/page.tsx @@ -0,0 +1,34 @@ +import { dbGetCourseAndDetailsAndLessonsById, dbUpsertCourseDetailsById } from "@/server/controllers/coursesController"; +import { redirect } from "next/navigation"; + +/** + * Intermediate route that creates a new CourseDetails entry unless it exists and pushes the user to that route. + * @description Should never display any UI + */ +export default async function AdminCourseDetailsNew ({ params }: { params: { courseId: string} }) { + + const courseId = params.courseId; + if (typeof courseId !== "string") { throw new Error("missing course or lesson id") }; + + const course = await dbGetCourseAndDetailsAndLessonsById(courseId); + + if (course && course.details && course.details.id) { + // If CourseDetails already exists, push to that. + redirect(`/admin/courses/${courseId}/course-details/${course.details.id}`); + } + const newCourseDetails = { + courseId: courseId, + content: String(`Hello **new course details for ${course?.name}!**`), + } + + const result = await dbUpsertCourseDetailsById(newCourseDetails); + + if (result && result.id) { + // If new LessonContent entry was created successfully, push user to route. + redirect(`/admin/courses/${courseId}/course-details/${result.id}`); + } + + return( +

You should never see this message. An error occured somewhere.

+ ) +} \ No newline at end of file diff --git a/src/app/(admin)/admin/courses/[courseId]/lessons/[lessonId]/lesson-material/[lessonMaterialId]/page.tsx b/src/app/(admin)/admin/courses/[courseId]/lessons/[lessonId]/lesson-material/[lessonMaterialId]/page.tsx index 43e176b..610a0d8 100644 --- a/src/app/(admin)/admin/courses/[courseId]/lessons/[lessonId]/lesson-material/[lessonMaterialId]/page.tsx +++ b/src/app/(admin)/admin/courses/[courseId]/lessons/[lessonId]/lesson-material/[lessonMaterialId]/page.tsx @@ -1,5 +1,5 @@ import Editor from "@/components/Editor"; -import { dbGetLessonAndRelationsById, dbGetLessonContentOrLessonTranscriptById } from "@/server/controllers/coursesController"; +import { dbGetLessonAndRelationsById, dbGetMdxByModelId } from "@/server/controllers/coursesController"; /** * Common route for models LessonContent and LessonTranscript that fetches respective data and renders the MDX Editor to the UI. @@ -14,7 +14,7 @@ export default async function AdminLessonMaterialEdit ({ const lessonMaterialId = params.lessonMaterialId; if (typeof lessonMaterialId !== "string") { throw new Error("missing lessonContent id") }; - const lessonMaterial = await dbGetLessonContentOrLessonTranscriptById(lessonMaterialId); + const lessonMaterial = await dbGetMdxByModelId(lessonMaterialId); const lesson = await dbGetLessonAndRelationsById(lessonId); if (!lessonMaterial) { @@ -25,6 +25,6 @@ export default async function AdminLessonMaterialEdit ({ } return( - + ) } \ No newline at end of file diff --git a/src/app/(admin)/admin/courses/[courseId]/page.tsx b/src/app/(admin)/admin/courses/[courseId]/page.tsx index ac3a916..84a4c53 100644 --- a/src/app/(admin)/admin/courses/[courseId]/page.tsx +++ b/src/app/(admin)/admin/courses/[courseId]/page.tsx @@ -1,18 +1,20 @@ import Link from 'next/link' -import { dbGetCourseAndLessonsById } from '@/server/controllers/coursesController'; +import { dbGetCourseAndDetailsAndLessonsById } from '@/server/controllers/coursesController'; import { redirect } from "next/navigation"; import Heading from '@/components/Heading'; import AdminCourseFormContainer from '@/components/AdminCourseFormContainer' import CourseMaterialCard from '@/components/CourseMaterialCard'; +import toast from 'react-hot-toast'; export default async function AdminCourseEdit ({ params }: { params: { courseId: string }}) { - const id = params.courseId; - if (typeof id !== "string") { throw new Error('missing course id') }; + const courseId = params.courseId; + if (typeof courseId !== "string") { throw new Error('missing course id') }; - const course = await dbGetCourseAndLessonsById(id); + const course = await dbGetCourseAndDetailsAndLessonsById(courseId); if (!course) { + toast.error("Oops! No course was found here!") console.log("No course found"); redirect("/"); } @@ -21,7 +23,25 @@ export default async function AdminCourseEdit ({ params }: { params: { courseId:
{course.name} - + +
+ Course Details + {( + course.details + ) ? ( + + ) : ( +
+ Currently no details. + + + +
+ )} +
diff --git a/src/app/(admin)/admin/layout.tsx b/src/app/(admin)/admin/layout.tsx index 44a2eac..dd5eca5 100644 --- a/src/app/(admin)/admin/layout.tsx +++ b/src/app/(admin)/admin/layout.tsx @@ -2,7 +2,8 @@ import { getServerAuthSession } from "@/server/auth"; import { redirect } from "next/navigation"; -export const dynamic = 'force-dynamic'; // Nextjs flag that disables all caching of fetch requests and always invalidates routes on /admin/* +export const dynamic = 'force-dynamic'; // Nextjs flags that disables all caching of fetch requests and always invalidates routes on /admin/* +export const revalidate = 0; /** * AdminLayout controls the access and UI for /admin/** diff --git a/src/app/page.tsx b/src/app/page.tsx index 6bfe07c..d97bf2d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,10 +1,7 @@ -import { apiServerside } from "../lib/trpc/trpcServerside" import AuthShowcase from "../components/test/AuthShowcase"; -import TodoList from "../components/test/TodoList"; import ToastTest from "@/components/test/ToastTest"; export default async function Home() { - const todos = await apiServerside.fiction.getTodos(); return (
@@ -14,7 +11,6 @@ export default async function Home() { //TODO To be removed */} -
) diff --git a/src/app/test/[courseSlug]/[lessonSlug]/page.tsx b/src/app/test/[courseSlug]/[lessonSlug]/page.tsx new file mode 100644 index 0000000..6f361c6 --- /dev/null +++ b/src/app/test/[courseSlug]/[lessonSlug]/page.tsx @@ -0,0 +1,51 @@ +import LoadingBars from "@/components/LoadingBars"; +import MDXRenderer from "@/components/MDXRenderer"; +import { MdxGetCompiledSourceProps, mdxGetCompiledSource } from "@/server/controllers/mdxController"; +import { Suspense } from "react"; + + +/** + * * TEST ROUTE + * * /test/first-course-updated/logic-introduction + */ +export default async function TestPage2({ params }: { params: { courseSlug: string, lessonSlug: string}}) { + const courseSlug = params.courseSlug; + const lessonSlug = params.lessonSlug; + /** + * TODO Why must I add the Props here for TS not to yell at me!? + */ + const mdxGetArgs: MdxGetCompiledSourceProps = { + courseSlug: courseSlug, + lessonSlug: lessonSlug, + lessonType: "CONTENT", + access: "PUBLIC", + } + const compiledMdx = await mdxGetCompiledSource(mdxGetArgs) + + const mdxGetArgs2: MdxGetCompiledSourceProps = { + courseSlug: courseSlug, + lessonSlug: lessonSlug, + lessonType: "TRANSCRIPT", + access: "PUBLIC", + } + const compiledMdx2 = await mdxGetCompiledSource(mdxGetArgs2) + + return ( +
+

Test page with 2 dynamic retrieval from db

+
+

Content:

+ }> + + + +

Transcript

+ }> + + +
+ + +
+ ) +} \ No newline at end of file diff --git a/src/app/test/[courseSlug]/page.tsx b/src/app/test/[courseSlug]/page.tsx new file mode 100644 index 0000000..e2d02e9 --- /dev/null +++ b/src/app/test/[courseSlug]/page.tsx @@ -0,0 +1,34 @@ +import LoadingBars from "@/components/LoadingBars"; +import MDXRenderer from "@/components/MDXRenderer"; +import { MdxGetCompiledSourceProps, mdxGetCompiledSource } from "@/server/controllers/mdxController"; +import { Suspense } from "react"; + + +/** + * * TEST ROUTE + * * /test/first-course-updated + */ +export default async function TestPage1({ params }: { params: { courseSlug: string }}) { + const courseSlug = params.courseSlug; + /** + * TODO Why must I add the Props here for TS not to yell at me!? + */ + const mdxGetArgs: MdxGetCompiledSourceProps = { + courseSlug: courseSlug, + access: "PUBLIC", + } + const compiledMdx = await mdxGetCompiledSource(mdxGetArgs) + + return ( +
+

Test page with 1 dynamic retrieval from db

+
+ }> + + +
+ + +
+ ) +} \ No newline at end of file diff --git a/src/app/test/page.tsx b/src/app/test/page.tsx index 0ccf44c..721ba9b 100644 --- a/src/app/test/page.tsx +++ b/src/app/test/page.tsx @@ -1,6 +1,6 @@ import LoadingBars from "@/components/LoadingBars"; import MDXRenderer from "@/components/MDXRenderer"; -import { dbGetLessonContentOrLessonTranscriptById } from "@/server/controllers/coursesController" +import { dbGetMdxByModelId } from "@/server/controllers/coursesController" import { mdxCompiler } from "@/server/mdxCompiler"; import { Suspense } from "react"; @@ -9,7 +9,7 @@ import { Suspense } from "react"; export default async function TestPage() { // TODO all these serverside things can be put into a single controller function. - const dataString = await dbGetLessonContentOrLessonTranscriptById("cllv8cfcy0001u22swg51l885"); + const dataString = await dbGetMdxByModelId("cllv8cfcy0001u22swg51l885"); // TODO refactor this to its own function let incomingMarkdown: string; diff --git a/src/components/AdminCourseFormContainer.tsx b/src/components/AdminCourseFormContainer.tsx index 06bbdfd..a276c03 100644 --- a/src/components/AdminCourseFormContainer.tsx +++ b/src/components/AdminCourseFormContainer.tsx @@ -2,7 +2,7 @@ import CourseForm, { UpsertCourseInputs } from "./forms/CourseForm"; import { apiClientside } from "@/lib/trpc/trpcClientside"; -import { type dbGetCourseAndLessonsById } from "@/server/controllers/coursesController"; +import { type dbGetCourseAndDetailsAndLessonsById } from "@/server/controllers/coursesController"; import { useParams, useRouter } from "next/navigation"; import { type SubmitHandler } from "react-hook-form"; @@ -14,7 +14,7 @@ const AdminCourseFormContainer = ({ id }: { id?: string; - initialCourse?: Awaited> + initialCourse?: Awaited> }) => { const router = useRouter(); const params = useParams(); diff --git a/src/components/CourseCard.tsx b/src/components/CourseCard.tsx index 38df79b..bbc05da 100644 --- a/src/components/CourseCard.tsx +++ b/src/components/CourseCard.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @next/next/no-img-element */ import type { Course } from "@prisma/client"; import Link from 'next/link' import Heading from './Heading' diff --git a/src/components/Editor.tsx b/src/components/Editor.tsx index f6e4918..471ab3a 100644 --- a/src/components/Editor.tsx +++ b/src/components/Editor.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { forwardRef } from "react"; +import React, { createContext, forwardRef, useContext } from "react"; import dynamic from "next/dynamic"; import { headingsPlugin } from '@mdxeditor/editor/plugins/headings' import { listsPlugin } from '@mdxeditor/editor/plugins/lists' @@ -45,9 +45,11 @@ import { tablePlugin, } from "@mdxeditor/editor"; import { apiClientside } from "@/lib/trpc/trpcClientside"; -import { type dbGetLessonContentOrLessonTranscriptById } from "@/server/controllers/coursesController"; +import { type dbGetMdxByModelId } from "@/server/controllers/coursesController"; import Heading from "./Heading"; -import { Lesson } from "@prisma/client"; +import toast from "react-hot-toast"; +import LoadingBars from "./LoadingBars"; + /** * NextJS dynamic import so that client-side only is enforced. Must import wrapped editor to satisfy requirements of forwardRef. @@ -67,40 +69,44 @@ const ForwardedRefMDXEditor = forwardRef((prop */ ForwardedRefMDXEditor.displayName = "ForwardedRefMDXEditor"; +/** + * Context to hold the state of mutation loading as passing props did not work with the MDXEditor Toolbar. + */ +const EditorContext = createContext(false); + type EditorProps = { - initialLessonMaterial: Awaited>; - lessonName: Lesson["name"]; + initialMaterial: Awaited>; + title: string; } /** * MDX Editor that allows live, rich text editing of markdown files on the client. * Renders only on Clientside through Next's dynamic import and a forwardRef wrapping so * that useRef hook properly is passed down to the function. - * @param props includes LessonContent object and lessonName string + * @param props includes MDX string and title string */ -export default function Editor({ initialLessonMaterial, lessonName }: EditorProps) { - const editorRef = React.useRef(null) +export default function Editor({ initialMaterial, title }: EditorProps) { + const editorRef = React.useRef(null); const utils = apiClientside.useContext(); - const updateLessonMaterialMutation = apiClientside.courses.updateLessonContentOrLessonTranscript.useMutation({ + const updateMaterialMutation = apiClientside.courses.updateMdxByModelId.useMutation({ onSuccess: () => { - // toast.success('Course updated successfully') - console.log("success! lesson content updated/created") - utils.courses.getLessonContentOrLessonTranscriptById.invalidate(); + toast.success("Success! Saved to database.") + utils.courses.getMdxByModelId.invalidate(); }, onError: (error) => { console.error(error) - // toast.error('Something went wrong') + toast.error('Something went wrong') } - }) + }); - if (!initialLessonMaterial) throw new Error("lessonMaterial Data missing / could not be retrieved from server") + if (!initialMaterial) throw new Error("lessonMaterial Data missing / could not be retrieved from server"); - const {data: lessonMaterial} = apiClientside.courses.getLessonContentOrLessonTranscriptById.useQuery({ id: initialLessonMaterial.id}, { - initialData: initialLessonMaterial, + const {data: material} = apiClientside.courses.getMdxByModelId.useQuery({ id: initialMaterial.id}, { + initialData: initialMaterial, refetchOnMount: false, refetchOnReconnect: false, - }) + }); - if (!lessonMaterial) throw new Error("lessonMaterial Data missing / could not be retrieved from client query") + if (!material) throw new Error("lessonMaterial Data missing / could not be retrieved from client query"); const handleSave = async () => { const markdownValue = editorRef.current?.getMarkdown(); @@ -109,57 +115,46 @@ export default function Editor({ initialLessonMaterial, lessonName }: EditorProp return; } - updateLessonMaterialMutation.mutate({ - id: lessonMaterial.id, + updateMaterialMutation.mutate({ + id: material.id, content: markdownValue }); - } - - let incomingMarkdown: string; - let incomingType: string; - if ("transcript" in lessonMaterial) { - incomingMarkdown = lessonMaterial.transcript; - incomingType = "transcript"; - } else if ("content" in lessonMaterial) { - incomingMarkdown = lessonMaterial.content; - incomingType = "content"; - } else { - incomingType = "nothing"; - incomingMarkdown = "No content available."; - } + }; return ( <> - Editing {incomingType} of "{lessonName} " + Editing {material.mdxCategory.toLowerCase()} of "{title} " {/* //TODO BTN below only for testing, CLEANUP when done */} - ( - - ) - }) - ]} - /> + + ( + + ) + }) + ]} + /> +
) @@ -169,6 +164,7 @@ type DefaultToolbarProps = { handleSave: () => void; } const DefaultToolbar: React.FC = ({ handleSave }) => { + const isLoading = useContext(EditorContext); const handleSaveButton = () => { handleSave(); @@ -183,7 +179,12 @@ const DefaultToolbar: React.FC = ({ handleSave }) => { { fallback: () => ( <> - +
+ { isLoading + ? + : + } +
diff --git a/src/components/LoadingBars.tsx b/src/components/LoadingBars.tsx index 3847930..67b06eb 100644 --- a/src/components/LoadingBars.tsx +++ b/src/components/LoadingBars.tsx @@ -1,10 +1,25 @@ +import cn from "classnames"; + + +type LoadingBarsProps = { + size?: "xs" | "sm" | "md" | "lg" +} /** * Purely presentational UI component that shows DaisyUI loading bars centered in its scope. + * Can optionally control the size of the loading bars by passing "xs", "sm", "md" or "lg" as props. + * Defaults to "lg" (large) if no props are passed. */ -const LoadingBars = () => { +const LoadingBars = ({size = "lg"}: LoadingBarsProps) => { + const loadingBarClasses = cn("loading loading-bars", { + "loading-xs": size === "xs", + "loading-sm": size === "sm", + "loading-md": size === "md", + "loading-lg": size === "lg", + }); + return (
- +
) } diff --git a/src/components/forms/CourseForm.tsx b/src/components/forms/CourseForm.tsx index 3d01b46..a3e6161 100644 --- a/src/components/forms/CourseForm.tsx +++ b/src/components/forms/CourseForm.tsx @@ -4,7 +4,7 @@ import TextAreaInput from './TextAreaInput'; import SubmitInput from './SubmitInput'; import Checkbox from "./Checkbox"; import { Lesson, Course } from "@prisma/client"; -import { type dbGetCourseAndLessonsById } from "@/server/controllers/coursesController"; +import { type dbGetCourseAndDetailsAndLessonsById } from "@/server/controllers/coursesController"; export type UpsertCourseInputs = { id: string; @@ -17,7 +17,7 @@ export type UpsertCourseInputs = { }; type Props = { - course?: Awaited>; + course?: Awaited>; onSubmit: SubmitHandler; isLoading: boolean; } diff --git a/src/components/test/TodoList.tsx b/src/components/test/TodoList.tsx deleted file mode 100644 index 75bed91..0000000 --- a/src/components/test/TodoList.tsx +++ /dev/null @@ -1,22 +0,0 @@ -"use client"; - -import { apiClientside } from "@/lib/trpc/trpcClientside"; -import { apiServerside } from "@/lib/trpc/trpcServerside"; - -export default function TodoList({ - initialTodos -}: { - initialTodos: Awaited>; -}) { - const getTodos = apiClientside.fiction.getTodos.useQuery(undefined, { - initialData: initialTodos, - refetchOnMount: false, - refetchOnReconnect: false, - }); - - return ( -
-
{JSON.stringify(getTodos.data)}
-
- ) -} \ No newline at end of file diff --git a/src/components/test/TodoListSimple.tsx b/src/components/test/TodoListSimple.tsx deleted file mode 100644 index 88df7ae..0000000 --- a/src/components/test/TodoListSimple.tsx +++ /dev/null @@ -1,15 +0,0 @@ -"use client"; - -import { apiClientside } from "@/lib/trpc/trpcClientside"; -import { apiServerside } from "@/lib/trpc/trpcServerside"; - -export default function TodoList() { - const getTodos = apiClientside.fiction.getTodos.useQuery(); - - return ( -
-

From Simple useQuery with no options

-
{JSON.stringify(getTodos.data)}
-
- ) -} \ No newline at end of file diff --git a/src/server/api/routers/coursesRouter.ts b/src/server/api/routers/coursesRouter.ts index e78aef8..f4ff9d6 100644 --- a/src/server/api/routers/coursesRouter.ts +++ b/src/server/api/routers/coursesRouter.ts @@ -1,14 +1,13 @@ import { dbGetAllCourses, - dbGetCourseAndLessonsById, + dbGetCourseAndDetailsAndLessonsById, dbGetLessonAndRelationsById, - dbGetLessonContentOrLessonTranscriptById, + dbGetMdxByModelId, dbGetVideoByLessonId, - dbUpdateLessonContentOrLessonTranscriptById, + dbUpdateMdxByModelId, dbUpsertCourseById, dbUpsertLessonById, dbUpsertLessonContentById, - dbUpsertVideoById } from "@/server/controllers/coursesController"; import { createTRPCRouter, publicProcedure, protectedProcedure, protectedAdminProcedure } from "../trpc"; import * as z from "zod"; @@ -30,7 +29,7 @@ export const coursesRouter = createTRPCRouter({ ) .query(async (opts) => { if (opts.input.id) { - return await dbGetCourseAndLessonsById(opts.input.id); + return await dbGetCourseAndDetailsAndLessonsById(opts.input.id); } else { return null; } @@ -49,7 +48,7 @@ export const coursesRouter = createTRPCRouter({ return null; } }), - getLessonContentOrLessonTranscriptById: protectedAdminProcedure + getMdxByModelId: protectedAdminProcedure .input( z .object({ @@ -57,7 +56,7 @@ export const coursesRouter = createTRPCRouter({ }) ) .query(async (opts) => { - return await dbGetLessonContentOrLessonTranscriptById(opts.input.id); + return await dbGetMdxByModelId(opts.input.id); }), getVideoByLessonId: protectedAdminProcedure .input( @@ -100,19 +99,6 @@ export const coursesRouter = createTRPCRouter({ .mutation(async (opts) => { return await dbUpsertLessonById(opts.input); }), - // TODO CLEANUP - // upsertVideo: protectedAdminProcedure - // .input( - // z - // .object({ - // id: z.string().optional(), - // lessonId: z.string(), - // fileName: z.string().optional(), - // }) - // ) - // .mutation(async (opts) => { - // return await dbUpsertVideoById(opts.input); - // }), upsertLessonContent: protectedAdminProcedure //TODO schedule for deletion and CLEANUP .input( z @@ -125,7 +111,7 @@ export const coursesRouter = createTRPCRouter({ .mutation(async (opts) => { return await dbUpsertLessonContentById(opts.input); }), - updateLessonContentOrLessonTranscript: protectedAdminProcedure + updateMdxByModelId: protectedAdminProcedure .input( z .object({ @@ -134,6 +120,6 @@ export const coursesRouter = createTRPCRouter({ }) ) .mutation(async (opts) => { - return await dbUpdateLessonContentOrLessonTranscriptById(opts.input); + return await dbUpdateMdxByModelId(opts.input); }) }) \ No newline at end of file diff --git a/src/server/api/routers/fiction.ts b/src/server/api/routers/fiction.ts index c454f95..a81c2c4 100644 --- a/src/server/api/routers/fiction.ts +++ b/src/server/api/routers/fiction.ts @@ -1,37 +1,9 @@ -import { dbGetAllUsers, multiplyFunc, testFunc } from "@/server/controllers/controller"; import { createTRPCRouter, publicProcedure, protectedProcedure, protectedAdminProcedure } from "../trpc"; import { z } from "zod"; // TODO delete this example router when no longer useful export const fictionRouter = createTRPCRouter({ - getTodos: publicProcedure.query(async () => { - const result = testFunc() - return result; - }), - multiply: publicProcedure - .input( - z - .object({ - multiplier: z.number(), - multiplicand: z.number(), - }) - ) - .output( - z - .object({ - result: z.number(), - }) - ) - .query(async (opts) => { - const result = multiplyFunc(opts.input.multiplier, opts.input.multiplicand); - return { result }; - }), - getAllUsers: publicProcedure - .query(async () => { - const result = dbGetAllUsers(); - return { result }; - }), getSecretMessage: protectedProcedure.query(() => { return "you can now see this secret message! | means you are logged in"; }), diff --git a/src/server/auth.ts b/src/server/auth.ts index dec4b19..67faff2 100644 --- a/src/server/auth.ts +++ b/src/server/auth.ts @@ -42,6 +42,7 @@ export const authOptions: NextAuthOptions = { ], }; +export type Access = "PUBLIC" | "USER" | "ADMIN"; /** * Helper function to get Auth Session on serverside. */ diff --git a/src/server/controllers/controller.ts b/src/server/controllers/controller.ts deleted file mode 100644 index 0678e7a..0000000 --- a/src/server/controllers/controller.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { prisma } from "../db"; - -// TODO examples to test with, CLEANUP once done -export const testFunc = () => { - return [10, 20, 30, 40, 50] -} - -export const multiplyFunc = (multiplier: number, multiplicand: number,) => { - const result = multiplier * multiplicand; - return result; -} - -export const dbGetAllUsers = async () => { - const result = await prisma.user.findMany(); - console.log(result); - return result; -} \ No newline at end of file diff --git a/src/server/controllers/coursesController.ts b/src/server/controllers/coursesController.ts index 31d9bc9..ff3066d 100644 --- a/src/server/controllers/coursesController.ts +++ b/src/server/controllers/coursesController.ts @@ -1,6 +1,6 @@ import { prisma } from "../db"; import * as z from "zod"; -import { LessonContent, LessonTranscript } from "@prisma/client"; +import { Course, CourseDetails, LessonContent, LessonTranscript } from "@prisma/client"; import { exclude } from "@/utils/utils"; import { AuthenticationError, requireAdminAuth } from "@/server/auth"; @@ -66,10 +66,10 @@ export const dbGetCourseBySlug = async (slug: string) => { } /** - * Calls the database to retrieve specific course and lessons by id identifier. + * Calls the database to retrieve specific course, its course details and lessons by id identifier. * @access "ADMIN"" */ -export const dbGetCourseAndLessonsById = async (id: string) => { +export const dbGetCourseAndDetailsAndLessonsById = async (id: string) => { try { const validId = z.string().parse(id); await requireAdminAuth(); @@ -78,7 +78,12 @@ export const dbGetCourseAndLessonsById = async (id: string) => { id: validId, }, include: { - lessons: true + lessons: true, + details: { + select: { + id: true + } + } } }); } catch (error) { @@ -127,40 +132,76 @@ export const dbGetLessonAndRelationsById = async (id: string) => { } /** - * Calls the database to retrieve specific lessonContent or lessonTranscript by id identifier. + * Calls the database to retrieve mdx field by id of the model as identifier. * Converts binary content of found record to string so that it can pass the tRPC network boundary - * and/or be passed down to Client Components from Server Components. - * @access "ADMIN"" + * and/or be passed down to Client Components from Server Components. + * @supports LessonContent | LessonTranscript | CourseDetails + * @access "ADMIN" | "INTERNAL" (add `true` as second argument to bypass Auth check) */ -export const dbGetLessonContentOrLessonTranscriptById = async (id: string) => { +export const dbGetMdxByModelId = async (id: string, internal?: boolean) => { try { - await requireAdminAuth(); + if (!internal) await requireAdminAuth(); const validId = z.string().parse(id); + /** + * tRPC cannot handle binary transmission, so the buffer from each below must be converted to string. + * And then new object is made from shallow copy of the result with the updated content field. + * + * First we look for a match in the LessonContent Model + */ const lessonContent = await prisma.lessonContent.findUnique({ where: { id: validId, }, }); if (!lessonContent) { + /** + * If no matching id found, proceed to query LessonTranscript. + */ const lessonTranscript = await prisma.lessonTranscript.findUnique({ where: { id: validId, } - }) - if (!lessonTranscript) throw new Error("No lessonTranscript or lessonContent found at db call") - const transcriptAsString = lessonTranscript.transcript.toString("utf-8"); + }); + if (!lessonTranscript) { + /** + * If no matching id found, proceed to query CourseDetails. + */ + const courseDetails = await prisma.courseDetails.findUnique({ + where: { + id: validId, + } + }); + /** + * All three query attempts failed, throw error. + */ + if (!courseDetails) throw new Error("No lessonTranscript, lessonContent or courseDetails found at db call") + /** + * Resolve third attempt if query successful. + */ + const courseDetailsContentAsString = courseDetails.mdx.toString("utf-8"); + const newResult = { + ...courseDetails, + mdx: courseDetailsContentAsString, + } + return newResult; + } + /** + * Resolve second attempt if query successful. + */ + const transcriptAsString = lessonTranscript.mdx.toString("utf-8"); const newResult = { ...lessonTranscript, - transcript: transcriptAsString, + mdx: transcriptAsString, } return newResult; }; - // tRPC cannot handle binary transmission, so the buffer must be converted to string here. - const contentAsString = lessonContent.content.toString("utf-8"); - // New object is made from shallow copy of the result with the updated content field. + /** + * Resolve first attempt if query successful. + */ + const contentAsString = lessonContent.mdx.toString("utf-8"); const newResult = { ...lessonContent, - content: contentAsString, + mdx: contentAsString, } return newResult; } catch (error) { @@ -170,6 +211,91 @@ export const dbGetLessonContentOrLessonTranscriptById = async (id: string) => { throw new Error("An error occurred while fetching the course."); } } +export type DBGetMdxContentByModelIdReturnType = Awaited>; + +export type LessonTypes = "CONTENT" | "TRANSCRIPT"; +export type DBGetMdxContentBySlugsProps = { + courseSlug: string; +} & ( + { + lessonSlug: string, + lessonType: LessonTypes; + } | { + lessonSlug?: never; + lessonType?: never; + } +) +/** + * Get uncompiled MDX by Course slug and/or Lesson slug. If only Course slug is provided, the + * function will attempt to find and retrieve the MDX of the CourseDetails that is + * related to this course. To get the MDX pertaining to a Lesson, a lessonType must + * be specified. + * @returns object with uncompiled MDX || placeholder string if data model non-existent + */ +export const dbGetMdxBySlugs = async ({ + courseSlug, + lessonSlug, + lessonType, +}: DBGetMdxContentBySlugsProps) => { + const validCourseSlug = z.string().parse(courseSlug); + const validLessonSlug = z.string().optional().parse(lessonSlug); + /** + * Since a Course may exist without CourseDetails, and Lesson may exist + * without LessonContent and LessonTranscript, instead of throwing an error + * a string is returned when the respective MDX data models are non-existant. + */ + if (validCourseSlug && validLessonSlug && lessonType) { + if (lessonType === "CONTENT") { + const lessonContent = await prisma.lessonContent.findFirst({ + where: { + lesson: { + slug: validLessonSlug, + course: { + slug: validCourseSlug, + } + } + }, + select: { + id: true, + } + }) + if (!lessonContent) return "No lesson content"; + return dbGetMdxByModelId(lessonContent.id, true); + } + if (lessonType === "TRANSCRIPT") { + const lessonTranscript = await prisma.lessonTranscript.findFirst({ + where: { + lesson: { + slug: validLessonSlug, + course: { + slug: validCourseSlug, + } + } + }, + select: { + id: true, + } + }) + if (!lessonTranscript) return "No lesson transcript"; + return dbGetMdxByModelId(lessonTranscript.id, true); + } + } + if (validCourseSlug) { + const courseDetails = await prisma.courseDetails.findFirst({ + where: { + course: { + slug: validCourseSlug, + } + }, + select: { + id: true, + } + }); + if (!courseDetails) return "No course details"; + return dbGetMdxByModelId(courseDetails.id, true); + } + throw new Error("Error occured when attempting to find data models by slug(s)") +} /** * Calls the database to retrieve specific Video entry based on the ID of the Lesson it is related to. @@ -334,15 +460,15 @@ export const dbUpsertLessonContentById = async ({ id: validId }, update: { - content: contentAsBuffer, + mdx: contentAsBuffer, }, create: { lessonId: validLessonId, - content: contentAsBuffer + mdx: contentAsBuffer } }); - const resultWithoutContent = exclude(result, ["content"]) + const resultWithoutContent = exclude(result, ["mdx"]) return resultWithoutContent; } catch (error) { if (error instanceof AuthenticationError) { @@ -377,15 +503,15 @@ export const dbUpsertLessonTranscriptById = async ({ id: validId }, update: { - transcript: contentAsBuffer, + mdx: contentAsBuffer, }, create: { lessonId: validLessonId, - transcript: contentAsBuffer + mdx: contentAsBuffer } }); - const resultWithoutTranscript = exclude(result, ["transcript"]) + const resultWithoutTranscript = exclude(result, ["mdx"]) return resultWithoutTranscript; } catch (error) { if (error instanceof AuthenticationError) { @@ -395,16 +521,59 @@ export const dbUpsertLessonTranscriptById = async ({ } } +/** + * Updates an existing CourseDetails model by id as identifier or creates a new one if id is not provided. + * Must have the id of the Course this CourseDetails relates to. + * @access "ADMIN"" + */ +export const dbUpsertCourseDetailsById = async ({ + id, courseId, content +}: { + id?: CourseDetails["id"], + courseId: Course["id"], + content: string, +}) => { + try { + await requireAdminAuth(); + + const validId = id ? z.string().parse(id) : "x"; // Prisma needs id of some value + const validCourseId = z.string().parse(courseId); + + const contentAsBuffer = Buffer.from(content, 'utf-8'); + + const result = await prisma.courseDetails.upsert({ + where: { + id: validId + }, + update: { + mdx: contentAsBuffer, + }, + create: { + courseId: validCourseId, + mdx: contentAsBuffer + } + }); + + const resultWithoutContent = exclude(result, ["mdx"]) + return resultWithoutContent; + } catch (error) { + if (error instanceof AuthenticationError) { + throw error; // Rethrow custom error as-is + } + throw new Error("An error occurred while upserting CourseDetails."); + } +} + /** * Updates an existing lessonContent or lessonTranscript by id as identifier. * @description If lessonContent, updates the content field. * @description If lessonTranscript, updates the transcript field. * @access "ADMIN"" */ -export const dbUpdateLessonContentOrLessonTranscriptById = async ({ +export const dbUpdateMdxByModelId = async ({ id, content }: { - id: LessonContent["id"] | LessonTranscript["id"], + id: LessonContent["id"] | LessonTranscript["id"] | CourseDetails["id"], content: string, }) => { try { @@ -422,12 +591,13 @@ export const dbUpdateLessonContentOrLessonTranscriptById = async ({ * @see {@link https://www.prisma.io/docs/concepts/components/prisma-client/raw-database-access#executeraw PrismaExecuteRaw} * @see {@link https://www.cockroachlabs.com/docs/stable/sql-statements SQL@CockroachDB} */ - let result = await prisma.$executeRaw`UPDATE "LessonContent" SET content = ${contentAsBuffer} WHERE id = ${validId};` - + let result = await prisma.$executeRaw`UPDATE "LessonContent" SET mdx = ${contentAsBuffer} WHERE id = ${validId};` if (result === 0) { - result = await prisma.$executeRaw`UPDATE "LessonTranscript" SET transcript = ${contentAsBuffer} WHERE id = ${validId};` + result = await prisma.$executeRaw`UPDATE "LessonTranscript" SET mdx = ${contentAsBuffer} WHERE id = ${validId};` + } + if (result === 0) { + result = await prisma.$executeRaw`UPDATE "CourseDetails" SET mdx = ${contentAsBuffer} WHERE id = ${validId};` } - if (result === 1) { return; } else { diff --git a/src/server/controllers/gcController.ts b/src/server/controllers/gcController.ts index 9dd1c78..e92db2e 100644 --- a/src/server/controllers/gcController.ts +++ b/src/server/controllers/gcController.ts @@ -114,4 +114,12 @@ export const gcGenerateReadSignedUrl = async ({ } catch(error) { throw new Error("An error occurred while attempting to delete file in storage."); } -} \ No newline at end of file +} + +// Optional for Options: +// Set a generation-match precondition to avoid potential race conditions +// and data corruptions. The request to upload is aborted if the object's +// generation number does not match your precondition. For a destination +// object that does not yet exist, set the ifGenerationMatch precondition to 0 +// If the destination object already exists in your bucket, set instead a +// generation-match precondition using its generation number. \ No newline at end of file diff --git a/src/server/controllers/gcControllerDel.ts b/src/server/controllers/gcControllerDel.ts deleted file mode 100644 index 94f73e6..0000000 --- a/src/server/controllers/gcControllerDel.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { type GenerateSignedPostPolicyV4Options } from "@google-cloud/storage"; -import { bucket } from "../bucket"; - -// TODO - FILE TO BE DELETED - -// export const gcGenerateSignedPostUploadURL = async (fileName: string) => { -// try { -// const filePath = `video/${fileName}` -// const file = bucket.file(filePath); -// const options: GenerateSignedPostPolicyV4Options = { -// expires: Date.now() + 1 * 60 * 1000, // 1 minute, -// fields: { 'x-goog-meta-test': 'data' }, -// }; -// const [response] = await file.generateSignedPostPolicyV4(options); -// return response; -// } catch(error) { -// throw new Error("An error occurred while attempting to get the upload link."); -// } -// } - -function test( - bucketName = 'my-bucket', - filePath = './local/path/to/file.txt', - destFileName = 'file.txt', - generationMatchPrecondition = 0 - ) { - // [START storage_upload_file] - /** - * TODO(developer): Uncomment the following lines before running the sample. - */ - // The ID of your GCS bucket - // const bucketName = 'your-unique-bucket-name'; - - // The path to your file to upload - // const filePath = 'path/to/your/file'; - - // The new ID for your GCS file - // const destFileName = 'your-new-file-name'; - - // Imports the Google Cloud client library - const {Storage} = require('@google-cloud/storage'); - - // Creates a client - const storage = new Storage(); - - async function uploadFile() { - const options = { - destination: destFileName, - // Optional: - // Set a generation-match precondition to avoid potential race conditions - // and data corruptions. The request to upload is aborted if the object's - // generation number does not match your precondition. For a destination - // object that does not yet exist, set the ifGenerationMatch precondition to 0 - // If the destination object already exists in your bucket, set instead a - // generation-match precondition using its generation number. - preconditionOpts: {ifGenerationMatch: generationMatchPrecondition}, - }; - - await storage.bucket(bucketName).upload(filePath, options); - console.log(`${filePath} uploaded to ${bucketName}`); - } - - uploadFile().catch(console.error); - // [END storage_upload_file] -} - \ No newline at end of file diff --git a/src/server/controllers/mdxController.ts b/src/server/controllers/mdxController.ts index d844018..8880ea8 100644 --- a/src/server/controllers/mdxController.ts +++ b/src/server/controllers/mdxController.ts @@ -1,27 +1,58 @@ -import { AuthenticationError, requireAdminAuth } from "../auth"; +import { z } from "zod"; +import { type Access, AuthenticationError, requireAdminAuth } from "../auth"; +import { dbGetMdxBySlugs, DBGetMdxContentBySlugsProps } from "./coursesController"; +import { mdxCompiler } from "../mdxCompiler"; -type MdxGetCompiledSourceProps = { - courseSlug: string; + +export type MdxGetCompiledSourceProps = { partSlug?: string; - lessonSlug?: string; - access: "PUBLIC" | "USER" | "ADMIN"; -} + access: Access; +} & DBGetMdxContentBySlugsProps; /** - * WIP + * Get compiled, render-ready MDX by Course slug and/or Lesson slug identifiers. + * If only Course slug is provided, it will attempt to retrieve MDX from CourseDetails, + * otherwise lesson type must be added along with Lesson slug to retrieve MDX from + * either LessonContent or LessonTranscript. If records are nonexistent for any of these + * MDX models, then a placeholder string is returned. + * @access Access must be specified. */ -const mdxGetCompiledSource = async ({ - courseSlug, partSlug, lessonSlug, access +export const mdxGetCompiledSource = async ({ + courseSlug, partSlug, lessonSlug, lessonType, access }: MdxGetCompiledSourceProps) => { try { - await requireAdminAuth(); - - - - - - return; + /** + * Authentication and authorization + */ + if (access === "ADMIN") { + await requireAdminAuth(); + } else if (access === "USER") { + // TODO perform "USER" checks here + } + /** + * Validation and retrieval from db + */ + const dbGetArgs = lessonSlug && lessonType + ? + { + courseSlug: z.string().parse(courseSlug), + lessonSlug: z.string().parse(lessonSlug), + lessonType: lessonType, + } + : + { + courseSlug: z.string().parse(courseSlug), + } + const uncompiledMdxContainer = await dbGetMdxBySlugs(dbGetArgs); + /** + * If records are non-existent in db, placeholder strings will be returned. + * Otherwise, an object of one of many possible models is returned, + * which needs to be filtered for the MDX string before it is compiled. + */ + if (typeof uncompiledMdxContainer === "string") { + return await mdxCompiler(uncompiledMdxContainer); + } + return await mdxCompiler(uncompiledMdxContainer.mdx); } catch (error) { - if (error instanceof AuthenticationError) { throw error; // Rethrow custom error as-is } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index be084d5..bb7d3c4 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,3 +1,4 @@ +import { DBGetMdxContentByModelIdReturnType } from "@/server/controllers/coursesController"; import { env } from "process"; /**