diff --git a/.env.sample b/.env.sample index 107936e..e53597e 100644 --- a/.env.sample +++ b/.env.sample @@ -1,18 +1,18 @@ DATABASE_URL='postgresql://postgres:postgres@postgres:5432/postgres' -GOOGLE_CLIENT_ID= -GOOGLE_CLIENT_SECRET= -GITHUB_CLIENT_SECRET= -GITHUB_CLIENT_ID= -DISCORD_CLIENT_ID= -DISCORD_CLIENT_SECRET= +GOOGLE_CLIENT_ID='' +GOOGLE_CLIENT_SECRET='' +GITHUB_CLIENT_SECRET='' +GITHUB_CLIENT_ID='' +DISCORD_CLIENT_ID='' +DISCORD_CLIENT_SECRET='' RAZORPAY_ID= RAZORPAY_SECRET= RAZORPAY_WEBHOOK_SECRET= APP_NAME='http://localhost:3000' -JWT_SECRET='' +JWT_SECRET='' -GMAIL_PASSWORD= -GMAIL_USER= \ No newline at end of file +GMAIL_PASSWORD='' +GMAIL_USER='' diff --git a/.gitignore b/.gitignore index 29e3d76..b156cf9 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,4 @@ yarn-error.log* *.tsbuildinfo next-env.d.ts -prisma/migrations - certificates \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index 283b4f1..7545ef7 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -5,9 +5,13 @@ npx prisma migrate deploy npx prisma generate # Run database migrations -npx prisma migrate dev --name init -npm install @node-rs/argon2-linux-arm64-musl +npx prisma migrate dev --name init + +# Install a cross-platform version of argon2 +npm install argon2 + +# Run the seed script npm run seed # Run the main container command -exec "$@" \ No newline at end of file +exec "$@" diff --git a/package-lock.json b/package-lock.json index 162cf4e..217600c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,11 +30,8 @@ "@radix-ui/react-tooltip": "^1.1.2", "@t3-oss/env-core": "^0.11.0", "@t3-oss/env-nextjs": "^0.11.0", - "@types/canvas-confetti": "^1.6.4", - "@types/jsonwebtoken": "^9.0.6", - "@types/lodash.throttle": "^4.1.9", - "@types/nodemailer": "^6.4.15", "arctic": "^1.9.2", + "argon2": "0.41.0", "canvas-confetti": "^1.9.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -67,7 +64,11 @@ "zustand": "^4.5.5" }, "devDependencies": { + "@types/canvas-confetti": "^1.6.4", + "@types/jsonwebtoken": "^9.0.6", + "@types/lodash.throttle": "^4.1.9", "@types/node": "^22.1.0", + "@types/nodemailer": "^6.4.15", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", @@ -107,34 +108,6 @@ "resolved": "https://registry.npmjs.org/@corex/deepmerge/-/deepmerge-4.0.43.tgz", "integrity": "sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==" }, - "node_modules/@emnapi/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.2.0.tgz", - "integrity": "sha512-E7Vgw78I93we4ZWdYCb4DGAwRROGkMIXk7/y87UmANR+J6qsWusmC3gLt0H+O0KOt5e6O38U8oJamgbudrES/w==", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.0.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz", - "integrity": "sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz", - "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "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", @@ -380,365 +353,28 @@ "lucia": "3.x" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.4.tgz", - "integrity": "sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.1.0", - "@emnapi/runtime": "^1.1.0", - "@tybys/wasm-util": "^0.9.0" - } - }, "node_modules/@next/env": { "version": "14.2.5", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.5.tgz", "integrity": "sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==", "license": "MIT" - }, - "node_modules/@next/eslint-plugin-next": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz", - "integrity": "sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "10.3.10" - } - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", - "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", - "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", - "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", - "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", - "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", - "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", - "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", - "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", - "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.8.3.tgz", - "integrity": "sha512-sf/QAEI59hsMEEE2J8vO4hKrXrv4Oplte3KI2N4MhMDYpytH0drkVfErmHBfWFZxxIEK03fX1WsBNswS2nIZKg==", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@node-rs/argon2-android-arm-eabi": "1.8.3", - "@node-rs/argon2-android-arm64": "1.8.3", - "@node-rs/argon2-darwin-arm64": "1.8.3", - "@node-rs/argon2-darwin-x64": "1.8.3", - "@node-rs/argon2-freebsd-x64": "1.8.3", - "@node-rs/argon2-linux-arm-gnueabihf": "1.8.3", - "@node-rs/argon2-linux-arm64-gnu": "1.8.3", - "@node-rs/argon2-linux-arm64-musl": "1.8.3", - "@node-rs/argon2-linux-x64-gnu": "1.8.3", - "@node-rs/argon2-linux-x64-musl": "1.8.3", - "@node-rs/argon2-wasm32-wasi": "1.8.3", - "@node-rs/argon2-win32-arm64-msvc": "1.8.3", - "@node-rs/argon2-win32-ia32-msvc": "1.8.3", - "@node-rs/argon2-win32-x64-msvc": "1.8.3" - } - }, - "node_modules/@node-rs/argon2-android-arm-eabi": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-1.8.3.tgz", - "integrity": "sha512-JFZPlNM0A8Og+Tncb8UZsQrhEMlbHBXPsT3hRoKImzVmTmq28Os0ucFWow0AACp2coLHBSydXH3Dh0lZup3rWw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2-android-arm64": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-1.8.3.tgz", - "integrity": "sha512-zaf8P3T92caeW2xnMA7P1QvRA4pIt/04oilYP44XlTCtMye//vwXDMeK53sl7dvYiJKnzAWDRx41k8vZvpZazg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2-darwin-arm64": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-1.8.3.tgz", - "integrity": "sha512-DV/IbmLGdNXBtXb5o2UI5ba6kvqXqPAJgmMOTUCuHeBSp992GlLHdfU4rzGu0dNrxudBnunNZv+crd0YdEQSUA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2-darwin-x64": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-1.8.3.tgz", - "integrity": "sha512-YMjmBGFZhLfYjfQ2gll9A+BZu/zAMV7lWZIbKxb7ZgEofILQwuGmExjDtY3Jplido/6leCEdpmlk2oIsME00LA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2-freebsd-x64": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-1.8.3.tgz", - "integrity": "sha512-Hq3Rj5Yb2RolTG/luRPnv+XiGCbi5nAK25Pc8ou/tVapwX+iktEm/NXbxc5zsMxraYVkCvfdwBjweC5O+KqCGw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2-linux-arm-gnueabihf": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-1.8.3.tgz", - "integrity": "sha512-x49l8RgzKoG0/V0IXa5rrEl1TcJEc936ctlYFvqcunSOyowZ6kiWtrp1qrbOR8gbaNILl11KTF52vF6+h8UlEQ==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2-linux-arm64-gnu": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-1.8.3.tgz", - "integrity": "sha512-gJesam/qA63reGkb9qJ2TjFSLBtY41zQh2oei7nfnYsmVQPuHHWItJxEa1Bm21SPW53gZex4jFJbDIgj0+PxIw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2-linux-arm64-musl": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-1.8.3.tgz", - "integrity": "sha512-7O6kQdSKzB4Tjx/EBa8zKIxnmLkQE8VdJgPm6Ksrpn+ueo0mx2xf76fIDnbbTCtm3UbB+y+FkTo2wLA7tOqIKg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2-linux-x64-gnu": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.8.3.tgz", - "integrity": "sha512-OBH+EFG7BGjFyldaao2H2gSCLmjtrrwf420B1L+lFn7JLW9UAjsIPFKAcWsYwPa/PwYzIge9Y7SGcpqlsSEX0w==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2-linux-x64-musl": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-1.8.3.tgz", - "integrity": "sha512-bDbMuyekIxZaN7NaX+gHVkOyABB8bcMEJYeRPW1vCXKHj3brJns1wiUFSxqeUXreupifNVJlQfPt1Y5B/vFXgQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@node-rs/argon2-wasm32-wasi": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-1.8.3.tgz", - "integrity": "sha512-NBf2cMCDbNKMzp13Pog8ZPmI0M9U4Ak5b95EUjkp17kdKZFds12dwW67EMnj7Zy+pRqby2QLECaWebDYfNENTg==", - "cpu": [ - "wasm32" - ], - "optional": true, + }, + "node_modules/@next/eslint-plugin-next": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz", + "integrity": "sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==", + "dev": true, + "license": "MIT", "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.3" - }, - "engines": { - "node": ">=14.0.0" + "glob": "10.3.10" } }, - "node_modules/@node-rs/argon2-win32-arm64-msvc": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-1.8.3.tgz", - "integrity": "sha512-AHpPo7UbdW5WWjwreVpgFSY0o1RY4A7cUFaqDXZB2OqEuyrhMxBdZct9PX7PQKI18D85pLsODnR+gvVuTwJ6rQ==", + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz", + "integrity": "sha512-tEQ7oinq1/CjSG9uSTerca3v4AZ+dFa+4Yu6ihaG8Ud8ddqLQgFGcnwYls13H5X5CPDPZJdYxyeMui6muOLd4g==", "cpu": [ - "arm64" + "x64" ], "optional": true, "os": [ @@ -748,19 +384,28 @@ "node": ">= 10" } }, - "node_modules/@node-rs/argon2-win32-ia32-msvc": { + "node_modules/@node-rs/argon2": { "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-1.8.3.tgz", - "integrity": "sha512-bqzn2rcQkEwCINefhm69ttBVVkgHJb/V03DdBKsPFtiX6H47axXKz62d1imi26zFXhOEYxhKbu3js03GobJOLw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], + "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.8.3.tgz", + "integrity": "sha512-sf/QAEI59hsMEEE2J8vO4hKrXrv4Oplte3KI2N4MhMDYpytH0drkVfErmHBfWFZxxIEK03fX1WsBNswS2nIZKg==", "engines": { "node": ">= 10" + }, + "optionalDependencies": { + "@node-rs/argon2-android-arm-eabi": "1.8.3", + "@node-rs/argon2-android-arm64": "1.8.3", + "@node-rs/argon2-darwin-arm64": "1.8.3", + "@node-rs/argon2-darwin-x64": "1.8.3", + "@node-rs/argon2-freebsd-x64": "1.8.3", + "@node-rs/argon2-linux-arm-gnueabihf": "1.8.3", + "@node-rs/argon2-linux-arm64-gnu": "1.8.3", + "@node-rs/argon2-linux-arm64-musl": "1.8.3", + "@node-rs/argon2-linux-x64-gnu": "1.8.3", + "@node-rs/argon2-linux-x64-musl": "1.8.3", + "@node-rs/argon2-wasm32-wasi": "1.8.3", + "@node-rs/argon2-win32-arm64-msvc": "1.8.3", + "@node-rs/argon2-win32-ia32-msvc": "1.8.3", + "@node-rs/argon2-win32-x64-msvc": "1.8.3" } }, "node_modules/@node-rs/argon2-win32-x64-msvc": { @@ -807,22 +452,6 @@ "@node-rs/bcrypt-win32-x64-msvc": "1.9.0" } }, - "node_modules/@node-rs/bcrypt-linux-arm64-musl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@node-rs/bcrypt-linux-arm64-musl/-/bcrypt-linux-arm64-musl-1.9.0.tgz", - "integrity": "sha512-/sIvKDABOI8QOEnLD7hIj02BVaNOuCIWBKvxcJOt8+TuwJ6zmY1UI5kSv9d99WbiHjTp97wtAUbZQwauU4b9ew==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "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", @@ -858,6 +487,14 @@ "node": ">= 8" } }, + "node_modules/@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1928,19 +1565,11 @@ } } }, - "node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", - "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/canvas-confetti": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/@types/canvas-confetti/-/canvas-confetti-1.6.4.tgz", - "integrity": "sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==" + "integrity": "sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==", + "dev": true }, "node_modules/@types/d3-array": { "version": "3.2.1", @@ -2007,6 +1636,7 @@ "version": "9.0.6", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -2016,12 +1646,14 @@ "version": "4.17.7", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", + "dev": true, "license": "MIT" }, "node_modules/@types/lodash.throttle": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz", "integrity": "sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==", + "dev": true, "license": "MIT", "dependencies": { "@types/lodash": "*" @@ -2031,6 +1663,7 @@ "version": "22.5.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz", "integrity": "sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -2040,6 +1673,7 @@ "version": "6.4.15", "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz", "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -2267,283 +1901,67 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arctic": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/arctic/-/arctic-1.9.2.tgz", - "integrity": "sha512-VTnGpYx+ypboJdNrWnK17WeD7zN/xSCHnpecd5QYsBfVZde/5i+7DJ1wrf/ioSDMiEjagXmyNWAE3V2C9f1hNg==", - "license": "MIT", - "dependencies": { - "oslo": "1.2.0" - } - }, - "node_modules/arctic/node_modules/@emnapi/core": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-0.45.0.tgz", - "integrity": "sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/arctic/node_modules/@emnapi/runtime": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", - "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.7.0.tgz", - "integrity": "sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@node-rs/argon2-android-arm-eabi": "1.7.0", - "@node-rs/argon2-android-arm64": "1.7.0", - "@node-rs/argon2-darwin-arm64": "1.7.0", - "@node-rs/argon2-darwin-x64": "1.7.0", - "@node-rs/argon2-freebsd-x64": "1.7.0", - "@node-rs/argon2-linux-arm-gnueabihf": "1.7.0", - "@node-rs/argon2-linux-arm64-gnu": "1.7.0", - "@node-rs/argon2-linux-arm64-musl": "1.7.0", - "@node-rs/argon2-linux-x64-gnu": "1.7.0", - "@node-rs/argon2-linux-x64-musl": "1.7.0", - "@node-rs/argon2-wasm32-wasi": "1.7.0", - "@node-rs/argon2-win32-arm64-msvc": "1.7.0", - "@node-rs/argon2-win32-ia32-msvc": "1.7.0", - "@node-rs/argon2-win32-x64-msvc": "1.7.0" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2-android-arm-eabi": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-1.7.0.tgz", - "integrity": "sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2-android-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-1.7.0.tgz", - "integrity": "sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2-darwin-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-1.7.0.tgz", - "integrity": "sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2-darwin-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-1.7.0.tgz", - "integrity": "sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2-freebsd-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-1.7.0.tgz", - "integrity": "sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2-linux-arm-gnueabihf": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-1.7.0.tgz", - "integrity": "sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2-linux-arm64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-1.7.0.tgz", - "integrity": "sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2-linux-arm64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-1.7.0.tgz", - "integrity": "sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2-linux-x64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.7.0.tgz", - "integrity": "sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/arctic/node_modules/@node-rs/argon2-linux-x64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-1.7.0.tgz", - "integrity": "sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/arctic/node_modules/@node-rs/argon2-wasm32-wasi": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-1.7.0.tgz", - "integrity": "sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==", - "cpu": [ - "wasm32" - ], - "optional": true, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", "dependencies": { - "@emnapi/core": "^0.45.0", - "@emnapi/runtime": "^0.45.0", - "@tybys/wasm-util": "^0.8.1", - "memfs-browser": "^3.4.13000" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=14.0.0" + "node": ">= 8" } }, - "node_modules/arctic/node_modules/@node-rs/argon2-win32-arm64-msvc": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-1.7.0.tgz", - "integrity": "sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" + "node_modules/arctic": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/arctic/-/arctic-1.9.2.tgz", + "integrity": "sha512-VTnGpYx+ypboJdNrWnK17WeD7zN/xSCHnpecd5QYsBfVZde/5i+7DJ1wrf/ioSDMiEjagXmyNWAE3V2C9f1hNg==", + "license": "MIT", + "dependencies": { + "oslo": "1.2.0" } }, - "node_modules/arctic/node_modules/@node-rs/argon2-win32-ia32-msvc": { + "node_modules/arctic/node_modules/@node-rs/argon2": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-1.7.0.tgz", - "integrity": "sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], + "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.7.0.tgz", + "integrity": "sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==", "engines": { "node": ">= 10" + }, + "optionalDependencies": { + "@node-rs/argon2-android-arm-eabi": "1.7.0", + "@node-rs/argon2-android-arm64": "1.7.0", + "@node-rs/argon2-darwin-arm64": "1.7.0", + "@node-rs/argon2-darwin-x64": "1.7.0", + "@node-rs/argon2-freebsd-x64": "1.7.0", + "@node-rs/argon2-linux-arm-gnueabihf": "1.7.0", + "@node-rs/argon2-linux-arm64-gnu": "1.7.0", + "@node-rs/argon2-linux-arm64-musl": "1.7.0", + "@node-rs/argon2-linux-x64-gnu": "1.7.0", + "@node-rs/argon2-linux-x64-musl": "1.7.0", + "@node-rs/argon2-wasm32-wasi": "1.7.0", + "@node-rs/argon2-win32-arm64-msvc": "1.7.0", + "@node-rs/argon2-win32-ia32-msvc": "1.7.0", + "@node-rs/argon2-win32-x64-msvc": "1.7.0" } }, "node_modules/arctic/node_modules/@node-rs/argon2-win32-x64-msvc": { @@ -2561,15 +1979,6 @@ "node": ">= 10" } }, - "node_modules/arctic/node_modules/@tybys/wasm-util": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.8.3.tgz", - "integrity": "sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/arctic/node_modules/oslo": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/oslo/-/oslo-1.2.0.tgz", @@ -2586,6 +1995,20 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, + "node_modules/argon2": { + "version": "0.41.0", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.0.tgz", + "integrity": "sha512-3LEs/lMujSxvzMdKeF4pf41zPMVctpXLvAzrOcfi2clrULeNCNwaAIjMTKKnC8iU+g48+LnH2XbGwh1P/5pwHg==", + "hasInstallScript": true, + "dependencies": { + "@phc/format": "^1.0.0", + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" + }, + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -4345,12 +3768,6 @@ } } }, - "node_modules/fs-monkey": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", - "optional": true - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5517,258 +4934,42 @@ } }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/lucia": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lucia/-/lucia-3.2.0.tgz", - "integrity": "sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==", - "license": "MIT", - "dependencies": { - "oslo": "1.2.0" - } - }, - "node_modules/lucia/node_modules/@emnapi/core": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-0.45.0.tgz", - "integrity": "sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/lucia/node_modules/@emnapi/runtime": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", - "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.7.0.tgz", - "integrity": "sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@node-rs/argon2-android-arm-eabi": "1.7.0", - "@node-rs/argon2-android-arm64": "1.7.0", - "@node-rs/argon2-darwin-arm64": "1.7.0", - "@node-rs/argon2-darwin-x64": "1.7.0", - "@node-rs/argon2-freebsd-x64": "1.7.0", - "@node-rs/argon2-linux-arm-gnueabihf": "1.7.0", - "@node-rs/argon2-linux-arm64-gnu": "1.7.0", - "@node-rs/argon2-linux-arm64-musl": "1.7.0", - "@node-rs/argon2-linux-x64-gnu": "1.7.0", - "@node-rs/argon2-linux-x64-musl": "1.7.0", - "@node-rs/argon2-wasm32-wasi": "1.7.0", - "@node-rs/argon2-win32-arm64-msvc": "1.7.0", - "@node-rs/argon2-win32-ia32-msvc": "1.7.0", - "@node-rs/argon2-win32-x64-msvc": "1.7.0" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-android-arm-eabi": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-1.7.0.tgz", - "integrity": "sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-android-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-1.7.0.tgz", - "integrity": "sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-darwin-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-1.7.0.tgz", - "integrity": "sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-darwin-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-1.7.0.tgz", - "integrity": "sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-freebsd-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-1.7.0.tgz", - "integrity": "sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-linux-arm-gnueabihf": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-1.7.0.tgz", - "integrity": "sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-linux-arm64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-1.7.0.tgz", - "integrity": "sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-linux-arm64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-1.7.0.tgz", - "integrity": "sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-linux-x64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.7.0.tgz", - "integrity": "sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-linux-x64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-1.7.0.tgz", - "integrity": "sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/lucia/node_modules/@node-rs/argon2-wasm32-wasi": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-1.7.0.tgz", - "integrity": "sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==", - "cpu": [ - "wasm32" - ], - "optional": true, - "dependencies": { - "@emnapi/core": "^0.45.0", - "@emnapi/runtime": "^0.45.0", - "@tybys/wasm-util": "^0.8.1", - "memfs-browser": "^3.4.13000" - }, - "engines": { - "node": ">=14.0.0" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, - "node_modules/lucia/node_modules/@node-rs/argon2-win32-arm64-msvc": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-1.7.0.tgz", - "integrity": "sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" + "node_modules/lucia": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/lucia/-/lucia-3.2.0.tgz", + "integrity": "sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==", + "license": "MIT", + "dependencies": { + "oslo": "1.2.0" } }, - "node_modules/lucia/node_modules/@node-rs/argon2-win32-ia32-msvc": { + "node_modules/lucia/node_modules/@node-rs/argon2": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-1.7.0.tgz", - "integrity": "sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], + "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.7.0.tgz", + "integrity": "sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==", "engines": { "node": ">= 10" + }, + "optionalDependencies": { + "@node-rs/argon2-android-arm-eabi": "1.7.0", + "@node-rs/argon2-android-arm64": "1.7.0", + "@node-rs/argon2-darwin-arm64": "1.7.0", + "@node-rs/argon2-darwin-x64": "1.7.0", + "@node-rs/argon2-freebsd-x64": "1.7.0", + "@node-rs/argon2-linux-arm-gnueabihf": "1.7.0", + "@node-rs/argon2-linux-arm64-gnu": "1.7.0", + "@node-rs/argon2-linux-arm64-musl": "1.7.0", + "@node-rs/argon2-linux-x64-gnu": "1.7.0", + "@node-rs/argon2-linux-x64-musl": "1.7.0", + "@node-rs/argon2-wasm32-wasi": "1.7.0", + "@node-rs/argon2-win32-arm64-msvc": "1.7.0", + "@node-rs/argon2-win32-ia32-msvc": "1.7.0", + "@node-rs/argon2-win32-x64-msvc": "1.7.0" } }, "node_modules/lucia/node_modules/@node-rs/argon2-win32-x64-msvc": { @@ -5786,15 +4987,6 @@ "node": ">= 10" } }, - "node_modules/lucia/node_modules/@tybys/wasm-util": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.8.3.tgz", - "integrity": "sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/lucia/node_modules/oslo": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/oslo/-/oslo-1.2.0.tgz", @@ -5814,27 +5006,6 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "optional": true, - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/memfs-browser": { - "version": "3.5.10302", - "resolved": "https://registry.npmjs.org/memfs-browser/-/memfs-browser-3.5.10302.tgz", - "integrity": "sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw==", - "optional": true, - "dependencies": { - "memfs": "3.5.3" - } - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -6086,6 +5257,24 @@ "react-dom": ">= 16.0.0" } }, + "node_modules/node-addon-api": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.1.0.tgz", + "integrity": "sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", + "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/nodemailer": { "version": "6.9.14", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", @@ -6215,321 +5404,105 @@ "es-object-atoms": "^1.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/oslo": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/oslo/-/oslo-1.2.1.tgz", - "integrity": "sha512-HfIhB5ruTdQv0XX2XlncWQiJ5SIHZ7NHZhVyHth0CSZ/xzge00etRyYy/3wp/Dsu+PkxMC+6+B2lS/GcKoewkA==", - "license": "MIT", - "dependencies": { - "@node-rs/argon2": "1.7.0", - "@node-rs/bcrypt": "1.9.0" - } - }, - "node_modules/oslo/node_modules/@emnapi/core": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-0.45.0.tgz", - "integrity": "sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/oslo/node_modules/@emnapi/runtime": { - "version": "0.45.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.45.0.tgz", - "integrity": "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/oslo/node_modules/@node-rs/argon2": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.7.0.tgz", - "integrity": "sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==", - "engines": { - "node": ">= 10" - }, - "optionalDependencies": { - "@node-rs/argon2-android-arm-eabi": "1.7.0", - "@node-rs/argon2-android-arm64": "1.7.0", - "@node-rs/argon2-darwin-arm64": "1.7.0", - "@node-rs/argon2-darwin-x64": "1.7.0", - "@node-rs/argon2-freebsd-x64": "1.7.0", - "@node-rs/argon2-linux-arm-gnueabihf": "1.7.0", - "@node-rs/argon2-linux-arm64-gnu": "1.7.0", - "@node-rs/argon2-linux-arm64-musl": "1.7.0", - "@node-rs/argon2-linux-x64-gnu": "1.7.0", - "@node-rs/argon2-linux-x64-musl": "1.7.0", - "@node-rs/argon2-wasm32-wasi": "1.7.0", - "@node-rs/argon2-win32-arm64-msvc": "1.7.0", - "@node-rs/argon2-win32-ia32-msvc": "1.7.0", - "@node-rs/argon2-win32-x64-msvc": "1.7.0" - } - }, - "node_modules/oslo/node_modules/@node-rs/argon2-android-arm-eabi": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-1.7.0.tgz", - "integrity": "sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/oslo/node_modules/@node-rs/argon2-android-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-1.7.0.tgz", - "integrity": "sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/oslo/node_modules/@node-rs/argon2-darwin-arm64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-1.7.0.tgz", - "integrity": "sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/oslo/node_modules/@node-rs/argon2-darwin-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-1.7.0.tgz", - "integrity": "sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/oslo/node_modules/@node-rs/argon2-freebsd-x64": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-1.7.0.tgz", - "integrity": "sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/oslo/node_modules/@node-rs/argon2-linux-arm-gnueabihf": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-1.7.0.tgz", - "integrity": "sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/oslo/node_modules/@node-rs/argon2-linux-arm64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-1.7.0.tgz", - "integrity": "sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/oslo/node_modules/@node-rs/argon2-linux-arm64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-1.7.0.tgz", - "integrity": "sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, "engines": { - "node": ">= 10" + "node": ">= 0.4" } }, - "node_modules/oslo/node_modules/@node-rs/argon2-linux-x64-gnu": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-1.7.0.tgz", - "integrity": "sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">= 10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/oslo/node_modules/@node-rs/argon2-linux-x64-musl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-1.7.0.tgz", - "integrity": "sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" } }, - "node_modules/oslo/node_modules/@node-rs/argon2-wasm32-wasi": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-1.7.0.tgz", - "integrity": "sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w==", - "cpu": [ - "wasm32" - ], - "optional": true, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", "dependencies": { - "@emnapi/core": "^0.45.0", - "@emnapi/runtime": "^0.45.0", - "@tybys/wasm-util": "^0.8.1", - "memfs-browser": "^3.4.13000" + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" }, "engines": { - "node": ">=14.0.0" + "node": ">= 0.8.0" } }, - "node_modules/oslo/node_modules/@node-rs/argon2-win32-arm64-msvc": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-1.7.0.tgz", - "integrity": "sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" + "node_modules/oslo": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/oslo/-/oslo-1.2.1.tgz", + "integrity": "sha512-HfIhB5ruTdQv0XX2XlncWQiJ5SIHZ7NHZhVyHth0CSZ/xzge00etRyYy/3wp/Dsu+PkxMC+6+B2lS/GcKoewkA==", + "license": "MIT", + "dependencies": { + "@node-rs/argon2": "1.7.0", + "@node-rs/bcrypt": "1.9.0" } }, - "node_modules/oslo/node_modules/@node-rs/argon2-win32-ia32-msvc": { + "node_modules/oslo/node_modules/@node-rs/argon2": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-1.7.0.tgz", - "integrity": "sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], + "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-1.7.0.tgz", + "integrity": "sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog==", "engines": { "node": ">= 10" + }, + "optionalDependencies": { + "@node-rs/argon2-android-arm-eabi": "1.7.0", + "@node-rs/argon2-android-arm64": "1.7.0", + "@node-rs/argon2-darwin-arm64": "1.7.0", + "@node-rs/argon2-darwin-x64": "1.7.0", + "@node-rs/argon2-freebsd-x64": "1.7.0", + "@node-rs/argon2-linux-arm-gnueabihf": "1.7.0", + "@node-rs/argon2-linux-arm64-gnu": "1.7.0", + "@node-rs/argon2-linux-arm64-musl": "1.7.0", + "@node-rs/argon2-linux-x64-gnu": "1.7.0", + "@node-rs/argon2-linux-x64-musl": "1.7.0", + "@node-rs/argon2-wasm32-wasi": "1.7.0", + "@node-rs/argon2-win32-arm64-msvc": "1.7.0", + "@node-rs/argon2-win32-ia32-msvc": "1.7.0", + "@node-rs/argon2-win32-x64-msvc": "1.7.0" } }, "node_modules/oslo/node_modules/@node-rs/argon2-win32-x64-msvc": { @@ -6547,15 +5520,6 @@ "node": ">= 10" } }, - "node_modules/oslo/node_modules/@tybys/wasm-util": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.8.3.tgz", - "integrity": "sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8195,6 +7159,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, "license": "MIT" }, "node_modules/uri-js": { @@ -8580,6 +7545,126 @@ "optional": true } } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz", + "integrity": "sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz", + "integrity": "sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz", + "integrity": "sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz", + "integrity": "sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz", + "integrity": "sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz", + "integrity": "sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz", + "integrity": "sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz", + "integrity": "sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } diff --git a/package.json b/package.json index e797e65..7fe4a0f 100644 --- a/package.json +++ b/package.json @@ -36,11 +36,8 @@ "@radix-ui/react-tooltip": "^1.1.2", "@t3-oss/env-core": "^0.11.0", "@t3-oss/env-nextjs": "^0.11.0", - "@types/canvas-confetti": "^1.6.4", - "@types/jsonwebtoken": "^9.0.6", - "@types/lodash.throttle": "^4.1.9", - "@types/nodemailer": "^6.4.15", "arctic": "^1.9.2", + "argon2": "0.41.0", "canvas-confetti": "^1.9.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", @@ -73,7 +70,11 @@ "zustand": "^4.5.5" }, "devDependencies": { + "@types/canvas-confetti": "^1.6.4", + "@types/jsonwebtoken": "^9.0.6", + "@types/lodash.throttle": "^4.1.9", "@types/node": "^22.1.0", + "@types/nodemailer": "^6.4.15", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8", diff --git a/prisma/migrations/20240826080229_init/migration.sql b/prisma/migrations/20240826080229_init/migration.sql new file mode 100644 index 0000000..03d8470 --- /dev/null +++ b/prisma/migrations/20240826080229_init/migration.sql @@ -0,0 +1,184 @@ +-- CreateEnum +CREATE TYPE "Role" AS ENUM ('USER', 'ADMIN'); + +-- CreateEnum +CREATE TYPE "ProviderType" AS ENUM ('GOOGLE', 'DISCORD', 'GITHUB'); + +-- CreateTable +CREATE TABLE "users" ( + "id" TEXT NOT NULL, + "username" TEXT, + "email" TEXT, + "emailVerified" BOOLEAN NOT NULL DEFAULT false, + "hashedPassword" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "imageUrl" TEXT, + "displayName" TEXT, + "role" "Role" NOT NULL DEFAULT 'USER', + + CONSTRAINT "users_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "oauth_accounts" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "provider" "ProviderType" NOT NULL, + "providerUserId" TEXT NOT NULL, + "accessToken" TEXT NOT NULL, + "refreshToken" TEXT, + "expiresAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "oauth_accounts_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "verification_emails" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "code" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "verification_emails_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "sessions" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "sessions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "password_reset_tokens" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "token" TEXT NOT NULL, + "expiresAt" TIMESTAMP(3) NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "password_reset_tokens_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "exams" ( + "id" TEXT NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT NOT NULL, + "price" INTEGER NOT NULL, + "duration" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "exams_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "questions" ( + "id" TEXT NOT NULL, + "examId" TEXT NOT NULL, + "text" TEXT NOT NULL, + "options" TEXT[], + "correctAnswer" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "questions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "exam_submissions" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "examId" TEXT NOT NULL, + "answers" JSONB NOT NULL, + "score" INTEGER NOT NULL, + "timeSpent" INTEGER NOT NULL, + "warningCount" INTEGER NOT NULL DEFAULT 0, + "correctAnswers" INTEGER[], + "incorrectAnswers" INTEGER[], + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "exam_submissions_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ExamProgress" ( + "id" TEXT NOT NULL, + "examId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "answers" JSONB NOT NULL, + "markedQuestions" JSONB NOT NULL, + "timeRemaining" INTEGER NOT NULL, + "warningCount" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ExamProgress_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "users_username_key" ON "users"("username"); + +-- CreateIndex +CREATE UNIQUE INDEX "users_email_key" ON "users"("email"); + +-- CreateIndex +CREATE INDEX "oauth_accounts_provider_providerUserId_idx" ON "oauth_accounts"("provider", "providerUserId"); + +-- CreateIndex +CREATE INDEX "verification_emails_userId_idx" ON "verification_emails"("userId"); + +-- CreateIndex +CREATE INDEX "sessions_userId_idx" ON "sessions"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "password_reset_tokens_userId_key" ON "password_reset_tokens"("userId"); + +-- CreateIndex +CREATE UNIQUE INDEX "password_reset_tokens_token_key" ON "password_reset_tokens"("token"); + +-- CreateIndex +CREATE INDEX "password_reset_tokens_userId_idx" ON "password_reset_tokens"("userId"); + +-- CreateIndex +CREATE INDEX "questions_examId_idx" ON "questions"("examId"); + +-- CreateIndex +CREATE INDEX "exam_submissions_userId_examId_idx" ON "exam_submissions"("userId", "examId"); + +-- CreateIndex +CREATE UNIQUE INDEX "ExamProgress_examId_userId_key" ON "ExamProgress"("examId", "userId"); + +-- AddForeignKey +ALTER TABLE "oauth_accounts" ADD CONSTRAINT "oauth_accounts_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "verification_emails" ADD CONSTRAINT "verification_emails_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "sessions" ADD CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "password_reset_tokens" ADD CONSTRAINT "password_reset_tokens_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "questions" ADD CONSTRAINT "questions_examId_fkey" FOREIGN KEY ("examId") REFERENCES "exams"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "exam_submissions" ADD CONSTRAINT "exam_submissions_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "exam_submissions" ADD CONSTRAINT "exam_submissions_examId_fkey" FOREIGN KEY ("examId") REFERENCES "exams"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ExamProgress" ADD CONSTRAINT "ExamProgress_examId_fkey" FOREIGN KEY ("examId") REFERENCES "exams"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ExamProgress" ADD CONSTRAINT "ExamProgress_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20240829193336_changes_questions_and_exam_schema/migration.sql b/prisma/migrations/20240829193336_changes_questions_and_exam_schema/migration.sql new file mode 100644 index 0000000..6b7fe56 --- /dev/null +++ b/prisma/migrations/20240829193336_changes_questions_and_exam_schema/migration.sql @@ -0,0 +1,18 @@ +/* + Warnings: + + - You are about to drop the column `examId` on the `questions` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "questions" DROP CONSTRAINT "questions_examId_fkey"; + +-- DropIndex +DROP INDEX "questions_examId_idx"; + +-- AlterTable +ALTER TABLE "exams" ADD COLUMN "questions" TEXT[]; + +-- AlterTable +ALTER TABLE "questions" DROP COLUMN "examId", +ADD COLUMN "image" TEXT; diff --git a/prisma/migrations/20240831174038_update_exam_structure/migration.sql b/prisma/migrations/20240831174038_update_exam_structure/migration.sql new file mode 100644 index 0000000..41ad51c --- /dev/null +++ b/prisma/migrations/20240831174038_update_exam_structure/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - You are about to drop the column `questions` on the `exams` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "exam_submissions" DROP CONSTRAINT "exam_submissions_examId_fkey"; + +-- AlterTable +ALTER TABLE "exam_submissions" ADD COLUMN "questions" TEXT[]; + +-- AlterTable +ALTER TABLE "exams" DROP COLUMN "questions", +ADD COLUMN "numQuestions" INTEGER NOT NULL DEFAULT 1; diff --git a/prisma/migrations/20240901100818_added_soft_delete_in_exams/migration.sql b/prisma/migrations/20240901100818_added_soft_delete_in_exams/migration.sql new file mode 100644 index 0000000..56c0e3c --- /dev/null +++ b/prisma/migrations/20240901100818_added_soft_delete_in_exams/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "exams" ADD COLUMN "isDeleted" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/migrations/20240901135449_update_exam_submission_schema/migration.sql b/prisma/migrations/20240901135449_update_exam_submission_schema/migration.sql new file mode 100644 index 0000000..a90acde --- /dev/null +++ b/prisma/migrations/20240901135449_update_exam_submission_schema/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "exam_submissions" ALTER COLUMN "correctAnswers" SET DATA TYPE TEXT[], +ALTER COLUMN "incorrectAnswers" SET DATA TYPE TEXT[]; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..fbffa92 --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (i.e. Git) +provider = "postgresql" \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 5444495..1ce5ebb 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -96,8 +96,8 @@ model Exam { description String price Int duration Int - questions Question[] - submissions ExamSubmission[] + numQuestions Int @default(1) + isDeleted Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt ExamProgress ExamProgress[] @@ -108,15 +108,13 @@ model Exam { model Question { id String @id @default(cuid()) - examId String - exam Exam @relation(fields: [examId], references: [id], onDelete: Cascade) text String options String[] + image String? correctAnswer Int createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - @@index([examId]) @@map("questions") } @@ -125,13 +123,13 @@ model ExamSubmission { userId String user User @relation(fields: [userId], references: [id], onDelete: Cascade) examId String - exam Exam @relation(fields: [examId], references: [id], onDelete: Cascade) + questions String[] answers Json score Int timeSpent Int warningCount Int @default(0) - correctAnswers Int[] - incorrectAnswers Int[] + correctAnswers String[] + incorrectAnswers String[] createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/prisma/seed.ts b/prisma/seed.ts index ca051dc..3983a52 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -3,55 +3,89 @@ import { PrismaClient } from '@prisma/client' const prisma = new PrismaClient() async function main() { - const mathExam = await prisma.exam.create({ + const user = await prisma.user.create({ + data: { + username: 'john_doe', + email: 'john.doe@example.com', + hashedPassword: 'hashedpassword', + role: 'USER', + }, + }) + + + const exam1 = await prisma.exam.create({ data: { title: 'Mathematics Proficiency Test', description: 'Comprehensive exam covering algebra, geometry, and calculus.', price: 2000, duration: 120, - questions: { - create: [ - { - text: 'What is the value of π (pi) to two decimal places?', - options: ['3.14', '3.16', '3.12', '3.18'], - correctAnswer: 0, - }, - { - text: 'Solve for x: 2x + 5 = 13', - options: ['x = 3', 'x = 4', 'x = 5', 'x = 6'], - correctAnswer: 1, - }, - ], - }, + numQuestions: 2, }, }) - const englishExam = await prisma.exam.create({ + const exam2 = await prisma.exam.create({ data: { title: 'English Language Assessment', description: 'Evaluate your English language proficiency.', price: 1600, duration: 90, - questions: { - create: [ - { - text: 'Which of the following is a correct sentence?', - options: [ - 'The cat is sleeping on the couch.', - 'The cat sleeping on the couch.', - 'The cat be sleeping on the couch.', - 'The cat sleeps on the couch is.', - ], - correctAnswer: 0, - }, - { - text: 'What is the plural form of "child"?', - options: ['childs', 'childen', 'children', 'childres'], - correctAnswer: 2, - }, - ], + numQuestions: 2, + }, + }) + + + const question1 = await prisma.question.create({ + data: { + text: 'What is the value of π (pi) to two decimal places?', + options: ['3.14', '3.16', '3.12', '3.18'], + correctAnswer: 0, + }, + }) + + const question2 = await prisma.question.create({ + data: { + text: 'Solve for x: 2x + 5 = 13', + options: ['x = 3', 'x = 4', 'x = 5', 'x = 6'], + correctAnswer: 1, + }, + }) + + const question3 = await prisma.question.create({ + data: { + text: 'Which of the following is a correct sentence?', + options: [ + 'The cat is sleeping on the couch.', + 'The cat sleeping on the couch.', + 'The cat be sleeping on the couch.', + 'The cat sleeps on the couch is.', + ], + correctAnswer: 0, + }, + }) + + const question4 = await prisma.question.create({ + data: { + text: 'What is the plural form of "child"?', + options: ['childs', 'childen', 'children', 'childres'], + correctAnswer: 2 + } + }) + + await prisma.examSubmission.create({ + data: { + userId: user.id, + examId: exam1.id, + questions: [question1.id, question2.id], + answers: { + [question1.id]: 0, + [question2.id]: 1, }, + score: 2, + timeSpent: 30, + warningCount: 0, + correctAnswers: [question1.id, question2.id], + incorrectAnswers: [], }, }) diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..0f08357 Binary files /dev/null and b/public/logo.png differ diff --git a/public/mcq/q1.png b/public/mcq/q1.png new file mode 100644 index 0000000..0aaee0b Binary files /dev/null and b/public/mcq/q1.png differ diff --git a/public/mcq/q2.png b/public/mcq/q2.png new file mode 100644 index 0000000..bb1def8 Binary files /dev/null and b/public/mcq/q2.png differ diff --git a/public/profile.png b/public/profile.png new file mode 100644 index 0000000..5634ae6 Binary files /dev/null and b/public/profile.png differ diff --git a/src/actions/exams.ts b/src/actions/exams.ts index 1abdd3b..2de6feb 100644 --- a/src/actions/exams.ts +++ b/src/actions/exams.ts @@ -5,28 +5,238 @@ import db from '@/lib/db' import { revalidatePath } from 'next/cache' import { cache } from 'react' -export const getExams = cache(async () => { - const response = await db.exam.findMany({ - select: { - id: true, - title: true, - description: true, - duration: true, - price: true, - }, - }) - return response -}) +type CreateExamInput = { + title: string + description: string + duration: number + price: number + numQuestions: number +} + +type UpdateExamInput = { + id: string + title?: string + description?: string + duration?: number + price?: number + numQuestions?: number +} interface SubmitExamParams { examId: string answers: Record timeSpent: number warningCount: number + questions: string[] +} + +export const createExam = cache(async (examData: CreateExamInput) => { + try { + const session = await validateRequest() + + if (!session || !session.user) { + throw new Error('Unauthorized') + } + + const createdExam = await db.exam.create({ + data: examData, + }) + + const responseData = { + id: createdExam.id, + title: createdExam.title, + description: createdExam.description, + duration: createdExam.duration, + price: createdExam.price, + numQuestions: createdExam.numQuestions, + } + + return { succes: true, data: responseData } + } catch (error) { + console.error('Error creating exam:', error) + throw new Error('Failed to create exam') + } +}) + +export const updateExam = cache(async (examData: UpdateExamInput) => { + try { + const session = await validateRequest() + + if (!session || !session.user) { + throw new Error('Unauthorized') + } + + const { id, ...updateFields } = examData + + const totalAvailableQuestions = await db.question.findMany() + + if ( + updateFields.numQuestions !== undefined && + updateFields.numQuestions > totalAvailableQuestions.length + ) { + throw new Error( + 'The number of questions are not available. Try fewer questions' + ) + } + + const updatedExam = await db.exam.update({ + where: { id }, + data: updateFields, + }) + + return { success: true, data: updatedExam } + } catch (error) { + console.error('Error updating exam:', error) + throw new Error('Failed to update exam') + } +}) + +export const getExams = async () => { + try { + const session = await validateRequest() + + if (!session || !session.user) { + throw new Error('Unauthorized') + } + + const response = await db.exam.findMany({ + select: { + id: true, + title: true, + description: true, + duration: true, + price: true, + numQuestions: true, + }, + where: { isDeleted: false }, + orderBy: { + createdAt: 'desc', + }, + }) + return { success: true, data: response } + } catch (error) { + console.error('Error fetching exam:', error) + throw new Error('Failed to fetch exam') + } +} + +export const deleteExam = cache(async (examId: string) => { + try { + const session = await validateRequest() + + if (!session || !session.user) { + throw new Error('Unauthorized') + } + + await db.exam.update({ + where: { id: examId }, + data: { + isDeleted: true, + }, + }) + + return { succes: true } + } catch (error) { + console.error('Error deleting exam:', error) + throw new Error('Failed to delete exam') + } +}) + +// Get Random Questions for an Exam +export const getRandomQuestionsForExam = async (examId: string) => { + try { + const session = await validateRequest() + + if (!session || !session.user) { + throw new Error('Unauthorized') + } + + const exam = await db.exam.findUnique({ + where: { id: examId }, + select: { numQuestions: true }, + }) + + if (!exam) { + throw new Error('Exam not found') + } + + const numQuestions = exam.numQuestions + + // Get all questions from the database + const allQuestions = await db.question.findMany() + + if (numQuestions > allQuestions.length) { + throw new Error('Number of questions exceeds available questions') + } + + const randomQuestions = allQuestions + .sort(() => 0.5 - Math.random()) + .slice(0, numQuestions) + + return { success: true, questions: randomQuestions } + } catch (error) { + console.error('Error fetching random questions for exam:', error) + return { + success: false, + error: 'An unexpected error occurred while fetching questions.', + } + } +} + +// Get Exam Data - Fetch exam details and random questions +export async function getExamData(examId: string) { + try { + const session = await validateRequest() + + if (!session || !session.user) { + throw new Error('Unauthorized') + } + + const exam = await db.exam.findUnique({ + where: { id: examId }, + select: { + id: true, + title: true, + description: true, + price: true, + duration: true, + numQuestions: true, + }, + }) + + if (!exam) { + throw new Error('Exam not found') + } + + const { success, questions, error } = + await getRandomQuestionsForExam(examId) + if (!success || !questions) { + throw new Error(error || 'Failed to fetch questions for the exam.') + } + + return { + id: exam.id, + title: exam.title, + description: exam.description, + price: exam.price, + duration: exam.duration, + numQuestions: exam.numQuestions, + questions: questions.map((q) => ({ + id: q.id, + text: q.text, + image: q.image, + options: q.options, + })), + } + } catch (error) { + console.error('Error fetching exam data:', error) + throw new Error('Failed to fetch exam data') + } } export async function submitExam({ examId, + questions, answers, timeSpent, warningCount, @@ -41,107 +251,102 @@ export async function submitExam({ const exam = await db.exam.findUnique({ where: { id: examId }, - include: { questions: true }, + select: { + numQuestions: true, + }, }) if (!exam) { throw new Error('Exam not found') } + const examQuestions = await db.question.findMany({ + where: { id: { in: questions } }, + }) + + if ( + !examQuestions || + examQuestions.length === 0 || + examQuestions.length !== exam.numQuestions + ) { + throw new Error('No questions found for the exam.') + } + let score = 0 - const correctAnswers: number[] = [] - const incorrectAnswers: number[] = [] + const correctAnswers: string[] = [] + const incorrectAnswers: string[] = [] - exam.questions.forEach((question, index) => { + examQuestions.forEach((question) => { if (answers[question.id] === question.correctAnswer) { score += 1 - correctAnswers.push(index) + correctAnswers.push(question.id) } else { - incorrectAnswers.push(index) + incorrectAnswers.push(question.id) } }) const response = await db.examSubmission.create({ data: { examId, - userId: userId, + userId, answers: answers, score, timeSpent, warningCount, correctAnswers, incorrectAnswers, + questions, }, }) + revalidatePath('/user-results') revalidatePath('/exam-results') return { id: response.id, score, - totalQuestions: exam.questions.length, + totalQuestions: exam.numQuestions, correctAnswers, incorrectAnswers, } } -export async function getExamData(examId: string) { - try { - const exam = await db.exam.findUnique({ - where: { id: examId }, - include: { - questions: { - select: { - id: true, - text: true, - options: true, - }, - }, - }, - }) - - if (!exam) { - throw new Error('Exam not found') - } - - return { - id: exam.id, - title: exam.title, - description: exam.description, - price: exam.price, - duration: exam.duration, - questions: exam.questions.map((q) => ({ - id: q.id, - text: q.text, - options: q.options, - })), - } - } catch (error) { - console.error('Error fetching exam data:', error) - throw new Error('Failed to fetch exam data') - } -} - export async function getExamResults(examId: string) { const session = await validateRequest() - if (!session || !session.user) { throw new Error('Unauthorized') } const submission = await db.examSubmission.findFirst({ - where: { id: examId, userId: session.user.id }, - include: { exam: { include: { questions: true } } }, - orderBy: { createdAt: 'desc' }, + where: { + examId, + userId: session.user.id, + }, + orderBy: { + createdAt: 'desc', + }, }) if (!submission) { throw new Error('Exam submission not found') } + const exam = await db.exam.findUnique({ + where: { + id: examId, + }, + select: { + numQuestions: true, + }, + }) + + if (!exam) { + throw new Error('Exam not found') + } + return { score: submission.score, - totalQuestions: submission.exam.questions.length, + totalQuestions: exam.numQuestions, timeSpent: submission.timeSpent, warningCount: submission.warningCount, correctAnswers: submission.correctAnswers, @@ -152,44 +357,83 @@ export async function getExamResults(examId: string) { export const getUserResults = cache( async (page: number = 1, pageSize: number = 10) => { const session = await validateRequest() - if (!session || !session.user) { throw new Error('Unauthorized') } + // Calculate pagination parameters const skip = (page - 1) * pageSize - const [results, totalCount] = await Promise.all([ - db.examSubmission.findMany({ - where: { userId: session.user.id }, - include: { - exam: { - include: { questions: true }, + try { + const [submissions, totalCount] = await Promise.all([ + db.examSubmission.findMany({ + where: { userId: session.user.id }, + orderBy: { createdAt: 'desc' }, + skip, + take: pageSize, + }), + db.examSubmission.count({ + where: { userId: session.user.id }, + }), + ]) + + const examIds = submissions + .map((submission) => submission.examId) + .filter((value, index, self) => self.indexOf(value) === index) + + const [exams, questions] = await Promise.all([ + db.exam.findMany({ + where: { id: { in: examIds } }, + }), + db.question.findMany({ + where: { + id: { + in: submissions.flatMap((submission) => submission.questions), + }, }, - }, - orderBy: { createdAt: 'desc' }, - skip, - take: pageSize, - }), - db.examSubmission.count({ - where: { userId: session.user.id }, - }), - ]) - - const totalPages = Math.ceil(totalCount / pageSize) + }), + ]) - return { - results: results.map((result) => ({ - id: result.id, - examTitle: result.exam.title, - score: result.score, - totalQuestions: result.exam.questions.length, - timeSpent: result.timeSpent, - date: result.createdAt.toISOString(), - examId: result.examId, - })), - totalPages, - currentPage: page, + const examsMap = new Map(exams.map((exam) => [exam.id, exam])) + const questionsMap = new Map( + questions.map((question) => [question.id, question]) + ) + + const results = submissions.map((submission) => { + const exam = examsMap.get(submission.examId) + if (!exam) { + throw new Error('Exam not found') + } + + const examQuestions = submission.questions + .map((questionId) => questionsMap.get(questionId)) + .filter(Boolean) + if (!examQuestions.length) { + throw new Error('Questions not found for the exam') + } + + return { + id: submission.id, + examTitle: exam.title, + score: submission.score, + totalQuestions: exam.numQuestions, + timeSpent: submission.timeSpent, + date: submission.createdAt.toISOString(), + examId: submission.examId, + questions: examQuestions, + } + }) + + const totalPages = Math.ceil(totalCount / pageSize) + + return { + results, + totalPages, + currentPage: page, + } + } catch (error) { + console.error('Error fetching user results:', error) + throw new Error('Failed to fetch user results') } } ) diff --git a/src/actions/questions.ts b/src/actions/questions.ts new file mode 100644 index 0000000..9ecb066 --- /dev/null +++ b/src/actions/questions.ts @@ -0,0 +1,142 @@ +'use server' + +import db from '@/lib/db' +import { z } from 'zod' +import { + createQuestionsSchema, + createQuestionsValues, + updateQuestionsSchema, + updateQuestionsValues, +} from '@/schemas' +import { validateRequest } from '@/auth' + +export const createQuestions = async (values: createQuestionsValues) => { + try { + const session = await validateRequest() + + if (!session || !session.user) { + throw new Error('Unauthorized') + } + const validatedData = createQuestionsSchema.safeParse(values) + if (!validatedData.success) { + return { error: validatedData.error.errors[0].message } + } + + const { questions } = validatedData.data + + const createdQuestions = await db.$transaction( + questions.map((question) => + db.question.create({ + data: { + text: question.text, + options: question.options, + correctAnswer: question.correctAnswer, + image: question.image || null, + }, + }) + ) + ) + return { success: true, questions: createdQuestions } + } catch (error) { + console.error('Error creating questions:', error) + return { error: 'An unexpected error occurred while creating questions.' } + } +} + +export const getAllQuestions = async () => { + try { + const session = await validateRequest() + + if (!session || !session.user) { + throw new Error('Unauthorized') + } + const questions = await db.question.findMany({ + select: { + id: true, + text: true, + options: true, + correctAnswer: true, + image: true, + createdAt: true, + updatedAt: true, + }, + }) + + return { success: true, questions } + } catch (error) { + console.error('Error fetching questions:', error) + return { error: 'An unexpected error occurred while fetching questions.' } + } +} + +export const updateQuestions = async ( + questions: updateQuestionsValues['questions'] +) => { + try { + const session = await validateRequest() + + if (!session || !session.user) { + throw new Error('Unauthorized') + } + + const validatedData = updateQuestionsSchema.safeParse({ questions }) + if (!validatedData.success) { + return { error: validatedData.error.errors[0].message } + } + + const { questions: validQuestions } = validatedData.data + + const updateResults = await Promise.all( + validQuestions.map(async (question) => { + try { + const updatedQuestion = await db.question.update({ + where: { id: question.id }, + data: { + text: question.text, + options: question.options, + correctAnswer: question.correctAnswer, + image: question.image || null, + }, + }) + return { success: true, question: updatedQuestion } + } catch (error) { + console.error('Error updating question:', error) + return { + error: `Failed to update question with ID ${question.id}`, + } + } + }) + ) + + const errors = updateResults.filter((result) => result.error) + if (errors.length > 0) { + return { success: false, errors } + } + + return { success: true, results: updateResults } + } catch (error) { + console.error('Error updating questions:', error) + return { + error: 'An unexpected error occurred while updating questions.', + } + } +} + +export const deleteQuestion = async (id: string) => { + try { + const session = await validateRequest() + + if (!session || !session.user) { + throw new Error('Unauthorized') + } + await db.question.delete({ + where: { id }, + }) + return { success: true, message: 'Question deleted successfully.' } + } catch (error) { + console.error('Error deleting question:', error) + return { + error: 'An unexpected error occurred while deleting the question.', + } + } +} diff --git a/src/app/(main)/(non-exam-section)/available-exams/page.tsx b/src/app/(main)/(non-exam-section)/available-exams/page.tsx index 83e84f3..cf84a45 100644 --- a/src/app/(main)/(non-exam-section)/available-exams/page.tsx +++ b/src/app/(main)/(non-exam-section)/available-exams/page.tsx @@ -1,17 +1,15 @@ -import React from 'react' - -import { getExams } from '@/actions/exams' -import { validateRequest } from '@/auth' import AvailableExams from '@/components/exams/avaiable' +import React, { useEffect } from 'react' +import { validateRequest } from '@/auth' + const Page = async () => { - const data = await getExams() const session = await validateRequest() const user = session?.user if (!user) return null - return + return } export default Page diff --git a/src/app/(main)/(non-exam-section)/questions/create/page.tsx b/src/app/(main)/(non-exam-section)/questions/create/page.tsx new file mode 100644 index 0000000..aae1585 --- /dev/null +++ b/src/app/(main)/(non-exam-section)/questions/create/page.tsx @@ -0,0 +1,6 @@ +import React from 'react' +import CreateQuestionsBankPage from '@/components/questions/create' + +export default async function CreateQuestionsPage() { + return +} diff --git a/src/app/(main)/(non-exam-section)/questions/edit/page.tsx b/src/app/(main)/(non-exam-section)/questions/edit/page.tsx new file mode 100644 index 0000000..aae1585 --- /dev/null +++ b/src/app/(main)/(non-exam-section)/questions/edit/page.tsx @@ -0,0 +1,6 @@ +import React from 'react' +import CreateQuestionsBankPage from '@/components/questions/create' + +export default async function CreateQuestionsPage() { + return +} diff --git a/src/app/(main)/(non-exam-section)/questions/page.tsx b/src/app/(main)/(non-exam-section)/questions/page.tsx new file mode 100644 index 0000000..6ab8d1a --- /dev/null +++ b/src/app/(main)/(non-exam-section)/questions/page.tsx @@ -0,0 +1,6 @@ +import React from 'react' +import { QuestionsPage } from '@/components/questions' + +export default async function QuestionsBankPage() { + return +} diff --git a/src/app/(main)/(non-exam-section)/user-results/page.tsx b/src/app/(main)/(non-exam-section)/user-results/page.tsx index c5f78ed..d734318 100644 --- a/src/app/(main)/(non-exam-section)/user-results/page.tsx +++ b/src/app/(main)/(non-exam-section)/user-results/page.tsx @@ -1,8 +1,5 @@ -import { getUserResults } from '@/actions/exams' -import UserResults from '@/components/exams/user-results' +import UserResults from '@/components/exams/user-results' export default async function UserResultsPage() { - const results = await getUserResults() - return } diff --git a/src/components/exams/avaiable.tsx b/src/components/exams/avaiable.tsx index 95a3d0f..076484f 100644 --- a/src/components/exams/avaiable.tsx +++ b/src/components/exams/avaiable.tsx @@ -3,9 +3,17 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { motion, useAnimation } from 'framer-motion' -import { Clock, CreditCard, Loader2 } from 'lucide-react' import { User } from 'lucia' import { useRazorpay } from '@/hooks/use-razorpay' +import { + Clock, + CreditCard, + Loader2, + FilePlus2, + FileQuestion, + Pencil, + Trash2, +} from 'lucide-react' import { Button } from '@/components/ui/button' import { @@ -16,22 +24,35 @@ import { CardHeader, CardTitle, } from '@/components/ui/card' +import { NewExamModal } from './new-exam-modal' +import { useGlobalStore } from '@/store' +import Loader from '../ui/loader' +import { deleteExam, getExams } from '@/actions/exams' +import { toast } from 'sonner' +import { ConfirmDeleteModal } from '../ui/confirm-delete-modal' -interface Exam { +type Exam = { id: string title: string description: string + numQuestions: number duration: number price: number } -export default function AvailableExams({ - exams, - user, -}: { - exams: Exam[] - user: User -}) { +export default function AvailableExams({ user }: { user: User }) { + const { exams, setExams } = useGlobalStore((state) => ({ + exams: state.exams, + setExams: state.setExams, + })) + + const [selectedExam, setSelectedExam] = useState(null) + const [isCreateModalOpen, setCreateModalOpen] = useState(false) + const [isEditModalOpen, setEditModalOpen] = useState(false) + const [isDeleteModalOpen, setDeleteModalOpen] = useState(false) + const [isDeleting, setIsDeleting] = useState(false) + const [examToDelete, setExamToDelete] = useState(null) + const controls = useAnimation() const router = useRouter() const processPayment = useRazorpay() @@ -46,6 +67,45 @@ export default function AvailableExams({ })) }, [controls]) + useEffect(() => { + fetchExams() + }, []) + + const fetchExams = async () => { + try { + const response = await getExams() + console.log('Response=> ', response) + if (response.success && response.data) { + setExams(response.data) + } else { + console.error('Failed to fetch exams') + } + } catch (error) { + console.error('Error fetching exams:', error) + } + } + + const handleDeleteClick = (examId: string) => { + setExamToDelete(examId) + setDeleteModalOpen(true) + } + + const handleDelete = async () => { + if (!examToDelete) return + + setIsDeleting(true) + try { + await deleteExam(examToDelete) + toast.success('Exam Deleted Successfully.') + setDeleteModalOpen(false) + fetchExams() + } catch (error) { + toast.error(`Failed to delete exam`) + } finally { + setIsDeleting(false) + } + } + const getGradientColor = (index: number) => { const colors = [ 'from-blue-500/5 to-blue-500/0', @@ -72,9 +132,9 @@ export default function AvailableExams({ } return ( -
+
-
+
+
+ +
- {exams.map((exam, index) => ( - - 0 ? ( + exams.map((exam, index) => ( + - - {exam.title} - {exam.description} - - -
- - {exam.duration} minutes -
-
- - INR {exam.price} -
-
- - - -
-
- ))} + + +
+ + {exam.title} + +
+ { + setSelectedExam(exam) + setEditModalOpen(true) + }} + /> + handleDeleteClick(exam.id)} + /> +
+
+ {exam.description} +
+ +
+ + {exam.duration} minutes +
+
+ + {exam.numQuestions} Questions +
+
+ + INR {exam.price} +
+
+ + + +
+ + )) + ) : ( +
+ + Loading Exams... +
+ )}
+ + setCreateModalOpen(false)} + fetchExams={fetchExams} + /> + {isEditModalOpen && selectedExam && ( + setEditModalOpen(false)} + fetchExams={fetchExams} + /> + )} + + {isDeleteModalOpen && examToDelete && ( + setDeleteModalOpen(false)} + examId={examToDelete} + /> + )}
) } diff --git a/src/components/exams/exam-component.tsx b/src/components/exams/exam-component.tsx index cee4c27..21d582f 100644 --- a/src/components/exams/exam-component.tsx +++ b/src/components/exams/exam-component.tsx @@ -27,11 +27,12 @@ import { Clock, AlertTriangle, Maximize } from 'lucide-react' import { toast } from 'sonner' import { submitExam } from '@/actions/exams' import { motion, AnimatePresence } from 'framer-motion' - +import Image from 'next/image' interface Question { id: string text: string options: string[] + image?: string } interface ExamComponentProps { @@ -135,19 +136,20 @@ export default function ExamComponent({ try { onExitFullscreen() const timeSpent = duration * 60 - timeRemaining - + const examQuestions = questions.map((q) => q.id) const result = await submitExam({ examId, answers, timeSpent, warningCount, + questions: examQuestions, }) toast.success('Exam Submitted', { description: `Your exam has been successfully submitted. Your score: ${result.score}/${questions.length}`, }) - router.push(`/exam-results/${result.id}`) + router.push(`/exam-results/${examId}`) } catch (error) { console.error('Error submitting exam:', error) toast.error('Error', { @@ -163,6 +165,7 @@ export default function ExamComponent({ questions.length, router, warningCount, + questions, ]) const formatTime = useCallback((seconds: number) => { @@ -202,14 +205,23 @@ export default function ExamComponent({ - {questions[currentQuestion].text} - + + {questions[currentQuestion].image && ( + question-image + )} + handleAnswer(parseInt(value))} diff --git a/src/components/exams/exam-results.tsx b/src/components/exams/exam-results.tsx index 6ba559c..11c88fd 100644 --- a/src/components/exams/exam-results.tsx +++ b/src/components/exams/exam-results.tsx @@ -16,6 +16,7 @@ import { import { motion } from 'framer-motion' import confetti from 'canvas-confetti' import useSound from 'use-sound' +import Loader from '../ui/loader' import { PieChart, Pie, @@ -31,8 +32,8 @@ interface ExamResultsProps { totalQuestions: number timeSpent: number warningCount: number - correctAnswers: number[] - incorrectAnswers: number[] + correctAnswers: string[] + incorrectAnswers: string[] } } @@ -66,139 +67,151 @@ export default function ExamResults({ result }: ExamResultsProps) { exit={{ opacity: 0, y: -20 }} className='container mx-auto px-4 py-8' > - - - - Exam Results - - - - -

- {percentage.toFixed(1)}% -

- -
- -
+ {result ? ( + + + + Exam Results + + + -

- Score -

-

- {result.score}/{result.totalQuestions} -

+

+ {percentage.toFixed(1)}% +

+
+ +
+ +

+ Score +

+

+ {result.score}/{result.totalQuestions} +

+
+ +

+ Time Taken +

+

+ + {Math.floor(result.timeSpent / 60)}m {result.timeSpent % 60}s +

+
+ +

+ Warnings +

+ 0 ? 'destructive' : 'secondary' + } + className='text-2xl py-2 px-4' + > + + {result.warningCount} + +
+
+ -

- Time Taken -

-

- - {Math.floor(result.timeSpent / 60)}m {result.timeSpent % 60}s -

+

+ Question Analysis +

+
+
+ + + + {pieData.map((entry, index) => ( + + ))} + + + + + +
+
+
+ + + {result.correctAnswers.length} Correct + +
+
+ + + {result.incorrectAnswers.length} Incorrect + +
+
+
+ -

- Warnings -

- 0 ? 'destructive' : 'secondary'} - className='text-2xl py-2 px-4' +
-
- - -

- Question Analysis -

-
-
- - - - {pieData.map((entry, index) => ( - - ))} - - - - - -
-
-
- - - {result.correctAnswers.length} Correct - -
-
- - - {result.incorrectAnswers.length} Incorrect - -
-
-
-
- - - - -
-
+
+ + ) : ( +
+ + Loading Exams... +
+ )} ) } diff --git a/src/components/exams/instructions.tsx b/src/components/exams/instructions.tsx index 1fd7278..0be4749 100644 --- a/src/components/exams/instructions.tsx +++ b/src/components/exams/instructions.tsx @@ -10,75 +10,90 @@ import { } from '@/components/ui/card' import { motion } from 'framer-motion' +interface ExamData { + id: string + title: string + description: string + price: number + duration: number + numQuestions: number +} + interface ExamInstructionsProps { - examId: string - examTitle: string - examDescription: string - examPrice: number - examDuration: number + examData: ExamData onStart: () => void onCancel: () => void } export default function ExamInstructions({ - examId, - examTitle, - examDescription, - examPrice, - examDuration, + examData, onStart, onCancel, }: ExamInstructionsProps) { const [agreed, setAgreed] = useState(false) - + const { id, title, description, price, duration, numQuestions } = examData return ( - - - {examTitle} - Instructions + + + {title} +

{description}

-

{examDescription}

-

Price: INR {examPrice}

-

Duration: {examDuration} minutes

-
    -
  • This exam must be taken in full-screen mode.
  • -
  • - You will be prompted to enter full-screen mode before the exam - starts. -
  • -
  • - If you exit full-screen mode during the exam, you will be prompted - to return to full-screen. -
  • -
  • - Failure to return to full-screen mode will result in automatic - submission of your exam. -
  • -
  • - Changing tabs or windows during the exam will result in automatic - submission. -
  • -
  • - Ensure you have a stable internet connection before starting the - exam. -
  • -
  • - Once you start the exam, the timer will begin and cannot be - paused. -
  • -
-
+
+

+ Price: INR {price} +

+

+ Duration: {duration} minutes +

+

+ Questions: {numQuestions} +

+
+
+

Instructions

+
    +
  • This exam must be taken in full-screen mode.
  • +
  • + You will be prompted to enter full-screen mode before the exam + starts. +
  • +
  • + If you exit full-screen mode during the exam, you will be + prompted to return to full-screen. +
  • +
  • + Failure to return to full-screen mode will result in automatic + submission of your exam. +
  • +
  • + Changing tabs or windows during the exam will result in + automatic submission. +
  • +
  • + Ensure you have a stable internet connection before starting the + exam. +
  • +
  • + Once you start the exam, the timer will begin and cannot be + paused. +
  • +
+
+
setAgreed(checked as boolean)} + className='h-5 w-5' /> -
diff --git a/src/components/exams/mutipage-form.tsx b/src/components/exams/mutipage-form.tsx index 46b07ee..9a57714 100644 --- a/src/components/exams/mutipage-form.tsx +++ b/src/components/exams/mutipage-form.tsx @@ -13,6 +13,7 @@ interface ExamData { description: string price: number duration: number + numQuestions: number questions: Question[] } @@ -85,11 +86,7 @@ export default function MultiStepExamPage({ exit={{ opacity: 0 }} > diff --git a/src/components/exams/new-exam-modal.tsx b/src/components/exams/new-exam-modal.tsx new file mode 100644 index 0000000..fbd6b8d --- /dev/null +++ b/src/components/exams/new-exam-modal.tsx @@ -0,0 +1,237 @@ +'use client' + +import * as React from 'react' +import * as Dialog from '@radix-ui/react-dialog' +import { useForm } from 'react-hook-form' +import { zodResolver } from '@hookform/resolvers/zod' +import { createExamSchema, CreateExamValues } from '@/schemas' +import { Button } from '@/components/ui/button' +import { toast } from 'sonner' +import { X } from 'lucide-react' +import { createExam, updateExam } from '@/actions/exams' +import { useGlobalStore } from '@/store' + +type DefaultValues = { + id?: string + title: string + description: string + duration: number + price: number + numQuestions: number +} + +interface NewExamModalProps { + defaultValues?: DefaultValues | null + onClose: () => void + open: boolean + trigger?: React.ReactNode + fetchExams: () => void +} + +export const NewExamModal = ({ + defaultValues, + onClose, + open, + trigger, + fetchExams, +}: NewExamModalProps) => { + const { exams, setExams } = useGlobalStore() + const { + register, + handleSubmit, + formState: { errors }, + reset, + setValue, + } = useForm({ + resolver: zodResolver(createExamSchema), + defaultValues: defaultValues || { + title: '', + description: '', + duration: undefined, + price: undefined, + numQuestions: undefined, + }, + }) + + React.useEffect(() => { + if (defaultValues) { + reset({ ...defaultValues }) + } else { + reset() + } + }, [defaultValues, setValue, reset]) + + const handleCreateOrUpdateExam = async (data: CreateExamValues) => { + try { + let response + if (defaultValues) { + if (!defaultValues.id) { + console.error('Error: Exam ID is missing for update.') + toast.error('Exam ID is missing. Cannot update exam.') + return + } + const examData = { id: defaultValues?.id, ...data } + response = await updateExam(examData) + + toast.success('Exam updated successfully!') + } else { + response = await createExam(data) + toast.success('Exam created successfully!') + } + + if (response && response.data) { + fetchExams() + onClose() + reset() + } else { + toast.error('Failed to save exam.') + } + } catch (error) { + console.error('Error handling exam:', error) + toast.error('An error occurred. Please try again.') + } + } + + return ( + + {trigger && {trigger}} + +
+ +
+
+ + {defaultValues ? 'Edit Exam' : 'Create New Exam'} + + + + + +
+ +
+
+ + + {errors.title && ( +

{errors.title.message}

+ )} +
+ +
+ +