Add deck generation and study flow improvements
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m22s
All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m22s
This commit is contained in:
parent
d6f3502cb1
commit
7b90409f2e
36 changed files with 8683 additions and 31 deletions
840
package-lock.json
generated
840
package-lock.json
generated
|
|
@ -22,6 +22,7 @@
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
|
"ts-fsrs": "^5.4.1",
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -33,7 +34,8 @@
|
||||||
"eslint-config-next": "16.2.9",
|
"eslint-config-next": "16.2.9",
|
||||||
"prisma": "^7.8.0",
|
"prisma": "^7.8.0",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"typescript": "^5",
|
||||||
|
"vitest": "^4.1.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@alloc/quick-lru": {
|
"node_modules/@alloc/quick-lru": {
|
||||||
|
|
@ -1349,6 +1351,16 @@
|
||||||
"node": ">=12.4.0"
|
"node": ">=12.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@oxc-project/types": {
|
||||||
|
"version": "0.139.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
|
||||||
|
"integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/Boshen"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@phc/format": {
|
"node_modules/@phc/format": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz",
|
||||||
|
|
@ -1737,6 +1749,293 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@rolldown/binding-android-arm64": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-darwin-arm64": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-darwin-x64": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-freebsd-x64": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"freebsd"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-arm64-gnu": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-arm64-musl": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
|
||||||
|
"cpu": [
|
||||||
|
"ppc64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-s390x-gnu": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
|
||||||
|
"cpu": [
|
||||||
|
"s390x"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-x64-gnu": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-linux-x64-musl": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"linux"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-openharmony-arm64": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"openharmony"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-wasm32-wasi": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
|
||||||
|
"cpu": [
|
||||||
|
"wasm32"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/core": "1.11.1",
|
||||||
|
"@emnapi/runtime": "1.11.1",
|
||||||
|
"@napi-rs/wasm-runtime": "^1.1.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": {
|
||||||
|
"version": "1.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
|
||||||
|
"integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@emnapi/wasi-threads": "1.2.2",
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||||
|
"version": "1.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
|
||||||
|
"integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/binding-win32-x64-msvc": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
|
||||||
|
"cpu": [
|
||||||
|
"x64"
|
||||||
|
],
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"win32"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@rolldown/pluginutils": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@rtsao/scc": {
|
"node_modules/@rtsao/scc": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
|
||||||
|
|
@ -2042,6 +2341,17 @@
|
||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/chai": {
|
||||||
|
"version": "5.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
||||||
|
"integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/deep-eql": "*",
|
||||||
|
"assertion-error": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/debug": {
|
"node_modules/@types/debug": {
|
||||||
"version": "4.1.13",
|
"version": "4.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
|
||||||
|
|
@ -2051,6 +2361,13 @@
|
||||||
"@types/ms": "*"
|
"@types/ms": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/deep-eql": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/estree": {
|
"node_modules/@types/estree": {
|
||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||||
|
|
@ -2110,6 +2427,7 @@
|
||||||
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
"integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
|
|
@ -2790,6 +3108,119 @@
|
||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@vitest/expect": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@standard-schema/spec": "^1.1.0",
|
||||||
|
"@types/chai": "^5.2.2",
|
||||||
|
"@vitest/spy": "4.1.10",
|
||||||
|
"@vitest/utils": "4.1.10",
|
||||||
|
"chai": "^6.2.2",
|
||||||
|
"tinyrainbow": "^3.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/mocker": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/spy": "4.1.10",
|
||||||
|
"estree-walker": "^3.0.3",
|
||||||
|
"magic-string": "^0.30.21"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"msw": "^2.4.9",
|
||||||
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"msw": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vite": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/pretty-format": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tinyrainbow": "^3.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/runner": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/utils": "4.1.10",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/snapshot": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/pretty-format": "4.1.10",
|
||||||
|
"@vitest/utils": "4.1.10",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"pathe": "^2.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/spy": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vitest/utils": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/pretty-format": "4.1.10",
|
||||||
|
"convert-source-map": "^2.0.0",
|
||||||
|
"tinyrainbow": "^3.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.17.0",
|
"version": "8.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
|
||||||
|
|
@ -3040,6 +3471,16 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/assertion-error": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ast-types-flow": {
|
"node_modules/ast-types-flow": {
|
||||||
"version": "0.0.8",
|
"version": "0.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
|
||||||
|
|
@ -3395,6 +3836,16 @@
|
||||||
"url": "https://github.com/sponsors/wooorm"
|
"url": "https://github.com/sponsors/wooorm"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chai": {
|
||||||
|
"version": "6.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
|
||||||
|
"integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
|
@ -4061,6 +4512,13 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/es-module-lexer": {
|
||||||
|
"version": "2.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz",
|
||||||
|
"integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/es-object-atoms": {
|
"node_modules/es-object-atoms": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
|
||||||
|
|
@ -4555,6 +5013,16 @@
|
||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/estree-walker": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/estree": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esutils": {
|
"node_modules/esutils": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||||
|
|
@ -4574,6 +5042,16 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/expect-type": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/exsolve": {
|
"node_modules/exsolve": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz",
|
||||||
|
|
@ -4797,6 +5275,21 @@
|
||||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/fsevents": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
|
"dev": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/function-bind": {
|
"node_modules/function-bind": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
|
@ -7675,6 +8168,20 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/obug": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/sxzz",
|
||||||
|
"https://opencollective.com/debug"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.20.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ohash": {
|
"node_modules/ohash": {
|
||||||
"version": "2.0.11",
|
"version": "2.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
|
||||||
|
|
@ -8407,6 +8914,40 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rolldown": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@oxc-project/types": "=0.139.0",
|
||||||
|
"@rolldown/pluginutils": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"rolldown": "bin/cli.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@rolldown/binding-android-arm64": "1.1.5",
|
||||||
|
"@rolldown/binding-darwin-arm64": "1.1.5",
|
||||||
|
"@rolldown/binding-darwin-x64": "1.1.5",
|
||||||
|
"@rolldown/binding-freebsd-x64": "1.1.5",
|
||||||
|
"@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
|
||||||
|
"@rolldown/binding-linux-arm64-gnu": "1.1.5",
|
||||||
|
"@rolldown/binding-linux-arm64-musl": "1.1.5",
|
||||||
|
"@rolldown/binding-linux-ppc64-gnu": "1.1.5",
|
||||||
|
"@rolldown/binding-linux-s390x-gnu": "1.1.5",
|
||||||
|
"@rolldown/binding-linux-x64-gnu": "1.1.5",
|
||||||
|
"@rolldown/binding-linux-x64-musl": "1.1.5",
|
||||||
|
"@rolldown/binding-openharmony-arm64": "1.1.5",
|
||||||
|
"@rolldown/binding-wasm32-wasi": "1.1.5",
|
||||||
|
"@rolldown/binding-win32-arm64-msvc": "1.1.5",
|
||||||
|
"@rolldown/binding-win32-x64-msvc": "1.1.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
|
|
@ -8739,6 +9280,13 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/siginfo": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/signal-exit": {
|
"node_modules/signal-exit": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||||
|
|
@ -8833,6 +9381,13 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/stackback": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/std-env": {
|
"node_modules/std-env": {
|
||||||
"version": "3.10.0",
|
"version": "3.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
|
||||||
|
|
@ -9130,6 +9685,23 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tinybench": {
|
||||||
|
"version": "2.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||||
|
"integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/tinyexec": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.17",
|
"version": "0.2.17",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
|
||||||
|
|
@ -9179,6 +9751,16 @@
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tinyrainbow": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/to-regex-range": {
|
"node_modules/to-regex-range": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
|
|
@ -9225,6 +9807,15 @@
|
||||||
"typescript": ">=4.8.4"
|
"typescript": ">=4.8.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ts-fsrs": {
|
||||||
|
"version": "5.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ts-fsrs/-/ts-fsrs-5.4.1.tgz",
|
||||||
|
"integrity": "sha512-mOp9+oexJexBTkwjg/jQI1aSUQRLIAvbimeKHLSmVdNJPwObugFNKmZkoggH5d6kZ0uaWLboP1Al1DnXAfIb9w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=20.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tsconfig-paths": {
|
"node_modules/tsconfig-paths": {
|
||||||
"version": "3.15.0",
|
"version": "3.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
|
||||||
|
|
@ -9646,6 +10237,236 @@
|
||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/vite": {
|
||||||
|
"version": "8.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz",
|
||||||
|
"integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"lightningcss": "^1.32.0",
|
||||||
|
"picomatch": "^4.0.5",
|
||||||
|
"postcss": "^8.5.16",
|
||||||
|
"rolldown": "~1.1.4",
|
||||||
|
"tinyglobby": "^0.2.17"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"vite": "bin/vite.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.19.0 || >=22.12.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"fsevents": "~2.3.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/node": "^20.19.0 || >=22.12.0",
|
||||||
|
"@vitejs/devtools": "^0.3.0",
|
||||||
|
"esbuild": "^0.27.0 || ^0.28.0",
|
||||||
|
"jiti": ">=1.21.0",
|
||||||
|
"less": "^4.0.0",
|
||||||
|
"sass": "^1.70.0",
|
||||||
|
"sass-embedded": "^1.70.0",
|
||||||
|
"stylus": ">=0.54.8",
|
||||||
|
"sugarss": "^5.0.0",
|
||||||
|
"terser": "^5.16.0",
|
||||||
|
"tsx": "^4.8.1",
|
||||||
|
"yaml": "^2.4.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitejs/devtools": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"esbuild": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"jiti": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"less": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"sass": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"sass-embedded": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"stylus": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"sugarss": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"terser": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"tsx": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"yaml": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/picomatch": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vite/node_modules/postcss": {
|
||||||
|
"version": "8.5.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
|
||||||
|
"integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"nanoid": "^3.3.12",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"source-map-js": "^1.2.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest": {
|
||||||
|
"version": "4.1.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz",
|
||||||
|
"integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@vitest/expect": "4.1.10",
|
||||||
|
"@vitest/mocker": "4.1.10",
|
||||||
|
"@vitest/pretty-format": "4.1.10",
|
||||||
|
"@vitest/runner": "4.1.10",
|
||||||
|
"@vitest/snapshot": "4.1.10",
|
||||||
|
"@vitest/spy": "4.1.10",
|
||||||
|
"@vitest/utils": "4.1.10",
|
||||||
|
"es-module-lexer": "^2.0.0",
|
||||||
|
"expect-type": "^1.3.0",
|
||||||
|
"magic-string": "^0.30.21",
|
||||||
|
"obug": "^2.1.1",
|
||||||
|
"pathe": "^2.0.3",
|
||||||
|
"picomatch": "^4.0.3",
|
||||||
|
"std-env": "^4.0.0-rc.1",
|
||||||
|
"tinybench": "^2.9.0",
|
||||||
|
"tinyexec": "^1.0.2",
|
||||||
|
"tinyglobby": "^0.2.15",
|
||||||
|
"tinyrainbow": "^3.1.0",
|
||||||
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0",
|
||||||
|
"why-is-node-running": "^2.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"vitest": "vitest.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^20.0.0 || ^22.0.0 || >=24.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/vitest"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@edge-runtime/vm": "*",
|
||||||
|
"@opentelemetry/api": "^1.9.0",
|
||||||
|
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
|
||||||
|
"@vitest/browser-playwright": "4.1.10",
|
||||||
|
"@vitest/browser-preview": "4.1.10",
|
||||||
|
"@vitest/browser-webdriverio": "4.1.10",
|
||||||
|
"@vitest/coverage-istanbul": "4.1.10",
|
||||||
|
"@vitest/coverage-v8": "4.1.10",
|
||||||
|
"@vitest/ui": "4.1.10",
|
||||||
|
"happy-dom": "*",
|
||||||
|
"jsdom": "*",
|
||||||
|
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@edge-runtime/vm": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@opentelemetry/api": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/browser-playwright": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/browser-preview": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/browser-webdriverio": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/coverage-istanbul": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/coverage-v8": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@vitest/ui": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"happy-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"jsdom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"vite": {
|
||||||
|
"optional": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/picomatch": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vitest/node_modules/std-env": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|
@ -9750,6 +10571,23 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/why-is-node-running": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"siginfo": "^2.0.0",
|
||||||
|
"stackback": "0.0.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"why-is-node-running": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.5",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint"
|
"lint": "eslint",
|
||||||
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
|
@ -23,6 +24,7 @@
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
|
"ts-fsrs": "^5.4.1",
|
||||||
"zod": "^4.4.3"
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -34,6 +36,7 @@
|
||||||
"eslint-config-next": "16.2.9",
|
"eslint-config-next": "16.2.9",
|
||||||
"prisma": "^7.8.0",
|
"prisma": "^7.8.0",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
"typescript": "^5"
|
"typescript": "^5",
|
||||||
|
"vitest": "^4.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SpacedRepetitionSet" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"classId" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"description" TEXT,
|
||||||
|
"newCardsPerDay" INTEGER NOT NULL DEFAULT 30,
|
||||||
|
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "SpacedRepetitionSet_classId_fkey" FOREIGN KEY ("classId") REFERENCES "Class" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SpacedRepetitionSetDeck" (
|
||||||
|
"setId" TEXT NOT NULL,
|
||||||
|
"deckId" TEXT NOT NULL,
|
||||||
|
"sortOrder" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"addedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
PRIMARY KEY ("setId", "deckId"),
|
||||||
|
CONSTRAINT "SpacedRepetitionSetDeck_setId_fkey" FOREIGN KEY ("setId") REFERENCES "SpacedRepetitionSet" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "SpacedRepetitionSetDeck_deckId_fkey" FOREIGN KEY ("deckId") REFERENCES "Deck" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SpacedRepetitionCardState" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"setId" TEXT NOT NULL,
|
||||||
|
"flashcardId" TEXT NOT NULL,
|
||||||
|
"due" DATETIME NOT NULL,
|
||||||
|
"stability" REAL NOT NULL,
|
||||||
|
"difficulty" REAL NOT NULL,
|
||||||
|
"elapsedDays" INTEGER NOT NULL,
|
||||||
|
"scheduledDays" INTEGER NOT NULL,
|
||||||
|
"learningSteps" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"reps" INTEGER NOT NULL,
|
||||||
|
"lapses" INTEGER NOT NULL,
|
||||||
|
"state" INTEGER NOT NULL,
|
||||||
|
"firstReviewedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"lastReview" DATETIME,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "SpacedRepetitionCardState_setId_fkey" FOREIGN KEY ("setId") REFERENCES "SpacedRepetitionSet" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT "SpacedRepetitionCardState_flashcardId_fkey" FOREIGN KEY ("flashcardId") REFERENCES "Flashcard" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "SpacedRepetitionSet_classId_sortOrder_idx" ON "SpacedRepetitionSet"("classId", "sortOrder");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "SpacedRepetitionSetDeck_deckId_idx" ON "SpacedRepetitionSetDeck"("deckId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "SpacedRepetitionCardState_setId_due_idx" ON "SpacedRepetitionCardState"("setId", "due");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "SpacedRepetitionCardState_setId_firstReviewedAt_idx" ON "SpacedRepetitionCardState"("setId", "firstReviewedAt");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "SpacedRepetitionCardState_flashcardId_idx" ON "SpacedRepetitionCardState"("flashcardId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "SpacedRepetitionCardState_setId_flashcardId_key" ON "SpacedRepetitionCardState"("setId", "flashcardId");
|
||||||
|
|
@ -14,9 +14,10 @@ model Class {
|
||||||
sortOrder Int @default(0)
|
sortOrder Int @default(0)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
decks Deck[]
|
decks Deck[]
|
||||||
quizSets QuizSet[]
|
quizSets QuizSet[]
|
||||||
materialGroups MaterialGroup[]
|
materialGroups MaterialGroup[]
|
||||||
|
spacedRepetitionSets SpacedRepetitionSet[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Deck {
|
model Deck {
|
||||||
|
|
@ -27,12 +28,13 @@ model Deck {
|
||||||
sortOrder Int @default(0)
|
sortOrder Int @default(0)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||||||
groupId String?
|
groupId String?
|
||||||
group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
|
group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
|
||||||
cards Flashcard[]
|
cards Flashcard[]
|
||||||
shareLink ShareLink?
|
shareLink ShareLink?
|
||||||
progress StudyProgress[]
|
progress StudyProgress[]
|
||||||
|
spacedRepetitionMemberships SpacedRepetitionSetDeck[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Flashcard {
|
model Flashcard {
|
||||||
|
|
@ -42,7 +44,8 @@ model Flashcard {
|
||||||
back String
|
back String
|
||||||
sortOrder Int @default(0)
|
sortOrder Int @default(0)
|
||||||
|
|
||||||
deck Deck @relation(fields: [deckId], references: [id], onDelete: Cascade)
|
deck Deck @relation(fields: [deckId], references: [id], onDelete: Cascade)
|
||||||
|
spacedRepetitionStates SpacedRepetitionCardState[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model QuizSet {
|
model QuizSet {
|
||||||
|
|
@ -53,9 +56,9 @@ model QuizSet {
|
||||||
sortOrder Int @default(0)
|
sortOrder Int @default(0)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||||||
groupId String?
|
groupId String?
|
||||||
group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
|
group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)
|
||||||
questions Question[]
|
questions Question[]
|
||||||
attempts QuizAttempt[]
|
attempts QuizAttempt[]
|
||||||
shareLink ShareLink?
|
shareLink ShareLink?
|
||||||
|
|
@ -153,12 +156,69 @@ model MaterialGroup {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
classId String
|
classId String
|
||||||
name String
|
name String
|
||||||
type String // "DECK" | "QUIZ"
|
type String // "DECK" | "QUIZ"
|
||||||
sortOrder Int @default(0)
|
sortOrder Int @default(0)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||||||
decks Deck[]
|
decks Deck[]
|
||||||
quizSets QuizSet[]
|
quizSets QuizSet[]
|
||||||
shareLink ShareLink?
|
shareLink ShareLink?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model SpacedRepetitionSet {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
classId String
|
||||||
|
name String
|
||||||
|
description String?
|
||||||
|
newCardsPerDay Int @default(30)
|
||||||
|
sortOrder Int @default(0)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||||||
|
decks SpacedRepetitionSetDeck[]
|
||||||
|
cardStates SpacedRepetitionCardState[]
|
||||||
|
|
||||||
|
@@index([classId, sortOrder])
|
||||||
|
}
|
||||||
|
|
||||||
|
model SpacedRepetitionSetDeck {
|
||||||
|
setId String
|
||||||
|
deckId String
|
||||||
|
sortOrder Int @default(0)
|
||||||
|
addedAt DateTime @default(now())
|
||||||
|
|
||||||
|
set SpacedRepetitionSet @relation(fields: [setId], references: [id], onDelete: Cascade)
|
||||||
|
deck Deck @relation(fields: [deckId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@id([setId, deckId])
|
||||||
|
@@index([deckId])
|
||||||
|
}
|
||||||
|
|
||||||
|
model SpacedRepetitionCardState {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
setId String
|
||||||
|
flashcardId String
|
||||||
|
due DateTime
|
||||||
|
stability Float
|
||||||
|
difficulty Float
|
||||||
|
elapsedDays Int
|
||||||
|
scheduledDays Int
|
||||||
|
learningSteps Int @default(0)
|
||||||
|
reps Int
|
||||||
|
lapses Int
|
||||||
|
state Int
|
||||||
|
firstReviewedAt DateTime @default(now())
|
||||||
|
lastReview DateTime?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
|
||||||
|
set SpacedRepetitionSet @relation(fields: [setId], references: [id], onDelete: Cascade)
|
||||||
|
flashcard Flashcard @relation(fields: [flashcardId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([setId, flashcardId])
|
||||||
|
@@index([setId, due])
|
||||||
|
@@index([setId, firstReviewedAt])
|
||||||
|
@@index([flashcardId])
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { SpacedRepetitionViewer } from "@/components/spaced-repetition/SpacedRepetitionViewer";
|
||||||
|
|
||||||
|
export default function SpacedRepetitionStudyPage() {
|
||||||
|
return <SpacedRepetitionViewer />;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { SpacedRepetitionSets } from "@/components/spaced-repetition/SpacedRepetitionSets";
|
||||||
|
|
||||||
|
export default function SpacedRepetitionPage() {
|
||||||
|
return <SpacedRepetitionSets />;
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,10 @@ interface ClassItem {
|
||||||
slug: string;
|
slug: string;
|
||||||
name: string;
|
name: string;
|
||||||
_count: { decks: number; quizSets: number };
|
_count: { decks: number; quizSets: number };
|
||||||
|
spacedRepetitionDueCards: number;
|
||||||
|
spacedRepetitionLearningCards: number;
|
||||||
|
spacedRepetitionNewCards: number;
|
||||||
|
spacedRepetitionReadyCards: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const accents = ["#4f46e5", "#f97360", "#25845f", "#c27718", "#8b5cf6", "#0284c7"];
|
const accents = ["#4f46e5", "#f97360", "#25845f", "#c27718", "#8b5cf6", "#0284c7"];
|
||||||
|
|
@ -114,6 +118,14 @@ export default function HomePage() {
|
||||||
<div className="mt-3 flex flex-wrap gap-2 text-xs font-semibold text-text-secondary">
|
<div className="mt-3 flex flex-wrap gap-2 text-xs font-semibold text-text-secondary">
|
||||||
<span className="rounded-full bg-bg-surface-alt px-2.5 py-1">{cls._count.decks} {cls._count.decks === 1 ? "deck" : "decks"}</span>
|
<span className="rounded-full bg-bg-surface-alt px-2.5 py-1">{cls._count.decks} {cls._count.decks === 1 ? "deck" : "decks"}</span>
|
||||||
<span className="rounded-full bg-bg-surface-alt px-2.5 py-1">{cls._count.quizSets} {cls._count.quizSets === 1 ? "quiz" : "quizzes"}</span>
|
<span className="rounded-full bg-bg-surface-alt px-2.5 py-1">{cls._count.quizSets} {cls._count.quizSets === 1 ? "quiz" : "quizzes"}</span>
|
||||||
|
{cls.spacedRepetitionReadyCards > 0 && (
|
||||||
|
<span
|
||||||
|
className="rounded-full bg-error-bg px-2.5 py-1 text-error"
|
||||||
|
title={`${cls.spacedRepetitionDueCards} due, ${cls.spacedRepetitionLearningCards} learning, ${cls.spacedRepetitionNewCards} new`}
|
||||||
|
>
|
||||||
|
{cls.spacedRepetitionReadyCards} {cls.spacedRepetitionReadyCards === 1 ? "card" : "cards"} ready
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="absolute right-3 top-4 flex gap-1 rounded-xl border border-border-light bg-bg-surface p-1 opacity-100 shadow-sm sm:opacity-0 sm:group-hover:opacity-100">
|
<div className="absolute right-3 top-4 flex gap-1 rounded-xl border border-border-light bg-bg-surface p-1 opacity-100 shadow-sm sm:opacity-0 sm:group-hover:opacity-100">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { spacedRepetitionErrorResponse } from "@/lib/spacedRepetitionApi";
|
||||||
|
import * as spacedRepetitionService from "@/services/spacedRepetitionService";
|
||||||
|
|
||||||
|
export async function DELETE(
|
||||||
|
_request: NextRequest,
|
||||||
|
context: { params: Promise<{ id: string; deckId: string }> }
|
||||||
|
) {
|
||||||
|
const { id, deckId } = await context.params;
|
||||||
|
try {
|
||||||
|
await spacedRepetitionService.removeDeck(id, deckId);
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
return spacedRepetitionErrorResponse(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/app/api/spaced-repetition-sets/[id]/decks/route.ts
Normal file
43
src/app/api/spaced-repetition-sets/[id]/decks/route.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import {
|
||||||
|
addSpacedRepetitionDeckSchema,
|
||||||
|
reorderSpacedRepetitionDecksSchema,
|
||||||
|
} from "@/lib/validation/spacedRepetitionSchemas";
|
||||||
|
import { spacedRepetitionErrorResponse } from "@/lib/spacedRepetitionApi";
|
||||||
|
import * as spacedRepetitionService from "@/services/spacedRepetitionService";
|
||||||
|
|
||||||
|
export async function POST(
|
||||||
|
request: NextRequest,
|
||||||
|
context: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
const { id } = await context.params;
|
||||||
|
const parsed = addSpacedRepetitionDeckSchema.safeParse(
|
||||||
|
await request.json().catch(() => null)
|
||||||
|
);
|
||||||
|
if (!parsed.success) return NextResponse.json({ error: "Invalid deck" }, { status: 400 });
|
||||||
|
try {
|
||||||
|
return NextResponse.json(
|
||||||
|
await spacedRepetitionService.addDeck(id, parsed.data.deckId),
|
||||||
|
{ status: 201 }
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return spacedRepetitionErrorResponse(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PATCH(
|
||||||
|
request: NextRequest,
|
||||||
|
context: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
const { id } = await context.params;
|
||||||
|
const parsed = reorderSpacedRepetitionDecksSchema.safeParse(
|
||||||
|
await request.json().catch(() => null)
|
||||||
|
);
|
||||||
|
if (!parsed.success) return NextResponse.json({ error: "Invalid deck order" }, { status: 400 });
|
||||||
|
try {
|
||||||
|
await spacedRepetitionService.reorderDecks(id, parsed.data.deckIds);
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
return spacedRepetitionErrorResponse(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/app/api/spaced-repetition-sets/[id]/reviews/route.ts
Normal file
22
src/app/api/spaced-repetition-sets/[id]/reviews/route.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { reviewSpacedRepetitionCardSchema } from "@/lib/validation/spacedRepetitionSchemas";
|
||||||
|
import { spacedRepetitionErrorResponse } from "@/lib/spacedRepetitionApi";
|
||||||
|
import * as spacedRepetitionService from "@/services/spacedRepetitionService";
|
||||||
|
|
||||||
|
export async function POST(
|
||||||
|
request: NextRequest,
|
||||||
|
context: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
const { id } = await context.params;
|
||||||
|
const parsed = reviewSpacedRepetitionCardSchema.safeParse(
|
||||||
|
await request.json().catch(() => null)
|
||||||
|
);
|
||||||
|
if (!parsed.success) return NextResponse.json({ error: "Invalid review" }, { status: 400 });
|
||||||
|
try {
|
||||||
|
return NextResponse.json(
|
||||||
|
await spacedRepetitionService.reviewCard({ setId: id, ...parsed.data })
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return spacedRepetitionErrorResponse(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/app/api/spaced-repetition-sets/[id]/route.ts
Normal file
45
src/app/api/spaced-repetition-sets/[id]/route.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { updateSpacedRepetitionSetSchema } from "@/lib/validation/spacedRepetitionSchemas";
|
||||||
|
import { spacedRepetitionErrorResponse } from "@/lib/spacedRepetitionApi";
|
||||||
|
import * as spacedRepetitionService from "@/services/spacedRepetitionService";
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
_request: NextRequest,
|
||||||
|
context: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
const { id } = await context.params;
|
||||||
|
try {
|
||||||
|
return NextResponse.json(await spacedRepetitionService.getSet(id));
|
||||||
|
} catch (error) {
|
||||||
|
return spacedRepetitionErrorResponse(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function PATCH(
|
||||||
|
request: NextRequest,
|
||||||
|
context: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
const { id } = await context.params;
|
||||||
|
const parsed = updateSpacedRepetitionSetSchema.safeParse(
|
||||||
|
await request.json().catch(() => null)
|
||||||
|
);
|
||||||
|
if (!parsed.success) return NextResponse.json({ error: "Invalid update" }, { status: 400 });
|
||||||
|
try {
|
||||||
|
return NextResponse.json(await spacedRepetitionService.updateSet(id, parsed.data));
|
||||||
|
} catch (error) {
|
||||||
|
return spacedRepetitionErrorResponse(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function DELETE(
|
||||||
|
_request: NextRequest,
|
||||||
|
context: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
const { id } = await context.params;
|
||||||
|
try {
|
||||||
|
await spacedRepetitionService.deleteSet(id);
|
||||||
|
return NextResponse.json({ success: true });
|
||||||
|
} catch (error) {
|
||||||
|
return spacedRepetitionErrorResponse(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/app/api/spaced-repetition-sets/[id]/study/route.ts
Normal file
15
src/app/api/spaced-repetition-sets/[id]/study/route.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { spacedRepetitionErrorResponse } from "@/lib/spacedRepetitionApi";
|
||||||
|
import * as spacedRepetitionService from "@/services/spacedRepetitionService";
|
||||||
|
|
||||||
|
export async function GET(
|
||||||
|
_request: NextRequest,
|
||||||
|
context: { params: Promise<{ id: string }> }
|
||||||
|
) {
|
||||||
|
const { id } = await context.params;
|
||||||
|
try {
|
||||||
|
return NextResponse.json(await spacedRepetitionService.getStudyState(id));
|
||||||
|
} catch (error) {
|
||||||
|
return spacedRepetitionErrorResponse(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/app/api/spaced-repetition-sets/route.ts
Normal file
30
src/app/api/spaced-repetition-sets/route.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { createSpacedRepetitionSetSchema } from "@/lib/validation/spacedRepetitionSchemas";
|
||||||
|
import { spacedRepetitionErrorResponse } from "@/lib/spacedRepetitionApi";
|
||||||
|
import * as spacedRepetitionService from "@/services/spacedRepetitionService";
|
||||||
|
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const classId = new URL(request.url).searchParams.get("classId");
|
||||||
|
if (!classId) return NextResponse.json({ error: "classId is required" }, { status: 400 });
|
||||||
|
try {
|
||||||
|
return NextResponse.json(await spacedRepetitionService.listSetsByClass(classId));
|
||||||
|
} catch (error) {
|
||||||
|
return spacedRepetitionErrorResponse(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
const parsed = createSpacedRepetitionSetSchema.safeParse(
|
||||||
|
await request.json().catch(() => null)
|
||||||
|
);
|
||||||
|
if (!parsed.success) {
|
||||||
|
return NextResponse.json({ error: "Invalid repetition set" }, { status: 400 });
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return NextResponse.json(await spacedRepetitionService.createSet(parsed.data), {
|
||||||
|
status: 201,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return spacedRepetitionErrorResponse(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
365
src/components/spaced-repetition/SpacedRepetitionSets.tsx
Normal file
365
src/components/spaced-repetition/SpacedRepetitionSets.tsx
Normal file
|
|
@ -0,0 +1,365 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
/* eslint-disable react-hooks/refs -- @dnd-kit intentionally returns render-time ref callbacks and drag state. */
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
KeyboardSensor,
|
||||||
|
PointerSensor,
|
||||||
|
pointerWithin,
|
||||||
|
useDraggable,
|
||||||
|
useDndContext,
|
||||||
|
useDroppable,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
type DragEndEvent,
|
||||||
|
} from "@dnd-kit/core";
|
||||||
|
import {
|
||||||
|
SortableContext,
|
||||||
|
arrayMove,
|
||||||
|
sortableKeyboardCoordinates,
|
||||||
|
useSortable,
|
||||||
|
verticalListSortingStrategy,
|
||||||
|
} from "@dnd-kit/sortable";
|
||||||
|
import { CSS } from "@dnd-kit/utilities";
|
||||||
|
|
||||||
|
interface DeckSummary {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
sortOrder?: number;
|
||||||
|
_count: { cards: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SetSummary {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
newCardsPerDay: number;
|
||||||
|
decks: DeckSummary[];
|
||||||
|
stats: {
|
||||||
|
totalCards: number;
|
||||||
|
reviewedCards: number;
|
||||||
|
unseenCards: number;
|
||||||
|
dueCards: number;
|
||||||
|
learningAheadCards: number;
|
||||||
|
newCardsAvailable: number;
|
||||||
|
availableCards: number;
|
||||||
|
nextDue: string | null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EditState {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
newCardsPerDay: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyStudyAvailabilityChanged() {
|
||||||
|
window.dispatchEvent(new Event("spaced-repetition-updated"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function SourceDeck({ deck }: { deck: DeckSummary }) {
|
||||||
|
const drag = useDraggable({
|
||||||
|
id: `source:${deck.id}`,
|
||||||
|
data: { deckId: deck.id, sourceSetId: null },
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={drag.setNodeRef}
|
||||||
|
style={{ transform: CSS.Translate.toString(drag.transform), opacity: drag.isDragging ? 0.45 : 1 }}
|
||||||
|
className="flex min-w-0 items-center gap-3 rounded-xl border border-border-light bg-bg-surface p-4 shadow-sm"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
{...drag.attributes}
|
||||||
|
{...drag.listeners}
|
||||||
|
className="grid h-10 w-10 shrink-0 place-items-center rounded-lg text-text-muted hover:bg-bg-surface-alt hover:text-text-heading"
|
||||||
|
aria-label={`Drag ${deck.name} into a repetition set`}
|
||||||
|
>
|
||||||
|
<span aria-hidden>☷</span>
|
||||||
|
</button>
|
||||||
|
<div className="min-w-0">
|
||||||
|
<p className="truncate font-bold text-text-heading">{deck.name}</p>
|
||||||
|
<p className="text-xs text-text-muted">{deck._count.cards} cards</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MembershipDeck({ deck, setId, onRemove }: {
|
||||||
|
deck: DeckSummary;
|
||||||
|
setId: string;
|
||||||
|
onRemove: () => void;
|
||||||
|
}) {
|
||||||
|
const sortable = useSortable({
|
||||||
|
id: `set:${setId}:deck:${deck.id}`,
|
||||||
|
data: { deckId: deck.id, sourceSetId: setId, setId },
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={sortable.setNodeRef}
|
||||||
|
style={{
|
||||||
|
transform: CSS.Transform.toString(sortable.transform),
|
||||||
|
transition: sortable.transition,
|
||||||
|
opacity: sortable.isDragging ? 0.4 : 1,
|
||||||
|
}}
|
||||||
|
className="flex min-w-0 items-center gap-2 rounded-xl border border-border-light bg-bg-surface-alt/55 p-3"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
{...sortable.attributes}
|
||||||
|
{...sortable.listeners}
|
||||||
|
className="grid h-9 w-9 shrink-0 place-items-center rounded-lg text-text-muted hover:bg-bg-surface hover:text-text-heading"
|
||||||
|
aria-label={`Reorder or copy ${deck.name}`}
|
||||||
|
>
|
||||||
|
<span aria-hidden>☷</span>
|
||||||
|
</button>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<p className="truncate text-sm font-bold text-text-heading">{deck.name}</p>
|
||||||
|
<p className="text-xs text-text-muted">{deck._count.cards} cards</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={onRemove}
|
||||||
|
className="grid h-9 w-9 shrink-0 place-items-center rounded-lg text-text-muted hover:bg-error-bg hover:text-error"
|
||||||
|
aria-label={`Remove ${deck.name} from this repetition set`}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetDropZone({ set, children }: { set: SetSummary; children: React.ReactNode }) {
|
||||||
|
const drop = useDroppable({ id: `set:${set.id}`, data: { setId: set.id } });
|
||||||
|
const { over } = useDndContext();
|
||||||
|
const isOver =
|
||||||
|
over?.id === `set:${set.id}` || over?.data.current?.setId === set.id;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={drop.setNodeRef}
|
||||||
|
className={`grid min-h-20 grid-cols-1 gap-3 rounded-xl border-2 border-dashed p-3 sm:grid-cols-2 ${
|
||||||
|
isOver ? "border-primary bg-primary/5" : "border-border-light"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SpacedRepetitionSets() {
|
||||||
|
const { classSlug } = useParams<{ classSlug: string }>();
|
||||||
|
const [classId, setClassId] = useState("");
|
||||||
|
const [sets, setSets] = useState<SetSummary[]>([]);
|
||||||
|
const [decks, setDecks] = useState<DeckSummary[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [creating, setCreating] = useState(false);
|
||||||
|
const [newName, setNewName] = useState("");
|
||||||
|
const [editing, setEditing] = useState<EditState | null>(null);
|
||||||
|
const [message, setMessage] = useState<string | null>(null);
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(PointerSensor, { activationConstraint: { distance: 6 } }),
|
||||||
|
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
||||||
|
);
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const classesResponse = await fetch("/api/classes");
|
||||||
|
const classes = (await classesResponse.json()) as Array<{ id: string; slug: string }>;
|
||||||
|
const currentClass = classes.find((item) => item.slug === classSlug);
|
||||||
|
if (!currentClass) throw new Error("Class not found");
|
||||||
|
setClassId(currentClass.id);
|
||||||
|
const [setsResponse, decksResponse] = await Promise.all([
|
||||||
|
fetch(`/api/spaced-repetition-sets?classId=${currentClass.id}`),
|
||||||
|
fetch(`/api/decks/list?classId=${currentClass.id}`),
|
||||||
|
]);
|
||||||
|
if (!setsResponse.ok || !decksResponse.ok) throw new Error("Unable to load repetition sets");
|
||||||
|
setSets(await setsResponse.json());
|
||||||
|
setDecks(await decksResponse.json());
|
||||||
|
} catch (error) {
|
||||||
|
setMessage(error instanceof Error ? error.message : "Unable to load repetition sets");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [classSlug]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = window.setTimeout(() => void load(), 0);
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
}, [load]);
|
||||||
|
|
||||||
|
async function createSet() {
|
||||||
|
if (!newName.trim()) return;
|
||||||
|
const response = await fetch("/api/spaced-repetition-sets", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ classId, name: newName, newCardsPerDay: 30 }),
|
||||||
|
});
|
||||||
|
if (!response.ok) return setMessage("Could not create repetition set");
|
||||||
|
setNewName("");
|
||||||
|
setCreating(false);
|
||||||
|
await load();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveEdit() {
|
||||||
|
if (!editing) return;
|
||||||
|
const response = await fetch(`/api/spaced-repetition-sets/${editing.id}`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: editing.name,
|
||||||
|
description: editing.description,
|
||||||
|
newCardsPerDay: editing.newCardsPerDay,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (!response.ok) return setMessage("Could not update repetition set");
|
||||||
|
setEditing(null);
|
||||||
|
notifyStudyAvailabilityChanged();
|
||||||
|
await load();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteSet(set: SetSummary) {
|
||||||
|
if (!confirm(`Delete “${set.name}”? Its spaced-repetition schedules will be permanently removed.`)) return;
|
||||||
|
const response = await fetch(`/api/spaced-repetition-sets/${set.id}`, { method: "DELETE" });
|
||||||
|
if (!response.ok) return setMessage("Could not delete repetition set");
|
||||||
|
notifyStudyAvailabilityChanged();
|
||||||
|
await load();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeDeck(set: SetSummary, deck: DeckSummary) {
|
||||||
|
if (!confirm(`Remove “${deck.name}” from “${set.name}”? Its scheduling history in this set will be reset.`)) return;
|
||||||
|
const response = await fetch(`/api/spaced-repetition-sets/${set.id}/decks/${deck.id}`, { method: "DELETE" });
|
||||||
|
if (!response.ok) return setMessage("Could not remove deck");
|
||||||
|
notifyStudyAvailabilityChanged();
|
||||||
|
await load();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addDeck(setId: string, deckId: string) {
|
||||||
|
const target = sets.find((set) => set.id === setId);
|
||||||
|
const deck = decks.find((item) => item.id === deckId);
|
||||||
|
if (target?.decks.some((item) => item.id === deckId)) {
|
||||||
|
setMessage(`${deck?.name ?? "That deck"} is already in ${target.name}.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const response = await fetch(`/api/spaced-repetition-sets/${setId}/decks`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ deckId }),
|
||||||
|
});
|
||||||
|
if (!response.ok) return setMessage((await response.json()).error ?? "Could not add deck");
|
||||||
|
setMessage(`${deck?.name ?? "Deck"} added.`);
|
||||||
|
notifyStudyAvailabilityChanged();
|
||||||
|
await load();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDragEnd(event: DragEndEvent) {
|
||||||
|
const activeData = event.active.data.current as { deckId?: string; sourceSetId?: string | null } | undefined;
|
||||||
|
const overData = event.over?.data.current as { setId?: string; deckId?: string } | undefined;
|
||||||
|
if (!activeData?.deckId || !overData?.setId) return;
|
||||||
|
const targetSetId = overData.setId;
|
||||||
|
|
||||||
|
if (activeData.sourceSetId === targetSetId && overData.deckId) {
|
||||||
|
const target = sets.find((set) => set.id === targetSetId);
|
||||||
|
if (!target) return;
|
||||||
|
const oldIndex = target.decks.findIndex((deck) => deck.id === activeData.deckId);
|
||||||
|
const newIndex = target.decks.findIndex((deck) => deck.id === overData.deckId);
|
||||||
|
if (oldIndex < 0 || newIndex < 0 || oldIndex === newIndex) return;
|
||||||
|
const ordered = arrayMove(target.decks, oldIndex, newIndex);
|
||||||
|
setSets((current) => current.map((set) => set.id === targetSetId ? { ...set, decks: ordered } : set));
|
||||||
|
const response = await fetch(`/api/spaced-repetition-sets/${targetSetId}/decks`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ deckIds: ordered.map((deck) => deck.id) }),
|
||||||
|
});
|
||||||
|
if (!response.ok) await load();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await addDeck(targetSetId, activeData.deckId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) return <div className="animate-subtle-pulse py-16 text-center text-text-muted">Loading repetition sets…</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="pb-20">
|
||||||
|
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||||
|
<div><p className="text-xs font-bold uppercase tracking-[0.16em] text-primary">Adaptive review</p><h2 className="editorial-title mt-1 text-3xl text-text-heading">Spaced repetition</h2></div>
|
||||||
|
<button onClick={() => setCreating(true)} className="min-h-12 rounded-xl bg-primary px-5 font-bold text-white shadow-sm hover:bg-primary-hover">New repetition set</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{message && <div className="mb-5 flex items-center justify-between rounded-xl border border-primary/20 bg-bg-callout px-4 py-3 text-sm text-text-heading"><span>{message}</span><button onClick={() => setMessage(null)} aria-label="Dismiss message">×</button></div>}
|
||||||
|
|
||||||
|
{creating && (
|
||||||
|
<div className="mb-6 flex flex-col gap-3 rounded-2xl border border-primary/20 bg-bg-surface p-5 sm:flex-row">
|
||||||
|
<input autoFocus value={newName} onChange={(event) => setNewName(event.target.value)} onKeyDown={(event) => event.key === "Enter" && createSet()} placeholder="e.g. Patho comprehensive review" className="min-h-11 flex-1 rounded-lg border border-border bg-bg-surface-alt px-3 text-text-heading" />
|
||||||
|
<button onClick={createSet} className="rounded-lg bg-primary px-5 font-bold text-white">Create</button>
|
||||||
|
<button onClick={() => { setCreating(false); setNewName(""); }} className="rounded-lg border border-border px-5 font-bold text-text-heading">Cancel</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<DndContext sensors={sensors} collisionDetection={pointerWithin} onDragEnd={handleDragEnd}>
|
||||||
|
<div className="space-y-6">
|
||||||
|
{sets.length === 0 && <div className="rounded-2xl border border-dashed border-border p-10 text-center"><h3 className="font-bold text-text-heading">No repetition sets yet</h3><p className="mt-1 text-sm text-text-secondary">Create one, then drag flashcard decks into it.</p></div>}
|
||||||
|
{sets.map((set) => (
|
||||||
|
<section key={set.id} className="rounded-2xl border border-border-light bg-bg-surface/75 p-4 shadow-[var(--shadow-card)] sm:p-5">
|
||||||
|
<div className="mb-4 flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
|
||||||
|
<div className="min-w-0">
|
||||||
|
<h3 className="text-xl font-extrabold text-text-heading">{set.name}</h3>
|
||||||
|
{set.description && <p className="mt-1 text-sm text-text-secondary">{set.description}</p>}
|
||||||
|
<div className="mt-3 flex flex-wrap gap-2 text-xs">
|
||||||
|
<span className="rounded-full bg-badge-bg px-2.5 py-1 text-badge-text">{set.stats.totalCards} cards</span>
|
||||||
|
<span className="rounded-full bg-error-bg px-2.5 py-1 text-error">{set.stats.dueCards} due</span>
|
||||||
|
{set.stats.learningAheadCards > 0 && <span className="rounded-full bg-bg-callout px-2.5 py-1 text-primary">{set.stats.learningAheadCards} learning</span>}
|
||||||
|
<span className="rounded-full bg-success-bg px-2.5 py-1 text-success">{set.stats.newCardsAvailable} new today</span>
|
||||||
|
<span className="rounded-full bg-bg-surface-alt px-2.5 py-1 text-text-muted">Limit {set.newCardsPerDay}/day</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
{set.stats.totalCards === 0 ? <button disabled className="min-h-10 rounded-lg bg-bg-surface-alt px-5 text-sm font-bold text-text-muted">Add decks to study</button> : set.stats.availableCards > 0 ? (
|
||||||
|
<Link href={`/${classSlug}/spaced-repetition/${set.id}`} className="inline-flex min-h-10 items-center rounded-lg bg-primary px-5 text-sm font-bold text-white hover:bg-primary-hover">{set.stats.reviewedCards === 0 ? "Study" : "Continue"} · {set.stats.availableCards}</Link>
|
||||||
|
) : <Link href={`/${classSlug}/spaced-repetition/${set.id}`} className="inline-flex min-h-10 items-center rounded-lg bg-success-bg px-5 text-sm font-bold text-success">Caught up</Link>}
|
||||||
|
<button onClick={() => setEditing({ id: set.id, name: set.name, description: set.description ?? "", newCardsPerDay: set.newCardsPerDay })} className="grid h-10 w-10 place-items-center rounded-lg text-text-muted hover:bg-bg-surface-alt hover:text-text-heading" aria-label={`Edit ${set.name}`}>✎</button>
|
||||||
|
<button onClick={() => deleteSet(set)} className="grid h-10 w-10 place-items-center rounded-lg text-text-muted hover:bg-error-bg hover:text-error" aria-label={`Delete ${set.name}`}>
|
||||||
|
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SortableContext items={set.decks.map((deck) => `set:${set.id}:deck:${deck.id}`)} strategy={verticalListSortingStrategy}>
|
||||||
|
<SetDropZone set={set}>
|
||||||
|
{set.decks.length === 0 ? <p className="col-span-full py-4 text-center text-sm text-text-muted">Drop flashcard decks here</p> : set.decks.map((deck) => <MembershipDeck key={deck.id} deck={deck} setId={set.id} onRemove={() => removeDeck(set, deck)} />)}
|
||||||
|
</SetDropZone>
|
||||||
|
</SortableContext>
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<section className="mt-10 border-t border-border-light pt-8">
|
||||||
|
<p className="text-xs font-bold uppercase tracking-[0.16em] text-primary">Source library</p>
|
||||||
|
<h3 className="editorial-title mt-1 text-2xl text-text-heading">Flashcard decks</h3>
|
||||||
|
<p className="mb-5 mt-1 text-sm text-text-secondary">Drag a deck into any repetition set. The original deck stays here and can be reused.</p>
|
||||||
|
{decks.length ? <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">{decks.map((deck) => <SourceDeck key={deck.id} deck={deck} />)}</div> : <div className="rounded-xl border border-dashed border-border p-8 text-center text-sm text-text-muted">Create flashcard decks before building a repetition set.</div>}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</DndContext>
|
||||||
|
|
||||||
|
{editing && (
|
||||||
|
<div className="fixed inset-0 z-50 grid place-items-center bg-black/45 p-4 backdrop-blur-sm">
|
||||||
|
<div className="w-full max-w-md rounded-3xl border border-border bg-bg-surface p-6 shadow-[var(--shadow-modal)]">
|
||||||
|
<h3 className="editorial-title text-2xl text-text-heading">Edit repetition set</h3>
|
||||||
|
<label className="mt-5 block text-sm font-bold text-text-heading">Name</label>
|
||||||
|
<input value={editing.name} onChange={(event) => setEditing({ ...editing, name: event.target.value })} className="mt-1 min-h-11 w-full rounded-lg border border-border bg-bg-surface-alt px-3 text-text-heading" />
|
||||||
|
<label className="mt-4 block text-sm font-bold text-text-heading">Description</label>
|
||||||
|
<textarea value={editing.description} onChange={(event) => setEditing({ ...editing, description: event.target.value })} rows={3} className="mt-1 w-full resize-none rounded-lg border border-border bg-bg-surface-alt p-3 text-text-heading" />
|
||||||
|
<label className="mt-4 block text-sm font-bold text-text-heading">New cards per day</label>
|
||||||
|
<input type="number" min={0} max={999} value={editing.newCardsPerDay} onChange={(event) => setEditing({ ...editing, newCardsPerDay: Number(event.target.value) })} className="mt-1 min-h-11 w-full rounded-lg border border-border bg-bg-surface-alt px-3 text-text-heading" />
|
||||||
|
<p className="mt-1 text-xs text-text-muted">Due reviews are never capped. Set this to 0 to pause new cards.</p>
|
||||||
|
<div className="mt-6 flex justify-end gap-2"><button onClick={() => setEditing(null)} className="min-h-10 rounded-lg border border-border px-4 font-bold text-text-heading">Cancel</button><button onClick={saveEdit} className="min-h-10 rounded-lg bg-primary px-4 font-bold text-white">Save</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
220
src/components/spaced-repetition/SpacedRepetitionViewer.tsx
Normal file
220
src/components/spaced-repetition/SpacedRepetitionViewer.tsx
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import remarkGfm from "remark-gfm";
|
||||||
|
import type { SpacedRepetitionRating } from "@/lib/spacedRepetition";
|
||||||
|
|
||||||
|
interface StudyState {
|
||||||
|
set: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
class: { slug: string; name: string };
|
||||||
|
stats: {
|
||||||
|
totalCards: number;
|
||||||
|
reviewedCards: number;
|
||||||
|
unseenCards: number;
|
||||||
|
dueCards: number;
|
||||||
|
learningAheadCards: number;
|
||||||
|
newCardsAvailable: number;
|
||||||
|
availableCards: number;
|
||||||
|
nextDue: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
currentCard: {
|
||||||
|
id: string;
|
||||||
|
front: string;
|
||||||
|
back: string;
|
||||||
|
sourceDeckName: string;
|
||||||
|
stateVersion: string | null;
|
||||||
|
previews: Array<{
|
||||||
|
rating: SpacedRepetitionRating;
|
||||||
|
due: string;
|
||||||
|
scheduledDays: number;
|
||||||
|
}>;
|
||||||
|
} | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ratingStyles: Record<SpacedRepetitionRating, string> = {
|
||||||
|
AGAIN: "border-error/35 text-error hover:bg-error-bg",
|
||||||
|
HARD: "border-accent/40 text-accent hover:bg-bg-surface-alt",
|
||||||
|
GOOD: "border-success/35 text-success hover:bg-success-bg",
|
||||||
|
EASY: "border-primary/35 text-primary hover:bg-bg-callout",
|
||||||
|
};
|
||||||
|
|
||||||
|
function intervalLabel(due: string, scheduledDays: number) {
|
||||||
|
if (scheduledDays >= 1) {
|
||||||
|
if (scheduledDays < 30) return `${scheduledDays}d`;
|
||||||
|
const months = Math.round(scheduledDays / 30);
|
||||||
|
if (months < 12) return `${months}mo`;
|
||||||
|
return `${Math.round(months / 12)}y`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const minutes = Math.max(1, Math.round((new Date(due).getTime() - Date.now()) / 60000));
|
||||||
|
if (minutes < 60) return `${minutes}m`;
|
||||||
|
const hours = Math.round(minutes / 60);
|
||||||
|
return `${hours}h`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dueLabel(due: string | null) {
|
||||||
|
if (!due) return "No reviews scheduled";
|
||||||
|
return new Intl.DateTimeFormat(undefined, {
|
||||||
|
dateStyle: "medium",
|
||||||
|
timeStyle: "short",
|
||||||
|
}).format(new Date(due));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SpacedRepetitionViewer() {
|
||||||
|
const { classSlug, setId } = useParams<{ classSlug: string; setId: string }>();
|
||||||
|
const router = useRouter();
|
||||||
|
const [data, setData] = useState<StudyState | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [flipped, setFlipped] = useState(false);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const load = useCallback(async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/spaced-repetition-sets/${setId}/study`, {
|
||||||
|
cache: "no-store",
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error("Repetition set not found");
|
||||||
|
const nextData = (await response.json()) as StudyState;
|
||||||
|
if (nextData.set.class.slug !== classSlug) throw new Error("Repetition set not found");
|
||||||
|
setData(nextData);
|
||||||
|
setFlipped(false);
|
||||||
|
setError(null);
|
||||||
|
} catch {
|
||||||
|
router.push(`/${classSlug}/spaced-repetition`);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [classSlug, router, setId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = window.setTimeout(() => void load(), 0);
|
||||||
|
return () => window.clearTimeout(timer);
|
||||||
|
}, [load]);
|
||||||
|
|
||||||
|
const submitRating = useCallback(async (rating: SpacedRepetitionRating) => {
|
||||||
|
if (!data?.currentCard || saving) return;
|
||||||
|
setSaving(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/spaced-repetition-sets/${setId}/reviews`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
flashcardId: data.currentCard.id,
|
||||||
|
rating,
|
||||||
|
expectedStateVersion: data.currentCard.stateVersion,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (response.status === 409) {
|
||||||
|
await load();
|
||||||
|
setError("That review was already saved. The queue has been refreshed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!response.ok) {
|
||||||
|
const body = await response.json().catch(() => null);
|
||||||
|
throw new Error(body?.error ?? "Unable to save review");
|
||||||
|
}
|
||||||
|
setData(await response.json());
|
||||||
|
window.dispatchEvent(new Event("spaced-repetition-updated"));
|
||||||
|
setFlipped(false);
|
||||||
|
} catch (reviewError) {
|
||||||
|
setError(reviewError instanceof Error ? reviewError.message : "Unable to save review");
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
}, [data, load, saving, setId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function onKeyDown(event: KeyboardEvent) {
|
||||||
|
if (!data?.currentCard || saving) return;
|
||||||
|
if (event.key === " " || event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
setFlipped((current) => !current);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!flipped) return;
|
||||||
|
const keyboardRatings: Record<string, SpacedRepetitionRating> = {
|
||||||
|
"1": "AGAIN", "2": "HARD", "3": "GOOD", "4": "EASY",
|
||||||
|
};
|
||||||
|
const rating = keyboardRatings[event.key];
|
||||||
|
if (rating) submitRating(rating);
|
||||||
|
}
|
||||||
|
window.addEventListener("keydown", onKeyDown);
|
||||||
|
return () => window.removeEventListener("keydown", onKeyDown);
|
||||||
|
}, [data, flipped, saving, submitRating]);
|
||||||
|
|
||||||
|
if (loading || !data) {
|
||||||
|
return <div className="mx-auto max-w-5xl animate-subtle-pulse py-16 text-center text-text-muted">Loading review queue…</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const card = data.currentCard;
|
||||||
|
return (
|
||||||
|
<div className="mx-auto w-full max-w-5xl py-1 pb-16 md:py-3">
|
||||||
|
<header className="mb-6 rounded-2xl border border-border-light bg-bg-surface/75 p-4 shadow-[var(--shadow-card)] sm:p-5">
|
||||||
|
<button onClick={() => router.push(`/${classSlug}/spaced-repetition`)} className="mb-2 inline-flex min-h-10 items-center gap-2 text-sm text-text-muted hover:text-text-heading"><span aria-hidden>←</span> Back to repetition sets</button>
|
||||||
|
<div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
|
||||||
|
<div><p className="text-xs font-bold uppercase tracking-[0.16em] text-primary">Spaced repetition</p><h1 className="editorial-title mt-1 text-3xl text-text-heading">{data.set.name}</h1>{data.set.description && <p className="mt-1 text-sm text-text-secondary">{data.set.description}</p>}</div>
|
||||||
|
<div className="flex flex-wrap gap-2 text-xs">
|
||||||
|
<span className="rounded-full bg-error-bg px-3 py-1.5 text-error">{data.set.stats.dueCards} due</span>
|
||||||
|
{data.set.stats.learningAheadCards > 0 && <span className="rounded-full bg-bg-callout px-3 py-1.5 text-primary">{data.set.stats.learningAheadCards} learning</span>}
|
||||||
|
<span className="rounded-full bg-success-bg px-3 py-1.5 text-success">{data.set.stats.newCardsAvailable} new</span>
|
||||||
|
<span className="rounded-full bg-bg-surface-alt px-3 py-1.5 text-text-muted">{data.set.stats.reviewedCards}/{data.set.stats.totalCards} introduced</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{error && <div role="alert" className="mb-5 flex items-center justify-between rounded-xl border border-error/30 bg-error-bg px-4 py-3 text-sm text-error"><span>{error}</span><button onClick={() => setError(null)} aria-label="Dismiss error">×</button></div>}
|
||||||
|
|
||||||
|
{!card ? (
|
||||||
|
<div className="rounded-3xl border border-success/25 bg-bg-surface p-8 text-center shadow-[var(--shadow-card)] sm:p-12">
|
||||||
|
<div className="mx-auto grid h-16 w-16 place-items-center rounded-full bg-success-bg text-3xl text-success">✓</div>
|
||||||
|
<h2 className="editorial-title mt-5 text-3xl text-text-heading">You’re caught up</h2>
|
||||||
|
<p className="mt-2 text-text-secondary">There are no due cards and today’s new-card allowance is complete.</p>
|
||||||
|
<p className="mt-5 text-sm font-semibold text-text-muted">Next review: {dueLabel(data.set.stats.nextDue)}</p>
|
||||||
|
<button onClick={() => router.push(`/${classSlug}/spaced-repetition`)} className="mt-7 min-h-11 rounded-xl bg-primary px-6 font-bold text-white hover:bg-primary-hover">Back to sets</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex flex-col items-center">
|
||||||
|
<div className="mb-4 flex w-full items-center justify-between text-sm text-text-muted"><span>From {card.sourceDeckName}</span><span>{data.set.stats.availableCards} available</span></div>
|
||||||
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-label={flipped ? "Showing card answer. Press Space to show front." : "Showing card front. Press Space to reveal answer."}
|
||||||
|
onClick={() => !saving && setFlipped((current) => !current)}
|
||||||
|
className="perspective-1000 relative w-full cursor-pointer select-none"
|
||||||
|
>
|
||||||
|
<div className={`preserve-3d relative min-h-[420px] w-full transition-transform duration-300 md:min-h-[560px] ${flipped ? "rotate-x-180" : ""}`}>
|
||||||
|
<div className="paper-grid backface-hidden absolute inset-0 flex flex-col items-center justify-center rounded-[2rem] border border-border-light bg-bg-surface p-7 shadow-[var(--shadow-card-hover)] md:p-12">
|
||||||
|
<span className="absolute right-5 top-4 text-xs font-bold uppercase tracking-wider text-text-muted">Front</span>
|
||||||
|
<div className="markdown-content w-full text-center font-serif text-2xl font-semibold leading-relaxed text-text-heading md:text-4xl"><ReactMarkdown remarkPlugins={[remarkGfm]}>{card.front}</ReactMarkdown></div>
|
||||||
|
<p className="mt-7 text-xs text-text-muted">Tap or press Space to reveal the answer</p>
|
||||||
|
</div>
|
||||||
|
<div className="paper-grid backface-hidden absolute inset-0 flex rotate-x-180 flex-col items-center justify-center rounded-[2rem] border border-primary/25 bg-bg-surface p-7 shadow-[var(--shadow-card-hover)] md:p-12">
|
||||||
|
<span className="absolute right-5 top-4 text-xs font-bold uppercase tracking-wider text-text-muted">Back</span>
|
||||||
|
<div className="markdown-content max-h-[390px] w-full overflow-y-auto text-center text-lg leading-relaxed text-text-body md:max-h-[450px] md:text-xl"><ReactMarkdown remarkPlugins={[remarkGfm]}>{card.back}</ReactMarkdown></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`mt-6 grid w-full grid-cols-2 gap-2 transition-opacity sm:grid-cols-4 ${flipped ? "opacity-100" : "pointer-events-none opacity-0"}`} aria-hidden={!flipped}>
|
||||||
|
{card.previews.map((preview, index) => (
|
||||||
|
<button key={preview.rating} disabled={saving || !flipped} onClick={() => submitRating(preview.rating)} className={`min-h-16 rounded-xl border-2 bg-bg-surface px-3 py-2 text-sm font-extrabold transition-colors disabled:opacity-50 ${ratingStyles[preview.rating]}`}>
|
||||||
|
<span className="block">{preview.rating[0]}{preview.rating.slice(1).toLowerCase()}</span>
|
||||||
|
<span className="mt-0.5 block text-xs font-semibold opacity-75">{index + 1} · {intervalLabel(preview.due, preview.scheduledDays)}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<p className="mt-4 text-center text-xs text-text-muted">{flipped ? "Choose 1–4 based on how well you remembered" : "Space to flip"}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useRouter, usePathname } from "next/navigation";
|
import { useRouter, usePathname } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ThemeToggle } from "./ThemeToggle";
|
import { ThemeToggle } from "./ThemeToggle";
|
||||||
|
|
@ -19,6 +19,37 @@ export function Navbar() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const parts = pathname.split("/").filter(Boolean);
|
const parts = pathname.split("/").filter(Boolean);
|
||||||
const classSlug = parts[0] && parts[0] !== "shared" ? parts[0] : null;
|
const classSlug = parts[0] && parts[0] !== "shared" ? parts[0] : null;
|
||||||
|
const [hasReadyCards, setHasReadyCards] = useState(false);
|
||||||
|
|
||||||
|
const refreshDueCards = useCallback(async () => {
|
||||||
|
if (!classSlug) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/classes");
|
||||||
|
if (!response.ok) return;
|
||||||
|
const classes = await response.json() as Array<{
|
||||||
|
slug: string;
|
||||||
|
spacedRepetitionReadyCards: number;
|
||||||
|
}>;
|
||||||
|
const currentClass = classes.find((classItem) => classItem.slug === classSlug);
|
||||||
|
setHasReadyCards((currentClass?.spacedRepetitionReadyCards ?? 0) > 0);
|
||||||
|
} catch {
|
||||||
|
// Keep the last known state when a background refresh fails.
|
||||||
|
}
|
||||||
|
}, [classSlug]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initialRefresh = window.setTimeout(() => void refreshDueCards(), 0);
|
||||||
|
const interval = window.setInterval(refreshDueCards, 60_000);
|
||||||
|
window.addEventListener("focus", refreshDueCards);
|
||||||
|
window.addEventListener("spaced-repetition-updated", refreshDueCards);
|
||||||
|
return () => {
|
||||||
|
window.clearTimeout(initialRefresh);
|
||||||
|
window.clearInterval(interval);
|
||||||
|
window.removeEventListener("focus", refreshDueCards);
|
||||||
|
window.removeEventListener("spaced-repetition-updated", refreshDueCards);
|
||||||
|
};
|
||||||
|
}, [refreshDueCards]);
|
||||||
|
|
||||||
async function handleLogout() {
|
async function handleLogout() {
|
||||||
await fetch("/api/auth/logout", { method: "POST" });
|
await fetch("/api/auth/logout", { method: "POST" });
|
||||||
|
|
@ -61,6 +92,22 @@ export function Navbar() {
|
||||||
>
|
>
|
||||||
<span aria-hidden>✓</span> Quizzes
|
<span aria-hidden>✓</span> Quizzes
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
href={`/${classSlug}/spaced-repetition`}
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
className={`mt-1 flex min-h-11 items-center gap-3 rounded-xl px-3 text-sm font-semibold transition-colors ${pathname.includes("/spaced-repetition") ? "bg-bg-callout text-primary" : "text-text-secondary hover:bg-bg-surface-alt hover:text-text-heading"}`}
|
||||||
|
>
|
||||||
|
<span aria-hidden>↻</span> Spaced Repetition
|
||||||
|
{classSlug && hasReadyCards && (
|
||||||
|
<span
|
||||||
|
className="ml-auto grid h-5 w-5 place-items-center rounded-full bg-error-bg text-xs font-extrabold text-error"
|
||||||
|
title="Spaced repetition cards are ready to study"
|
||||||
|
>
|
||||||
|
<span aria-hidden>!</span>
|
||||||
|
<span className="sr-only">Cards ready to study</span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
export const STUDY_MODES = [
|
export const STUDY_MODES = [
|
||||||
{ key: "flashcards", label: "Flashcards", path: "flashcards" },
|
{ key: "flashcards", label: "Flashcards", path: "flashcards" },
|
||||||
{ key: "quizzes", label: "Quizzes", path: "quizzes" },
|
{ key: "quizzes", label: "Quizzes", path: "quizzes" },
|
||||||
|
{ key: "spaced-repetition", label: "Spaced Repetition", path: "spaced-repetition" },
|
||||||
// Future modes get added here — the layout, tab bar, and auth middleware need no changes.
|
// Future modes get added here — the layout, tab bar, and auth middleware need no changes.
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,3 +82,18 @@ export type StudyActivity = Prisma.StudyActivityModel
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type MaterialGroup = Prisma.MaterialGroupModel
|
export type MaterialGroup = Prisma.MaterialGroupModel
|
||||||
|
/**
|
||||||
|
* Model SpacedRepetitionSet
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type SpacedRepetitionSet = Prisma.SpacedRepetitionSetModel
|
||||||
|
/**
|
||||||
|
* Model SpacedRepetitionSetDeck
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type SpacedRepetitionSetDeck = Prisma.SpacedRepetitionSetDeckModel
|
||||||
|
/**
|
||||||
|
* Model SpacedRepetitionCardState
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type SpacedRepetitionCardState = Prisma.SpacedRepetitionCardStateModel
|
||||||
|
|
|
||||||
|
|
@ -106,3 +106,18 @@ export type StudyActivity = Prisma.StudyActivityModel
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export type MaterialGroup = Prisma.MaterialGroupModel
|
export type MaterialGroup = Prisma.MaterialGroupModel
|
||||||
|
/**
|
||||||
|
* Model SpacedRepetitionSet
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type SpacedRepetitionSet = Prisma.SpacedRepetitionSetModel
|
||||||
|
/**
|
||||||
|
* Model SpacedRepetitionSetDeck
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type SpacedRepetitionSetDeck = Prisma.SpacedRepetitionSetDeckModel
|
||||||
|
/**
|
||||||
|
* Model SpacedRepetitionCardState
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type SpacedRepetitionCardState = Prisma.SpacedRepetitionCardStateModel
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -396,7 +396,10 @@ export const ModelName = {
|
||||||
AuthSecurity: 'AuthSecurity',
|
AuthSecurity: 'AuthSecurity',
|
||||||
Setting: 'Setting',
|
Setting: 'Setting',
|
||||||
StudyActivity: 'StudyActivity',
|
StudyActivity: 'StudyActivity',
|
||||||
MaterialGroup: 'MaterialGroup'
|
MaterialGroup: 'MaterialGroup',
|
||||||
|
SpacedRepetitionSet: 'SpacedRepetitionSet',
|
||||||
|
SpacedRepetitionSetDeck: 'SpacedRepetitionSetDeck',
|
||||||
|
SpacedRepetitionCardState: 'SpacedRepetitionCardState'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||||
|
|
@ -412,7 +415,7 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||||
omit: GlobalOmitOptions
|
omit: GlobalOmitOptions
|
||||||
}
|
}
|
||||||
meta: {
|
meta: {
|
||||||
modelProps: "class" | "deck" | "flashcard" | "quizSet" | "question" | "answerOption" | "studyProgress" | "quizAttempt" | "shareLink" | "authSecurity" | "setting" | "studyActivity" | "materialGroup"
|
modelProps: "class" | "deck" | "flashcard" | "quizSet" | "question" | "answerOption" | "studyProgress" | "quizAttempt" | "shareLink" | "authSecurity" | "setting" | "studyActivity" | "materialGroup" | "spacedRepetitionSet" | "spacedRepetitionSetDeck" | "spacedRepetitionCardState"
|
||||||
txIsolationLevel: TransactionIsolationLevel
|
txIsolationLevel: TransactionIsolationLevel
|
||||||
}
|
}
|
||||||
model: {
|
model: {
|
||||||
|
|
@ -1378,6 +1381,228 @@ export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SpacedRepetitionSet: {
|
||||||
|
payload: Prisma.$SpacedRepetitionSetPayload<ExtArgs>
|
||||||
|
fields: Prisma.SpacedRepetitionSetFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.SpacedRepetitionSetFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.SpacedRepetitionSetFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.SpacedRepetitionSetFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.SpacedRepetitionSetFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.SpacedRepetitionSetFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.SpacedRepetitionSetCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.SpacedRepetitionSetCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.SpacedRepetitionSetCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.SpacedRepetitionSetUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.SpacedRepetitionSetUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.SpacedRepetitionSetUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.SpacedRepetitionSetUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetPayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.SpacedRepetitionSetAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateSpacedRepetitionSet>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.SpacedRepetitionSetGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.SpacedRepetitionSetGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.SpacedRepetitionSetCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.SpacedRepetitionSetCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SpacedRepetitionSetDeck: {
|
||||||
|
payload: Prisma.$SpacedRepetitionSetDeckPayload<ExtArgs>
|
||||||
|
fields: Prisma.SpacedRepetitionSetDeckFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionSetDeckPayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateSpacedRepetitionSetDeck>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.SpacedRepetitionSetDeckGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.SpacedRepetitionSetDeckCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.SpacedRepetitionSetDeckCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SpacedRepetitionCardState: {
|
||||||
|
payload: Prisma.$SpacedRepetitionCardStatePayload<ExtArgs>
|
||||||
|
fields: Prisma.SpacedRepetitionCardStateFieldRefs
|
||||||
|
operations: {
|
||||||
|
findUnique: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateFindUniqueArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload> | null
|
||||||
|
}
|
||||||
|
findUniqueOrThrow: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateFindUniqueOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload>
|
||||||
|
}
|
||||||
|
findFirst: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateFindFirstArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload> | null
|
||||||
|
}
|
||||||
|
findFirstOrThrow: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateFindFirstOrThrowArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload>
|
||||||
|
}
|
||||||
|
findMany: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateFindManyArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload>[]
|
||||||
|
}
|
||||||
|
create: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateCreateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload>
|
||||||
|
}
|
||||||
|
createMany: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateCreateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
createManyAndReturn: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateCreateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload>[]
|
||||||
|
}
|
||||||
|
delete: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateDeleteArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload>
|
||||||
|
}
|
||||||
|
update: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateUpdateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload>
|
||||||
|
}
|
||||||
|
deleteMany: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateDeleteManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateMany: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateUpdateManyArgs<ExtArgs>
|
||||||
|
result: BatchPayload
|
||||||
|
}
|
||||||
|
updateManyAndReturn: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateUpdateManyAndReturnArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload>[]
|
||||||
|
}
|
||||||
|
upsert: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateUpsertArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.PayloadToResult<Prisma.$SpacedRepetitionCardStatePayload>
|
||||||
|
}
|
||||||
|
aggregate: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateAggregateArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.AggregateSpacedRepetitionCardState>
|
||||||
|
}
|
||||||
|
groupBy: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateGroupByArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.SpacedRepetitionCardStateGroupByOutputType>[]
|
||||||
|
}
|
||||||
|
count: {
|
||||||
|
args: Prisma.SpacedRepetitionCardStateCountArgs<ExtArgs>
|
||||||
|
result: runtime.Types.Utils.Optional<Prisma.SpacedRepetitionCardStateCountAggregateOutputType> | number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} & {
|
} & {
|
||||||
other: {
|
other: {
|
||||||
|
|
@ -1566,6 +1791,52 @@ export const MaterialGroupScalarFieldEnum = {
|
||||||
export type MaterialGroupScalarFieldEnum = (typeof MaterialGroupScalarFieldEnum)[keyof typeof MaterialGroupScalarFieldEnum]
|
export type MaterialGroupScalarFieldEnum = (typeof MaterialGroupScalarFieldEnum)[keyof typeof MaterialGroupScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SpacedRepetitionSetScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
classId: 'classId',
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
newCardsPerDay: 'newCardsPerDay',
|
||||||
|
sortOrder: 'sortOrder',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SpacedRepetitionSetScalarFieldEnum = (typeof SpacedRepetitionSetScalarFieldEnum)[keyof typeof SpacedRepetitionSetScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SpacedRepetitionSetDeckScalarFieldEnum = {
|
||||||
|
setId: 'setId',
|
||||||
|
deckId: 'deckId',
|
||||||
|
sortOrder: 'sortOrder',
|
||||||
|
addedAt: 'addedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SpacedRepetitionSetDeckScalarFieldEnum = (typeof SpacedRepetitionSetDeckScalarFieldEnum)[keyof typeof SpacedRepetitionSetDeckScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SpacedRepetitionCardStateScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
setId: 'setId',
|
||||||
|
flashcardId: 'flashcardId',
|
||||||
|
due: 'due',
|
||||||
|
stability: 'stability',
|
||||||
|
difficulty: 'difficulty',
|
||||||
|
elapsedDays: 'elapsedDays',
|
||||||
|
scheduledDays: 'scheduledDays',
|
||||||
|
learningSteps: 'learningSteps',
|
||||||
|
reps: 'reps',
|
||||||
|
lapses: 'lapses',
|
||||||
|
state: 'state',
|
||||||
|
firstReviewedAt: 'firstReviewedAt',
|
||||||
|
lastReview: 'lastReview',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SpacedRepetitionCardStateScalarFieldEnum = (typeof SpacedRepetitionCardStateScalarFieldEnum)[keyof typeof SpacedRepetitionCardStateScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const SortOrder = {
|
export const SortOrder = {
|
||||||
asc: 'asc',
|
asc: 'asc',
|
||||||
desc: 'desc'
|
desc: 'desc'
|
||||||
|
|
@ -1745,6 +2016,9 @@ export type GlobalOmitConfig = {
|
||||||
setting?: Prisma.SettingOmit
|
setting?: Prisma.SettingOmit
|
||||||
studyActivity?: Prisma.StudyActivityOmit
|
studyActivity?: Prisma.StudyActivityOmit
|
||||||
materialGroup?: Prisma.MaterialGroupOmit
|
materialGroup?: Prisma.MaterialGroupOmit
|
||||||
|
spacedRepetitionSet?: Prisma.SpacedRepetitionSetOmit
|
||||||
|
spacedRepetitionSetDeck?: Prisma.SpacedRepetitionSetDeckOmit
|
||||||
|
spacedRepetitionCardState?: Prisma.SpacedRepetitionCardStateOmit
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Types for Logging */
|
/* Types for Logging */
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,10 @@ export const ModelName = {
|
||||||
AuthSecurity: 'AuthSecurity',
|
AuthSecurity: 'AuthSecurity',
|
||||||
Setting: 'Setting',
|
Setting: 'Setting',
|
||||||
StudyActivity: 'StudyActivity',
|
StudyActivity: 'StudyActivity',
|
||||||
MaterialGroup: 'MaterialGroup'
|
MaterialGroup: 'MaterialGroup',
|
||||||
|
SpacedRepetitionSet: 'SpacedRepetitionSet',
|
||||||
|
SpacedRepetitionSetDeck: 'SpacedRepetitionSetDeck',
|
||||||
|
SpacedRepetitionCardState: 'SpacedRepetitionCardState'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||||
|
|
@ -231,6 +234,52 @@ export const MaterialGroupScalarFieldEnum = {
|
||||||
export type MaterialGroupScalarFieldEnum = (typeof MaterialGroupScalarFieldEnum)[keyof typeof MaterialGroupScalarFieldEnum]
|
export type MaterialGroupScalarFieldEnum = (typeof MaterialGroupScalarFieldEnum)[keyof typeof MaterialGroupScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SpacedRepetitionSetScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
classId: 'classId',
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
newCardsPerDay: 'newCardsPerDay',
|
||||||
|
sortOrder: 'sortOrder',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SpacedRepetitionSetScalarFieldEnum = (typeof SpacedRepetitionSetScalarFieldEnum)[keyof typeof SpacedRepetitionSetScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SpacedRepetitionSetDeckScalarFieldEnum = {
|
||||||
|
setId: 'setId',
|
||||||
|
deckId: 'deckId',
|
||||||
|
sortOrder: 'sortOrder',
|
||||||
|
addedAt: 'addedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SpacedRepetitionSetDeckScalarFieldEnum = (typeof SpacedRepetitionSetDeckScalarFieldEnum)[keyof typeof SpacedRepetitionSetDeckScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SpacedRepetitionCardStateScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
setId: 'setId',
|
||||||
|
flashcardId: 'flashcardId',
|
||||||
|
due: 'due',
|
||||||
|
stability: 'stability',
|
||||||
|
difficulty: 'difficulty',
|
||||||
|
elapsedDays: 'elapsedDays',
|
||||||
|
scheduledDays: 'scheduledDays',
|
||||||
|
learningSteps: 'learningSteps',
|
||||||
|
reps: 'reps',
|
||||||
|
lapses: 'lapses',
|
||||||
|
state: 'state',
|
||||||
|
firstReviewedAt: 'firstReviewedAt',
|
||||||
|
lastReview: 'lastReview',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SpacedRepetitionCardStateScalarFieldEnum = (typeof SpacedRepetitionCardStateScalarFieldEnum)[keyof typeof SpacedRepetitionCardStateScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
export const SortOrder = {
|
export const SortOrder = {
|
||||||
asc: 'asc',
|
asc: 'asc',
|
||||||
desc: 'desc'
|
desc: 'desc'
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,7 @@ export type * from './models/AuthSecurity'
|
||||||
export type * from './models/Setting'
|
export type * from './models/Setting'
|
||||||
export type * from './models/StudyActivity'
|
export type * from './models/StudyActivity'
|
||||||
export type * from './models/MaterialGroup'
|
export type * from './models/MaterialGroup'
|
||||||
|
export type * from './models/SpacedRepetitionSet'
|
||||||
|
export type * from './models/SpacedRepetitionSetDeck'
|
||||||
|
export type * from './models/SpacedRepetitionCardState'
|
||||||
export type * from './commonInputTypes'
|
export type * from './commonInputTypes'
|
||||||
|
|
@ -219,6 +219,7 @@ export type ClassWhereInput = {
|
||||||
decks?: Prisma.DeckListRelationFilter
|
decks?: Prisma.DeckListRelationFilter
|
||||||
quizSets?: Prisma.QuizSetListRelationFilter
|
quizSets?: Prisma.QuizSetListRelationFilter
|
||||||
materialGroups?: Prisma.MaterialGroupListRelationFilter
|
materialGroups?: Prisma.MaterialGroupListRelationFilter
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetListRelationFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassOrderByWithRelationInput = {
|
export type ClassOrderByWithRelationInput = {
|
||||||
|
|
@ -230,6 +231,7 @@ export type ClassOrderByWithRelationInput = {
|
||||||
decks?: Prisma.DeckOrderByRelationAggregateInput
|
decks?: Prisma.DeckOrderByRelationAggregateInput
|
||||||
quizSets?: Prisma.QuizSetOrderByRelationAggregateInput
|
quizSets?: Prisma.QuizSetOrderByRelationAggregateInput
|
||||||
materialGroups?: Prisma.MaterialGroupOrderByRelationAggregateInput
|
materialGroups?: Prisma.MaterialGroupOrderByRelationAggregateInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetOrderByRelationAggregateInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassWhereUniqueInput = Prisma.AtLeast<{
|
export type ClassWhereUniqueInput = Prisma.AtLeast<{
|
||||||
|
|
@ -244,6 +246,7 @@ export type ClassWhereUniqueInput = Prisma.AtLeast<{
|
||||||
decks?: Prisma.DeckListRelationFilter
|
decks?: Prisma.DeckListRelationFilter
|
||||||
quizSets?: Prisma.QuizSetListRelationFilter
|
quizSets?: Prisma.QuizSetListRelationFilter
|
||||||
materialGroups?: Prisma.MaterialGroupListRelationFilter
|
materialGroups?: Prisma.MaterialGroupListRelationFilter
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetListRelationFilter
|
||||||
}, "id" | "slug">
|
}, "id" | "slug">
|
||||||
|
|
||||||
export type ClassOrderByWithAggregationInput = {
|
export type ClassOrderByWithAggregationInput = {
|
||||||
|
|
@ -279,6 +282,7 @@ export type ClassCreateInput = {
|
||||||
decks?: Prisma.DeckCreateNestedManyWithoutClassInput
|
decks?: Prisma.DeckCreateNestedManyWithoutClassInput
|
||||||
quizSets?: Prisma.QuizSetCreateNestedManyWithoutClassInput
|
quizSets?: Prisma.QuizSetCreateNestedManyWithoutClassInput
|
||||||
materialGroups?: Prisma.MaterialGroupCreateNestedManyWithoutClassInput
|
materialGroups?: Prisma.MaterialGroupCreateNestedManyWithoutClassInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetCreateNestedManyWithoutClassInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassUncheckedCreateInput = {
|
export type ClassUncheckedCreateInput = {
|
||||||
|
|
@ -290,6 +294,7 @@ export type ClassUncheckedCreateInput = {
|
||||||
decks?: Prisma.DeckUncheckedCreateNestedManyWithoutClassInput
|
decks?: Prisma.DeckUncheckedCreateNestedManyWithoutClassInput
|
||||||
quizSets?: Prisma.QuizSetUncheckedCreateNestedManyWithoutClassInput
|
quizSets?: Prisma.QuizSetUncheckedCreateNestedManyWithoutClassInput
|
||||||
materialGroups?: Prisma.MaterialGroupUncheckedCreateNestedManyWithoutClassInput
|
materialGroups?: Prisma.MaterialGroupUncheckedCreateNestedManyWithoutClassInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUncheckedCreateNestedManyWithoutClassInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassUpdateInput = {
|
export type ClassUpdateInput = {
|
||||||
|
|
@ -301,6 +306,7 @@ export type ClassUpdateInput = {
|
||||||
decks?: Prisma.DeckUpdateManyWithoutClassNestedInput
|
decks?: Prisma.DeckUpdateManyWithoutClassNestedInput
|
||||||
quizSets?: Prisma.QuizSetUpdateManyWithoutClassNestedInput
|
quizSets?: Prisma.QuizSetUpdateManyWithoutClassNestedInput
|
||||||
materialGroups?: Prisma.MaterialGroupUpdateManyWithoutClassNestedInput
|
materialGroups?: Prisma.MaterialGroupUpdateManyWithoutClassNestedInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUpdateManyWithoutClassNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassUncheckedUpdateInput = {
|
export type ClassUncheckedUpdateInput = {
|
||||||
|
|
@ -312,6 +318,7 @@ export type ClassUncheckedUpdateInput = {
|
||||||
decks?: Prisma.DeckUncheckedUpdateManyWithoutClassNestedInput
|
decks?: Prisma.DeckUncheckedUpdateManyWithoutClassNestedInput
|
||||||
quizSets?: Prisma.QuizSetUncheckedUpdateManyWithoutClassNestedInput
|
quizSets?: Prisma.QuizSetUncheckedUpdateManyWithoutClassNestedInput
|
||||||
materialGroups?: Prisma.MaterialGroupUncheckedUpdateManyWithoutClassNestedInput
|
materialGroups?: Prisma.MaterialGroupUncheckedUpdateManyWithoutClassNestedInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUncheckedUpdateManyWithoutClassNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassCreateManyInput = {
|
export type ClassCreateManyInput = {
|
||||||
|
|
@ -433,6 +440,20 @@ export type ClassUpdateOneRequiredWithoutMaterialGroupsNestedInput = {
|
||||||
update?: Prisma.XOR<Prisma.XOR<Prisma.ClassUpdateToOneWithWhereWithoutMaterialGroupsInput, Prisma.ClassUpdateWithoutMaterialGroupsInput>, Prisma.ClassUncheckedUpdateWithoutMaterialGroupsInput>
|
update?: Prisma.XOR<Prisma.XOR<Prisma.ClassUpdateToOneWithWhereWithoutMaterialGroupsInput, Prisma.ClassUpdateWithoutMaterialGroupsInput>, Prisma.ClassUncheckedUpdateWithoutMaterialGroupsInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ClassCreateNestedOneWithoutSpacedRepetitionSetsInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.ClassCreateWithoutSpacedRepetitionSetsInput, Prisma.ClassUncheckedCreateWithoutSpacedRepetitionSetsInput>
|
||||||
|
connectOrCreate?: Prisma.ClassCreateOrConnectWithoutSpacedRepetitionSetsInput
|
||||||
|
connect?: Prisma.ClassWhereUniqueInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClassUpdateOneRequiredWithoutSpacedRepetitionSetsNestedInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.ClassCreateWithoutSpacedRepetitionSetsInput, Prisma.ClassUncheckedCreateWithoutSpacedRepetitionSetsInput>
|
||||||
|
connectOrCreate?: Prisma.ClassCreateOrConnectWithoutSpacedRepetitionSetsInput
|
||||||
|
upsert?: Prisma.ClassUpsertWithoutSpacedRepetitionSetsInput
|
||||||
|
connect?: Prisma.ClassWhereUniqueInput
|
||||||
|
update?: Prisma.XOR<Prisma.XOR<Prisma.ClassUpdateToOneWithWhereWithoutSpacedRepetitionSetsInput, Prisma.ClassUpdateWithoutSpacedRepetitionSetsInput>, Prisma.ClassUncheckedUpdateWithoutSpacedRepetitionSetsInput>
|
||||||
|
}
|
||||||
|
|
||||||
export type ClassCreateWithoutDecksInput = {
|
export type ClassCreateWithoutDecksInput = {
|
||||||
id?: string
|
id?: string
|
||||||
slug: string
|
slug: string
|
||||||
|
|
@ -441,6 +462,7 @@ export type ClassCreateWithoutDecksInput = {
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
quizSets?: Prisma.QuizSetCreateNestedManyWithoutClassInput
|
quizSets?: Prisma.QuizSetCreateNestedManyWithoutClassInput
|
||||||
materialGroups?: Prisma.MaterialGroupCreateNestedManyWithoutClassInput
|
materialGroups?: Prisma.MaterialGroupCreateNestedManyWithoutClassInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetCreateNestedManyWithoutClassInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassUncheckedCreateWithoutDecksInput = {
|
export type ClassUncheckedCreateWithoutDecksInput = {
|
||||||
|
|
@ -451,6 +473,7 @@ export type ClassUncheckedCreateWithoutDecksInput = {
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
quizSets?: Prisma.QuizSetUncheckedCreateNestedManyWithoutClassInput
|
quizSets?: Prisma.QuizSetUncheckedCreateNestedManyWithoutClassInput
|
||||||
materialGroups?: Prisma.MaterialGroupUncheckedCreateNestedManyWithoutClassInput
|
materialGroups?: Prisma.MaterialGroupUncheckedCreateNestedManyWithoutClassInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUncheckedCreateNestedManyWithoutClassInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassCreateOrConnectWithoutDecksInput = {
|
export type ClassCreateOrConnectWithoutDecksInput = {
|
||||||
|
|
@ -477,6 +500,7 @@ export type ClassUpdateWithoutDecksInput = {
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
quizSets?: Prisma.QuizSetUpdateManyWithoutClassNestedInput
|
quizSets?: Prisma.QuizSetUpdateManyWithoutClassNestedInput
|
||||||
materialGroups?: Prisma.MaterialGroupUpdateManyWithoutClassNestedInput
|
materialGroups?: Prisma.MaterialGroupUpdateManyWithoutClassNestedInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUpdateManyWithoutClassNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassUncheckedUpdateWithoutDecksInput = {
|
export type ClassUncheckedUpdateWithoutDecksInput = {
|
||||||
|
|
@ -487,6 +511,7 @@ export type ClassUncheckedUpdateWithoutDecksInput = {
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
quizSets?: Prisma.QuizSetUncheckedUpdateManyWithoutClassNestedInput
|
quizSets?: Prisma.QuizSetUncheckedUpdateManyWithoutClassNestedInput
|
||||||
materialGroups?: Prisma.MaterialGroupUncheckedUpdateManyWithoutClassNestedInput
|
materialGroups?: Prisma.MaterialGroupUncheckedUpdateManyWithoutClassNestedInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUncheckedUpdateManyWithoutClassNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassCreateWithoutQuizSetsInput = {
|
export type ClassCreateWithoutQuizSetsInput = {
|
||||||
|
|
@ -497,6 +522,7 @@ export type ClassCreateWithoutQuizSetsInput = {
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
decks?: Prisma.DeckCreateNestedManyWithoutClassInput
|
decks?: Prisma.DeckCreateNestedManyWithoutClassInput
|
||||||
materialGroups?: Prisma.MaterialGroupCreateNestedManyWithoutClassInput
|
materialGroups?: Prisma.MaterialGroupCreateNestedManyWithoutClassInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetCreateNestedManyWithoutClassInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassUncheckedCreateWithoutQuizSetsInput = {
|
export type ClassUncheckedCreateWithoutQuizSetsInput = {
|
||||||
|
|
@ -507,6 +533,7 @@ export type ClassUncheckedCreateWithoutQuizSetsInput = {
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
decks?: Prisma.DeckUncheckedCreateNestedManyWithoutClassInput
|
decks?: Prisma.DeckUncheckedCreateNestedManyWithoutClassInput
|
||||||
materialGroups?: Prisma.MaterialGroupUncheckedCreateNestedManyWithoutClassInput
|
materialGroups?: Prisma.MaterialGroupUncheckedCreateNestedManyWithoutClassInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUncheckedCreateNestedManyWithoutClassInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassCreateOrConnectWithoutQuizSetsInput = {
|
export type ClassCreateOrConnectWithoutQuizSetsInput = {
|
||||||
|
|
@ -533,6 +560,7 @@ export type ClassUpdateWithoutQuizSetsInput = {
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
decks?: Prisma.DeckUpdateManyWithoutClassNestedInput
|
decks?: Prisma.DeckUpdateManyWithoutClassNestedInput
|
||||||
materialGroups?: Prisma.MaterialGroupUpdateManyWithoutClassNestedInput
|
materialGroups?: Prisma.MaterialGroupUpdateManyWithoutClassNestedInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUpdateManyWithoutClassNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassUncheckedUpdateWithoutQuizSetsInput = {
|
export type ClassUncheckedUpdateWithoutQuizSetsInput = {
|
||||||
|
|
@ -543,6 +571,7 @@ export type ClassUncheckedUpdateWithoutQuizSetsInput = {
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
decks?: Prisma.DeckUncheckedUpdateManyWithoutClassNestedInput
|
decks?: Prisma.DeckUncheckedUpdateManyWithoutClassNestedInput
|
||||||
materialGroups?: Prisma.MaterialGroupUncheckedUpdateManyWithoutClassNestedInput
|
materialGroups?: Prisma.MaterialGroupUncheckedUpdateManyWithoutClassNestedInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUncheckedUpdateManyWithoutClassNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassCreateWithoutMaterialGroupsInput = {
|
export type ClassCreateWithoutMaterialGroupsInput = {
|
||||||
|
|
@ -553,6 +582,7 @@ export type ClassCreateWithoutMaterialGroupsInput = {
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
decks?: Prisma.DeckCreateNestedManyWithoutClassInput
|
decks?: Prisma.DeckCreateNestedManyWithoutClassInput
|
||||||
quizSets?: Prisma.QuizSetCreateNestedManyWithoutClassInput
|
quizSets?: Prisma.QuizSetCreateNestedManyWithoutClassInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetCreateNestedManyWithoutClassInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassUncheckedCreateWithoutMaterialGroupsInput = {
|
export type ClassUncheckedCreateWithoutMaterialGroupsInput = {
|
||||||
|
|
@ -563,6 +593,7 @@ export type ClassUncheckedCreateWithoutMaterialGroupsInput = {
|
||||||
createdAt?: Date | string
|
createdAt?: Date | string
|
||||||
decks?: Prisma.DeckUncheckedCreateNestedManyWithoutClassInput
|
decks?: Prisma.DeckUncheckedCreateNestedManyWithoutClassInput
|
||||||
quizSets?: Prisma.QuizSetUncheckedCreateNestedManyWithoutClassInput
|
quizSets?: Prisma.QuizSetUncheckedCreateNestedManyWithoutClassInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUncheckedCreateNestedManyWithoutClassInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassCreateOrConnectWithoutMaterialGroupsInput = {
|
export type ClassCreateOrConnectWithoutMaterialGroupsInput = {
|
||||||
|
|
@ -589,6 +620,7 @@ export type ClassUpdateWithoutMaterialGroupsInput = {
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
decks?: Prisma.DeckUpdateManyWithoutClassNestedInput
|
decks?: Prisma.DeckUpdateManyWithoutClassNestedInput
|
||||||
quizSets?: Prisma.QuizSetUpdateManyWithoutClassNestedInput
|
quizSets?: Prisma.QuizSetUpdateManyWithoutClassNestedInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUpdateManyWithoutClassNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassUncheckedUpdateWithoutMaterialGroupsInput = {
|
export type ClassUncheckedUpdateWithoutMaterialGroupsInput = {
|
||||||
|
|
@ -599,6 +631,67 @@ export type ClassUncheckedUpdateWithoutMaterialGroupsInput = {
|
||||||
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
decks?: Prisma.DeckUncheckedUpdateManyWithoutClassNestedInput
|
decks?: Prisma.DeckUncheckedUpdateManyWithoutClassNestedInput
|
||||||
quizSets?: Prisma.QuizSetUncheckedUpdateManyWithoutClassNestedInput
|
quizSets?: Prisma.QuizSetUncheckedUpdateManyWithoutClassNestedInput
|
||||||
|
spacedRepetitionSets?: Prisma.SpacedRepetitionSetUncheckedUpdateManyWithoutClassNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClassCreateWithoutSpacedRepetitionSetsInput = {
|
||||||
|
id?: string
|
||||||
|
slug: string
|
||||||
|
name: string
|
||||||
|
sortOrder?: number
|
||||||
|
createdAt?: Date | string
|
||||||
|
decks?: Prisma.DeckCreateNestedManyWithoutClassInput
|
||||||
|
quizSets?: Prisma.QuizSetCreateNestedManyWithoutClassInput
|
||||||
|
materialGroups?: Prisma.MaterialGroupCreateNestedManyWithoutClassInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClassUncheckedCreateWithoutSpacedRepetitionSetsInput = {
|
||||||
|
id?: string
|
||||||
|
slug: string
|
||||||
|
name: string
|
||||||
|
sortOrder?: number
|
||||||
|
createdAt?: Date | string
|
||||||
|
decks?: Prisma.DeckUncheckedCreateNestedManyWithoutClassInput
|
||||||
|
quizSets?: Prisma.QuizSetUncheckedCreateNestedManyWithoutClassInput
|
||||||
|
materialGroups?: Prisma.MaterialGroupUncheckedCreateNestedManyWithoutClassInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClassCreateOrConnectWithoutSpacedRepetitionSetsInput = {
|
||||||
|
where: Prisma.ClassWhereUniqueInput
|
||||||
|
create: Prisma.XOR<Prisma.ClassCreateWithoutSpacedRepetitionSetsInput, Prisma.ClassUncheckedCreateWithoutSpacedRepetitionSetsInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClassUpsertWithoutSpacedRepetitionSetsInput = {
|
||||||
|
update: Prisma.XOR<Prisma.ClassUpdateWithoutSpacedRepetitionSetsInput, Prisma.ClassUncheckedUpdateWithoutSpacedRepetitionSetsInput>
|
||||||
|
create: Prisma.XOR<Prisma.ClassCreateWithoutSpacedRepetitionSetsInput, Prisma.ClassUncheckedCreateWithoutSpacedRepetitionSetsInput>
|
||||||
|
where?: Prisma.ClassWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClassUpdateToOneWithWhereWithoutSpacedRepetitionSetsInput = {
|
||||||
|
where?: Prisma.ClassWhereInput
|
||||||
|
data: Prisma.XOR<Prisma.ClassUpdateWithoutSpacedRepetitionSetsInput, Prisma.ClassUncheckedUpdateWithoutSpacedRepetitionSetsInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClassUpdateWithoutSpacedRepetitionSetsInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
slug?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
decks?: Prisma.DeckUpdateManyWithoutClassNestedInput
|
||||||
|
quizSets?: Prisma.QuizSetUpdateManyWithoutClassNestedInput
|
||||||
|
materialGroups?: Prisma.MaterialGroupUpdateManyWithoutClassNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ClassUncheckedUpdateWithoutSpacedRepetitionSetsInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
slug?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
decks?: Prisma.DeckUncheckedUpdateManyWithoutClassNestedInput
|
||||||
|
quizSets?: Prisma.QuizSetUncheckedUpdateManyWithoutClassNestedInput
|
||||||
|
materialGroups?: Prisma.MaterialGroupUncheckedUpdateManyWithoutClassNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -610,12 +703,14 @@ export type ClassCountOutputType = {
|
||||||
decks: number
|
decks: number
|
||||||
quizSets: number
|
quizSets: number
|
||||||
materialGroups: number
|
materialGroups: number
|
||||||
|
spacedRepetitionSets: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ClassCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type ClassCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
decks?: boolean | ClassCountOutputTypeCountDecksArgs
|
decks?: boolean | ClassCountOutputTypeCountDecksArgs
|
||||||
quizSets?: boolean | ClassCountOutputTypeCountQuizSetsArgs
|
quizSets?: boolean | ClassCountOutputTypeCountQuizSetsArgs
|
||||||
materialGroups?: boolean | ClassCountOutputTypeCountMaterialGroupsArgs
|
materialGroups?: boolean | ClassCountOutputTypeCountMaterialGroupsArgs
|
||||||
|
spacedRepetitionSets?: boolean | ClassCountOutputTypeCountSpacedRepetitionSetsArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -649,6 +744,13 @@ export type ClassCountOutputTypeCountMaterialGroupsArgs<ExtArgs extends runtime.
|
||||||
where?: Prisma.MaterialGroupWhereInput
|
where?: Prisma.MaterialGroupWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ClassCountOutputType without action
|
||||||
|
*/
|
||||||
|
export type ClassCountOutputTypeCountSpacedRepetitionSetsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
where?: Prisma.SpacedRepetitionSetWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type ClassSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type ClassSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
|
|
@ -659,6 +761,7 @@ export type ClassSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||||
decks?: boolean | Prisma.Class$decksArgs<ExtArgs>
|
decks?: boolean | Prisma.Class$decksArgs<ExtArgs>
|
||||||
quizSets?: boolean | Prisma.Class$quizSetsArgs<ExtArgs>
|
quizSets?: boolean | Prisma.Class$quizSetsArgs<ExtArgs>
|
||||||
materialGroups?: boolean | Prisma.Class$materialGroupsArgs<ExtArgs>
|
materialGroups?: boolean | Prisma.Class$materialGroupsArgs<ExtArgs>
|
||||||
|
spacedRepetitionSets?: boolean | Prisma.Class$spacedRepetitionSetsArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.ClassCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.ClassCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["class"]>
|
}, ExtArgs["result"]["class"]>
|
||||||
|
|
||||||
|
|
@ -691,6 +794,7 @@ export type ClassInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||||
decks?: boolean | Prisma.Class$decksArgs<ExtArgs>
|
decks?: boolean | Prisma.Class$decksArgs<ExtArgs>
|
||||||
quizSets?: boolean | Prisma.Class$quizSetsArgs<ExtArgs>
|
quizSets?: boolean | Prisma.Class$quizSetsArgs<ExtArgs>
|
||||||
materialGroups?: boolean | Prisma.Class$materialGroupsArgs<ExtArgs>
|
materialGroups?: boolean | Prisma.Class$materialGroupsArgs<ExtArgs>
|
||||||
|
spacedRepetitionSets?: boolean | Prisma.Class$spacedRepetitionSetsArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.ClassCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.ClassCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type ClassIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
export type ClassIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {}
|
||||||
|
|
@ -702,6 +806,7 @@ export type $ClassPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs
|
||||||
decks: Prisma.$DeckPayload<ExtArgs>[]
|
decks: Prisma.$DeckPayload<ExtArgs>[]
|
||||||
quizSets: Prisma.$QuizSetPayload<ExtArgs>[]
|
quizSets: Prisma.$QuizSetPayload<ExtArgs>[]
|
||||||
materialGroups: Prisma.$MaterialGroupPayload<ExtArgs>[]
|
materialGroups: Prisma.$MaterialGroupPayload<ExtArgs>[]
|
||||||
|
spacedRepetitionSets: Prisma.$SpacedRepetitionSetPayload<ExtArgs>[]
|
||||||
}
|
}
|
||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -1106,6 +1211,7 @@ export interface Prisma__ClassClient<T, Null = never, ExtArgs extends runtime.Ty
|
||||||
decks<T extends Prisma.Class$decksArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Class$decksArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$DeckPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
decks<T extends Prisma.Class$decksArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Class$decksArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$DeckPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
quizSets<T extends Prisma.Class$quizSetsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Class$quizSetsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$QuizSetPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
quizSets<T extends Prisma.Class$quizSetsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Class$quizSetsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$QuizSetPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
materialGroups<T extends Prisma.Class$materialGroupsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Class$materialGroupsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$MaterialGroupPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
materialGroups<T extends Prisma.Class$materialGroupsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Class$materialGroupsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$MaterialGroupPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
|
spacedRepetitionSets<T extends Prisma.Class$spacedRepetitionSetsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Class$spacedRepetitionSetsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$SpacedRepetitionSetPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
/**
|
/**
|
||||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||||
|
|
@ -1602,6 +1708,30 @@ export type Class$materialGroupsArgs<ExtArgs extends runtime.Types.Extensions.In
|
||||||
distinct?: Prisma.MaterialGroupScalarFieldEnum | Prisma.MaterialGroupScalarFieldEnum[]
|
distinct?: Prisma.MaterialGroupScalarFieldEnum | Prisma.MaterialGroupScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class.spacedRepetitionSets
|
||||||
|
*/
|
||||||
|
export type Class$spacedRepetitionSetsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the SpacedRepetitionSet
|
||||||
|
*/
|
||||||
|
select?: Prisma.SpacedRepetitionSetSelect<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Omit specific fields from the SpacedRepetitionSet
|
||||||
|
*/
|
||||||
|
omit?: Prisma.SpacedRepetitionSetOmit<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Choose, which related nodes to fetch as well
|
||||||
|
*/
|
||||||
|
include?: Prisma.SpacedRepetitionSetInclude<ExtArgs> | null
|
||||||
|
where?: Prisma.SpacedRepetitionSetWhereInput
|
||||||
|
orderBy?: Prisma.SpacedRepetitionSetOrderByWithRelationInput | Prisma.SpacedRepetitionSetOrderByWithRelationInput[]
|
||||||
|
cursor?: Prisma.SpacedRepetitionSetWhereUniqueInput
|
||||||
|
take?: number
|
||||||
|
skip?: number
|
||||||
|
distinct?: Prisma.SpacedRepetitionSetScalarFieldEnum | Prisma.SpacedRepetitionSetScalarFieldEnum[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class without action
|
* Class without action
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -237,6 +237,7 @@ export type DeckWhereInput = {
|
||||||
cards?: Prisma.FlashcardListRelationFilter
|
cards?: Prisma.FlashcardListRelationFilter
|
||||||
shareLink?: Prisma.XOR<Prisma.ShareLinkNullableScalarRelationFilter, Prisma.ShareLinkWhereInput> | null
|
shareLink?: Prisma.XOR<Prisma.ShareLinkNullableScalarRelationFilter, Prisma.ShareLinkWhereInput> | null
|
||||||
progress?: Prisma.StudyProgressListRelationFilter
|
progress?: Prisma.StudyProgressListRelationFilter
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckListRelationFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckOrderByWithRelationInput = {
|
export type DeckOrderByWithRelationInput = {
|
||||||
|
|
@ -252,6 +253,7 @@ export type DeckOrderByWithRelationInput = {
|
||||||
cards?: Prisma.FlashcardOrderByRelationAggregateInput
|
cards?: Prisma.FlashcardOrderByRelationAggregateInput
|
||||||
shareLink?: Prisma.ShareLinkOrderByWithRelationInput
|
shareLink?: Prisma.ShareLinkOrderByWithRelationInput
|
||||||
progress?: Prisma.StudyProgressOrderByRelationAggregateInput
|
progress?: Prisma.StudyProgressOrderByRelationAggregateInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckOrderByRelationAggregateInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckWhereUniqueInput = Prisma.AtLeast<{
|
export type DeckWhereUniqueInput = Prisma.AtLeast<{
|
||||||
|
|
@ -270,6 +272,7 @@ export type DeckWhereUniqueInput = Prisma.AtLeast<{
|
||||||
cards?: Prisma.FlashcardListRelationFilter
|
cards?: Prisma.FlashcardListRelationFilter
|
||||||
shareLink?: Prisma.XOR<Prisma.ShareLinkNullableScalarRelationFilter, Prisma.ShareLinkWhereInput> | null
|
shareLink?: Prisma.XOR<Prisma.ShareLinkNullableScalarRelationFilter, Prisma.ShareLinkWhereInput> | null
|
||||||
progress?: Prisma.StudyProgressListRelationFilter
|
progress?: Prisma.StudyProgressListRelationFilter
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckListRelationFilter
|
||||||
}, "id">
|
}, "id">
|
||||||
|
|
||||||
export type DeckOrderByWithAggregationInput = {
|
export type DeckOrderByWithAggregationInput = {
|
||||||
|
|
@ -311,6 +314,7 @@ export type DeckCreateInput = {
|
||||||
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedCreateInput = {
|
export type DeckUncheckedCreateInput = {
|
||||||
|
|
@ -324,6 +328,7 @@ export type DeckUncheckedCreateInput = {
|
||||||
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUpdateInput = {
|
export type DeckUpdateInput = {
|
||||||
|
|
@ -337,6 +342,7 @@ export type DeckUpdateInput = {
|
||||||
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||||
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedUpdateInput = {
|
export type DeckUncheckedUpdateInput = {
|
||||||
|
|
@ -350,6 +356,7 @@ export type DeckUncheckedUpdateInput = {
|
||||||
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckCreateManyInput = {
|
export type DeckCreateManyInput = {
|
||||||
|
|
@ -572,6 +579,20 @@ export type DeckUncheckedUpdateManyWithoutGroupNestedInput = {
|
||||||
deleteMany?: Prisma.DeckScalarWhereInput | Prisma.DeckScalarWhereInput[]
|
deleteMany?: Prisma.DeckScalarWhereInput | Prisma.DeckScalarWhereInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DeckCreateNestedOneWithoutSpacedRepetitionMembershipsInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.DeckCreateWithoutSpacedRepetitionMembershipsInput, Prisma.DeckUncheckedCreateWithoutSpacedRepetitionMembershipsInput>
|
||||||
|
connectOrCreate?: Prisma.DeckCreateOrConnectWithoutSpacedRepetitionMembershipsInput
|
||||||
|
connect?: Prisma.DeckWhereUniqueInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeckUpdateOneRequiredWithoutSpacedRepetitionMembershipsNestedInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.DeckCreateWithoutSpacedRepetitionMembershipsInput, Prisma.DeckUncheckedCreateWithoutSpacedRepetitionMembershipsInput>
|
||||||
|
connectOrCreate?: Prisma.DeckCreateOrConnectWithoutSpacedRepetitionMembershipsInput
|
||||||
|
upsert?: Prisma.DeckUpsertWithoutSpacedRepetitionMembershipsInput
|
||||||
|
connect?: Prisma.DeckWhereUniqueInput
|
||||||
|
update?: Prisma.XOR<Prisma.XOR<Prisma.DeckUpdateToOneWithWhereWithoutSpacedRepetitionMembershipsInput, Prisma.DeckUpdateWithoutSpacedRepetitionMembershipsInput>, Prisma.DeckUncheckedUpdateWithoutSpacedRepetitionMembershipsInput>
|
||||||
|
}
|
||||||
|
|
||||||
export type DeckCreateWithoutClassInput = {
|
export type DeckCreateWithoutClassInput = {
|
||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -582,6 +603,7 @@ export type DeckCreateWithoutClassInput = {
|
||||||
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedCreateWithoutClassInput = {
|
export type DeckUncheckedCreateWithoutClassInput = {
|
||||||
|
|
@ -594,6 +616,7 @@ export type DeckUncheckedCreateWithoutClassInput = {
|
||||||
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckCreateOrConnectWithoutClassInput = {
|
export type DeckCreateOrConnectWithoutClassInput = {
|
||||||
|
|
@ -644,6 +667,7 @@ export type DeckCreateWithoutCardsInput = {
|
||||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
||||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedCreateWithoutCardsInput = {
|
export type DeckUncheckedCreateWithoutCardsInput = {
|
||||||
|
|
@ -656,6 +680,7 @@ export type DeckUncheckedCreateWithoutCardsInput = {
|
||||||
groupId?: string | null
|
groupId?: string | null
|
||||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckCreateOrConnectWithoutCardsInput = {
|
export type DeckCreateOrConnectWithoutCardsInput = {
|
||||||
|
|
@ -684,6 +709,7 @@ export type DeckUpdateWithoutCardsInput = {
|
||||||
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
||||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||||
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedUpdateWithoutCardsInput = {
|
export type DeckUncheckedUpdateWithoutCardsInput = {
|
||||||
|
|
@ -696,6 +722,7 @@ export type DeckUncheckedUpdateWithoutCardsInput = {
|
||||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckCreateWithoutProgressInput = {
|
export type DeckCreateWithoutProgressInput = {
|
||||||
|
|
@ -708,6 +735,7 @@ export type DeckCreateWithoutProgressInput = {
|
||||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
||||||
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedCreateWithoutProgressInput = {
|
export type DeckUncheckedCreateWithoutProgressInput = {
|
||||||
|
|
@ -720,6 +748,7 @@ export type DeckUncheckedCreateWithoutProgressInput = {
|
||||||
groupId?: string | null
|
groupId?: string | null
|
||||||
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckCreateOrConnectWithoutProgressInput = {
|
export type DeckCreateOrConnectWithoutProgressInput = {
|
||||||
|
|
@ -748,6 +777,7 @@ export type DeckUpdateWithoutProgressInput = {
|
||||||
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
||||||
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedUpdateWithoutProgressInput = {
|
export type DeckUncheckedUpdateWithoutProgressInput = {
|
||||||
|
|
@ -760,6 +790,7 @@ export type DeckUncheckedUpdateWithoutProgressInput = {
|
||||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckCreateWithoutShareLinkInput = {
|
export type DeckCreateWithoutShareLinkInput = {
|
||||||
|
|
@ -772,6 +803,7 @@ export type DeckCreateWithoutShareLinkInput = {
|
||||||
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
||||||
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedCreateWithoutShareLinkInput = {
|
export type DeckUncheckedCreateWithoutShareLinkInput = {
|
||||||
|
|
@ -784,6 +816,7 @@ export type DeckUncheckedCreateWithoutShareLinkInput = {
|
||||||
groupId?: string | null
|
groupId?: string | null
|
||||||
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckCreateOrConnectWithoutShareLinkInput = {
|
export type DeckCreateOrConnectWithoutShareLinkInput = {
|
||||||
|
|
@ -812,6 +845,7 @@ export type DeckUpdateWithoutShareLinkInput = {
|
||||||
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
||||||
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||||
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedUpdateWithoutShareLinkInput = {
|
export type DeckUncheckedUpdateWithoutShareLinkInput = {
|
||||||
|
|
@ -824,6 +858,7 @@ export type DeckUncheckedUpdateWithoutShareLinkInput = {
|
||||||
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckCreateWithoutGroupInput = {
|
export type DeckCreateWithoutGroupInput = {
|
||||||
|
|
@ -836,6 +871,7 @@ export type DeckCreateWithoutGroupInput = {
|
||||||
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||||
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||||
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedCreateWithoutGroupInput = {
|
export type DeckUncheckedCreateWithoutGroupInput = {
|
||||||
|
|
@ -848,6 +884,7 @@ export type DeckUncheckedCreateWithoutGroupInput = {
|
||||||
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||||
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||||
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedCreateNestedManyWithoutDeckInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckCreateOrConnectWithoutGroupInput = {
|
export type DeckCreateOrConnectWithoutGroupInput = {
|
||||||
|
|
@ -875,6 +912,74 @@ export type DeckUpdateManyWithWhereWithoutGroupInput = {
|
||||||
data: Prisma.XOR<Prisma.DeckUpdateManyMutationInput, Prisma.DeckUncheckedUpdateManyWithoutGroupInput>
|
data: Prisma.XOR<Prisma.DeckUpdateManyMutationInput, Prisma.DeckUncheckedUpdateManyWithoutGroupInput>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DeckCreateWithoutSpacedRepetitionMembershipsInput = {
|
||||||
|
id?: string
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
sortOrder?: number
|
||||||
|
createdAt?: Date | string
|
||||||
|
class: Prisma.ClassCreateNestedOneWithoutDecksInput
|
||||||
|
group?: Prisma.MaterialGroupCreateNestedOneWithoutDecksInput
|
||||||
|
cards?: Prisma.FlashcardCreateNestedManyWithoutDeckInput
|
||||||
|
shareLink?: Prisma.ShareLinkCreateNestedOneWithoutDeckInput
|
||||||
|
progress?: Prisma.StudyProgressCreateNestedManyWithoutDeckInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeckUncheckedCreateWithoutSpacedRepetitionMembershipsInput = {
|
||||||
|
id?: string
|
||||||
|
classId: string
|
||||||
|
name: string
|
||||||
|
description?: string | null
|
||||||
|
sortOrder?: number
|
||||||
|
createdAt?: Date | string
|
||||||
|
groupId?: string | null
|
||||||
|
cards?: Prisma.FlashcardUncheckedCreateNestedManyWithoutDeckInput
|
||||||
|
shareLink?: Prisma.ShareLinkUncheckedCreateNestedOneWithoutDeckInput
|
||||||
|
progress?: Prisma.StudyProgressUncheckedCreateNestedManyWithoutDeckInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeckCreateOrConnectWithoutSpacedRepetitionMembershipsInput = {
|
||||||
|
where: Prisma.DeckWhereUniqueInput
|
||||||
|
create: Prisma.XOR<Prisma.DeckCreateWithoutSpacedRepetitionMembershipsInput, Prisma.DeckUncheckedCreateWithoutSpacedRepetitionMembershipsInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeckUpsertWithoutSpacedRepetitionMembershipsInput = {
|
||||||
|
update: Prisma.XOR<Prisma.DeckUpdateWithoutSpacedRepetitionMembershipsInput, Prisma.DeckUncheckedUpdateWithoutSpacedRepetitionMembershipsInput>
|
||||||
|
create: Prisma.XOR<Prisma.DeckCreateWithoutSpacedRepetitionMembershipsInput, Prisma.DeckUncheckedCreateWithoutSpacedRepetitionMembershipsInput>
|
||||||
|
where?: Prisma.DeckWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeckUpdateToOneWithWhereWithoutSpacedRepetitionMembershipsInput = {
|
||||||
|
where?: Prisma.DeckWhereInput
|
||||||
|
data: Prisma.XOR<Prisma.DeckUpdateWithoutSpacedRepetitionMembershipsInput, Prisma.DeckUncheckedUpdateWithoutSpacedRepetitionMembershipsInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeckUpdateWithoutSpacedRepetitionMembershipsInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
class?: Prisma.ClassUpdateOneRequiredWithoutDecksNestedInput
|
||||||
|
group?: Prisma.MaterialGroupUpdateOneWithoutDecksNestedInput
|
||||||
|
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||||
|
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||||
|
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeckUncheckedUpdateWithoutSpacedRepetitionMembershipsInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
classId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
name?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
description?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||||
|
groupId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||||
|
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
|
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||||
|
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
export type DeckCreateManyClassInput = {
|
export type DeckCreateManyClassInput = {
|
||||||
id?: string
|
id?: string
|
||||||
name: string
|
name: string
|
||||||
|
|
@ -894,6 +999,7 @@ export type DeckUpdateWithoutClassInput = {
|
||||||
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||||
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedUpdateWithoutClassInput = {
|
export type DeckUncheckedUpdateWithoutClassInput = {
|
||||||
|
|
@ -906,6 +1012,7 @@ export type DeckUncheckedUpdateWithoutClassInput = {
|
||||||
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedUpdateManyWithoutClassInput = {
|
export type DeckUncheckedUpdateManyWithoutClassInput = {
|
||||||
|
|
@ -936,6 +1043,7 @@ export type DeckUpdateWithoutGroupInput = {
|
||||||
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
cards?: Prisma.FlashcardUpdateManyWithoutDeckNestedInput
|
||||||
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
shareLink?: Prisma.ShareLinkUpdateOneWithoutDeckNestedInput
|
||||||
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
progress?: Prisma.StudyProgressUpdateManyWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedUpdateWithoutGroupInput = {
|
export type DeckUncheckedUpdateWithoutGroupInput = {
|
||||||
|
|
@ -948,6 +1056,7 @@ export type DeckUncheckedUpdateWithoutGroupInput = {
|
||||||
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
cards?: Prisma.FlashcardUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
shareLink?: Prisma.ShareLinkUncheckedUpdateOneWithoutDeckNestedInput
|
||||||
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
progress?: Prisma.StudyProgressUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
|
spacedRepetitionMemberships?: Prisma.SpacedRepetitionSetDeckUncheckedUpdateManyWithoutDeckNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckUncheckedUpdateManyWithoutGroupInput = {
|
export type DeckUncheckedUpdateManyWithoutGroupInput = {
|
||||||
|
|
@ -967,11 +1076,13 @@ export type DeckUncheckedUpdateManyWithoutGroupInput = {
|
||||||
export type DeckCountOutputType = {
|
export type DeckCountOutputType = {
|
||||||
cards: number
|
cards: number
|
||||||
progress: number
|
progress: number
|
||||||
|
spacedRepetitionMemberships: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeckCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type DeckCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
cards?: boolean | DeckCountOutputTypeCountCardsArgs
|
cards?: boolean | DeckCountOutputTypeCountCardsArgs
|
||||||
progress?: boolean | DeckCountOutputTypeCountProgressArgs
|
progress?: boolean | DeckCountOutputTypeCountProgressArgs
|
||||||
|
spacedRepetitionMemberships?: boolean | DeckCountOutputTypeCountSpacedRepetitionMembershipsArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -998,6 +1109,13 @@ export type DeckCountOutputTypeCountProgressArgs<ExtArgs extends runtime.Types.E
|
||||||
where?: Prisma.StudyProgressWhereInput
|
where?: Prisma.StudyProgressWhereInput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeckCountOutputType without action
|
||||||
|
*/
|
||||||
|
export type DeckCountOutputTypeCountSpacedRepetitionMembershipsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
where?: Prisma.SpacedRepetitionSetDeckWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type DeckSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type DeckSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
|
|
@ -1012,6 +1130,7 @@ export type DeckSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = r
|
||||||
cards?: boolean | Prisma.Deck$cardsArgs<ExtArgs>
|
cards?: boolean | Prisma.Deck$cardsArgs<ExtArgs>
|
||||||
shareLink?: boolean | Prisma.Deck$shareLinkArgs<ExtArgs>
|
shareLink?: boolean | Prisma.Deck$shareLinkArgs<ExtArgs>
|
||||||
progress?: boolean | Prisma.Deck$progressArgs<ExtArgs>
|
progress?: boolean | Prisma.Deck$progressArgs<ExtArgs>
|
||||||
|
spacedRepetitionMemberships?: boolean | Prisma.Deck$spacedRepetitionMembershipsArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.DeckCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.DeckCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["deck"]>
|
}, ExtArgs["result"]["deck"]>
|
||||||
|
|
||||||
|
|
@ -1056,6 +1175,7 @@ export type DeckInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||||
cards?: boolean | Prisma.Deck$cardsArgs<ExtArgs>
|
cards?: boolean | Prisma.Deck$cardsArgs<ExtArgs>
|
||||||
shareLink?: boolean | Prisma.Deck$shareLinkArgs<ExtArgs>
|
shareLink?: boolean | Prisma.Deck$shareLinkArgs<ExtArgs>
|
||||||
progress?: boolean | Prisma.Deck$progressArgs<ExtArgs>
|
progress?: boolean | Prisma.Deck$progressArgs<ExtArgs>
|
||||||
|
spacedRepetitionMemberships?: boolean | Prisma.Deck$spacedRepetitionMembershipsArgs<ExtArgs>
|
||||||
_count?: boolean | Prisma.DeckCountOutputTypeDefaultArgs<ExtArgs>
|
_count?: boolean | Prisma.DeckCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type DeckIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type DeckIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
|
@ -1075,6 +1195,7 @@ export type $DeckPayload<ExtArgs extends runtime.Types.Extensions.InternalArgs =
|
||||||
cards: Prisma.$FlashcardPayload<ExtArgs>[]
|
cards: Prisma.$FlashcardPayload<ExtArgs>[]
|
||||||
shareLink: Prisma.$ShareLinkPayload<ExtArgs> | null
|
shareLink: Prisma.$ShareLinkPayload<ExtArgs> | null
|
||||||
progress: Prisma.$StudyProgressPayload<ExtArgs>[]
|
progress: Prisma.$StudyProgressPayload<ExtArgs>[]
|
||||||
|
spacedRepetitionMemberships: Prisma.$SpacedRepetitionSetDeckPayload<ExtArgs>[]
|
||||||
}
|
}
|
||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -1483,6 +1604,7 @@ export interface Prisma__DeckClient<T, Null = never, ExtArgs extends runtime.Typ
|
||||||
cards<T extends Prisma.Deck$cardsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$cardsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$FlashcardPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
cards<T extends Prisma.Deck$cardsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$cardsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$FlashcardPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
shareLink<T extends Prisma.Deck$shareLinkArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$shareLinkArgs<ExtArgs>>): Prisma.Prisma__ShareLinkClient<runtime.Types.Result.GetResult<Prisma.$ShareLinkPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
shareLink<T extends Prisma.Deck$shareLinkArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$shareLinkArgs<ExtArgs>>): Prisma.Prisma__ShareLinkClient<runtime.Types.Result.GetResult<Prisma.$ShareLinkPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions>
|
||||||
progress<T extends Prisma.Deck$progressArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$progressArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$StudyProgressPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
progress<T extends Prisma.Deck$progressArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$progressArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$StudyProgressPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
|
spacedRepetitionMemberships<T extends Prisma.Deck$spacedRepetitionMembershipsArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Deck$spacedRepetitionMembershipsArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$SpacedRepetitionSetDeckPayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
/**
|
/**
|
||||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||||
|
|
@ -2003,6 +2125,30 @@ export type Deck$progressArgs<ExtArgs extends runtime.Types.Extensions.InternalA
|
||||||
distinct?: Prisma.StudyProgressScalarFieldEnum | Prisma.StudyProgressScalarFieldEnum[]
|
distinct?: Prisma.StudyProgressScalarFieldEnum | Prisma.StudyProgressScalarFieldEnum[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deck.spacedRepetitionMemberships
|
||||||
|
*/
|
||||||
|
export type Deck$spacedRepetitionMembershipsArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the SpacedRepetitionSetDeck
|
||||||
|
*/
|
||||||
|
select?: Prisma.SpacedRepetitionSetDeckSelect<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Omit specific fields from the SpacedRepetitionSetDeck
|
||||||
|
*/
|
||||||
|
omit?: Prisma.SpacedRepetitionSetDeckOmit<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Choose, which related nodes to fetch as well
|
||||||
|
*/
|
||||||
|
include?: Prisma.SpacedRepetitionSetDeckInclude<ExtArgs> | null
|
||||||
|
where?: Prisma.SpacedRepetitionSetDeckWhereInput
|
||||||
|
orderBy?: Prisma.SpacedRepetitionSetDeckOrderByWithRelationInput | Prisma.SpacedRepetitionSetDeckOrderByWithRelationInput[]
|
||||||
|
cursor?: Prisma.SpacedRepetitionSetDeckWhereUniqueInput
|
||||||
|
take?: number
|
||||||
|
skip?: number
|
||||||
|
distinct?: Prisma.SpacedRepetitionSetDeckScalarFieldEnum | Prisma.SpacedRepetitionSetDeckScalarFieldEnum[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deck without action
|
* Deck without action
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -217,6 +217,7 @@ export type FlashcardWhereInput = {
|
||||||
back?: Prisma.StringFilter<"Flashcard"> | string
|
back?: Prisma.StringFilter<"Flashcard"> | string
|
||||||
sortOrder?: Prisma.IntFilter<"Flashcard"> | number
|
sortOrder?: Prisma.IntFilter<"Flashcard"> | number
|
||||||
deck?: Prisma.XOR<Prisma.DeckScalarRelationFilter, Prisma.DeckWhereInput>
|
deck?: Prisma.XOR<Prisma.DeckScalarRelationFilter, Prisma.DeckWhereInput>
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateListRelationFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlashcardOrderByWithRelationInput = {
|
export type FlashcardOrderByWithRelationInput = {
|
||||||
|
|
@ -226,6 +227,7 @@ export type FlashcardOrderByWithRelationInput = {
|
||||||
back?: Prisma.SortOrder
|
back?: Prisma.SortOrder
|
||||||
sortOrder?: Prisma.SortOrder
|
sortOrder?: Prisma.SortOrder
|
||||||
deck?: Prisma.DeckOrderByWithRelationInput
|
deck?: Prisma.DeckOrderByWithRelationInput
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateOrderByRelationAggregateInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlashcardWhereUniqueInput = Prisma.AtLeast<{
|
export type FlashcardWhereUniqueInput = Prisma.AtLeast<{
|
||||||
|
|
@ -238,6 +240,7 @@ export type FlashcardWhereUniqueInput = Prisma.AtLeast<{
|
||||||
back?: Prisma.StringFilter<"Flashcard"> | string
|
back?: Prisma.StringFilter<"Flashcard"> | string
|
||||||
sortOrder?: Prisma.IntFilter<"Flashcard"> | number
|
sortOrder?: Prisma.IntFilter<"Flashcard"> | number
|
||||||
deck?: Prisma.XOR<Prisma.DeckScalarRelationFilter, Prisma.DeckWhereInput>
|
deck?: Prisma.XOR<Prisma.DeckScalarRelationFilter, Prisma.DeckWhereInput>
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateListRelationFilter
|
||||||
}, "id">
|
}, "id">
|
||||||
|
|
||||||
export type FlashcardOrderByWithAggregationInput = {
|
export type FlashcardOrderByWithAggregationInput = {
|
||||||
|
|
@ -270,6 +273,7 @@ export type FlashcardCreateInput = {
|
||||||
back: string
|
back: string
|
||||||
sortOrder?: number
|
sortOrder?: number
|
||||||
deck: Prisma.DeckCreateNestedOneWithoutCardsInput
|
deck: Prisma.DeckCreateNestedOneWithoutCardsInput
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateCreateNestedManyWithoutFlashcardInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlashcardUncheckedCreateInput = {
|
export type FlashcardUncheckedCreateInput = {
|
||||||
|
|
@ -278,6 +282,7 @@ export type FlashcardUncheckedCreateInput = {
|
||||||
front: string
|
front: string
|
||||||
back: string
|
back: string
|
||||||
sortOrder?: number
|
sortOrder?: number
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateUncheckedCreateNestedManyWithoutFlashcardInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlashcardUpdateInput = {
|
export type FlashcardUpdateInput = {
|
||||||
|
|
@ -286,6 +291,7 @@ export type FlashcardUpdateInput = {
|
||||||
back?: Prisma.StringFieldUpdateOperationsInput | string
|
back?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
deck?: Prisma.DeckUpdateOneRequiredWithoutCardsNestedInput
|
deck?: Prisma.DeckUpdateOneRequiredWithoutCardsNestedInput
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateUpdateManyWithoutFlashcardNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlashcardUncheckedUpdateInput = {
|
export type FlashcardUncheckedUpdateInput = {
|
||||||
|
|
@ -294,6 +300,7 @@ export type FlashcardUncheckedUpdateInput = {
|
||||||
front?: Prisma.StringFieldUpdateOperationsInput | string
|
front?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
back?: Prisma.StringFieldUpdateOperationsInput | string
|
back?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateUncheckedUpdateManyWithoutFlashcardNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlashcardCreateManyInput = {
|
export type FlashcardCreateManyInput = {
|
||||||
|
|
@ -361,6 +368,11 @@ export type FlashcardSumOrderByAggregateInput = {
|
||||||
sortOrder?: Prisma.SortOrder
|
sortOrder?: Prisma.SortOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FlashcardScalarRelationFilter = {
|
||||||
|
is?: Prisma.FlashcardWhereInput
|
||||||
|
isNot?: Prisma.FlashcardWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
export type FlashcardCreateNestedManyWithoutDeckInput = {
|
export type FlashcardCreateNestedManyWithoutDeckInput = {
|
||||||
create?: Prisma.XOR<Prisma.FlashcardCreateWithoutDeckInput, Prisma.FlashcardUncheckedCreateWithoutDeckInput> | Prisma.FlashcardCreateWithoutDeckInput[] | Prisma.FlashcardUncheckedCreateWithoutDeckInput[]
|
create?: Prisma.XOR<Prisma.FlashcardCreateWithoutDeckInput, Prisma.FlashcardUncheckedCreateWithoutDeckInput> | Prisma.FlashcardCreateWithoutDeckInput[] | Prisma.FlashcardUncheckedCreateWithoutDeckInput[]
|
||||||
connectOrCreate?: Prisma.FlashcardCreateOrConnectWithoutDeckInput | Prisma.FlashcardCreateOrConnectWithoutDeckInput[]
|
connectOrCreate?: Prisma.FlashcardCreateOrConnectWithoutDeckInput | Prisma.FlashcardCreateOrConnectWithoutDeckInput[]
|
||||||
|
|
@ -403,11 +415,26 @@ export type FlashcardUncheckedUpdateManyWithoutDeckNestedInput = {
|
||||||
deleteMany?: Prisma.FlashcardScalarWhereInput | Prisma.FlashcardScalarWhereInput[]
|
deleteMany?: Prisma.FlashcardScalarWhereInput | Prisma.FlashcardScalarWhereInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FlashcardCreateNestedOneWithoutSpacedRepetitionStatesInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.FlashcardCreateWithoutSpacedRepetitionStatesInput, Prisma.FlashcardUncheckedCreateWithoutSpacedRepetitionStatesInput>
|
||||||
|
connectOrCreate?: Prisma.FlashcardCreateOrConnectWithoutSpacedRepetitionStatesInput
|
||||||
|
connect?: Prisma.FlashcardWhereUniqueInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlashcardUpdateOneRequiredWithoutSpacedRepetitionStatesNestedInput = {
|
||||||
|
create?: Prisma.XOR<Prisma.FlashcardCreateWithoutSpacedRepetitionStatesInput, Prisma.FlashcardUncheckedCreateWithoutSpacedRepetitionStatesInput>
|
||||||
|
connectOrCreate?: Prisma.FlashcardCreateOrConnectWithoutSpacedRepetitionStatesInput
|
||||||
|
upsert?: Prisma.FlashcardUpsertWithoutSpacedRepetitionStatesInput
|
||||||
|
connect?: Prisma.FlashcardWhereUniqueInput
|
||||||
|
update?: Prisma.XOR<Prisma.XOR<Prisma.FlashcardUpdateToOneWithWhereWithoutSpacedRepetitionStatesInput, Prisma.FlashcardUpdateWithoutSpacedRepetitionStatesInput>, Prisma.FlashcardUncheckedUpdateWithoutSpacedRepetitionStatesInput>
|
||||||
|
}
|
||||||
|
|
||||||
export type FlashcardCreateWithoutDeckInput = {
|
export type FlashcardCreateWithoutDeckInput = {
|
||||||
id?: string
|
id?: string
|
||||||
front: string
|
front: string
|
||||||
back: string
|
back: string
|
||||||
sortOrder?: number
|
sortOrder?: number
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateCreateNestedManyWithoutFlashcardInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlashcardUncheckedCreateWithoutDeckInput = {
|
export type FlashcardUncheckedCreateWithoutDeckInput = {
|
||||||
|
|
@ -415,6 +442,7 @@ export type FlashcardUncheckedCreateWithoutDeckInput = {
|
||||||
front: string
|
front: string
|
||||||
back: string
|
back: string
|
||||||
sortOrder?: number
|
sortOrder?: number
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateUncheckedCreateNestedManyWithoutFlashcardInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlashcardCreateOrConnectWithoutDeckInput = {
|
export type FlashcardCreateOrConnectWithoutDeckInput = {
|
||||||
|
|
@ -453,6 +481,54 @@ export type FlashcardScalarWhereInput = {
|
||||||
sortOrder?: Prisma.IntFilter<"Flashcard"> | number
|
sortOrder?: Prisma.IntFilter<"Flashcard"> | number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type FlashcardCreateWithoutSpacedRepetitionStatesInput = {
|
||||||
|
id?: string
|
||||||
|
front: string
|
||||||
|
back: string
|
||||||
|
sortOrder?: number
|
||||||
|
deck: Prisma.DeckCreateNestedOneWithoutCardsInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlashcardUncheckedCreateWithoutSpacedRepetitionStatesInput = {
|
||||||
|
id?: string
|
||||||
|
deckId: string
|
||||||
|
front: string
|
||||||
|
back: string
|
||||||
|
sortOrder?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlashcardCreateOrConnectWithoutSpacedRepetitionStatesInput = {
|
||||||
|
where: Prisma.FlashcardWhereUniqueInput
|
||||||
|
create: Prisma.XOR<Prisma.FlashcardCreateWithoutSpacedRepetitionStatesInput, Prisma.FlashcardUncheckedCreateWithoutSpacedRepetitionStatesInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlashcardUpsertWithoutSpacedRepetitionStatesInput = {
|
||||||
|
update: Prisma.XOR<Prisma.FlashcardUpdateWithoutSpacedRepetitionStatesInput, Prisma.FlashcardUncheckedUpdateWithoutSpacedRepetitionStatesInput>
|
||||||
|
create: Prisma.XOR<Prisma.FlashcardCreateWithoutSpacedRepetitionStatesInput, Prisma.FlashcardUncheckedCreateWithoutSpacedRepetitionStatesInput>
|
||||||
|
where?: Prisma.FlashcardWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlashcardUpdateToOneWithWhereWithoutSpacedRepetitionStatesInput = {
|
||||||
|
where?: Prisma.FlashcardWhereInput
|
||||||
|
data: Prisma.XOR<Prisma.FlashcardUpdateWithoutSpacedRepetitionStatesInput, Prisma.FlashcardUncheckedUpdateWithoutSpacedRepetitionStatesInput>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlashcardUpdateWithoutSpacedRepetitionStatesInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
front?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
back?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
deck?: Prisma.DeckUpdateOneRequiredWithoutCardsNestedInput
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlashcardUncheckedUpdateWithoutSpacedRepetitionStatesInput = {
|
||||||
|
id?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
deckId?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
front?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
back?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
}
|
||||||
|
|
||||||
export type FlashcardCreateManyDeckInput = {
|
export type FlashcardCreateManyDeckInput = {
|
||||||
id?: string
|
id?: string
|
||||||
front: string
|
front: string
|
||||||
|
|
@ -465,6 +541,7 @@ export type FlashcardUpdateWithoutDeckInput = {
|
||||||
front?: Prisma.StringFieldUpdateOperationsInput | string
|
front?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
back?: Prisma.StringFieldUpdateOperationsInput | string
|
back?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateUpdateManyWithoutFlashcardNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlashcardUncheckedUpdateWithoutDeckInput = {
|
export type FlashcardUncheckedUpdateWithoutDeckInput = {
|
||||||
|
|
@ -472,6 +549,7 @@ export type FlashcardUncheckedUpdateWithoutDeckInput = {
|
||||||
front?: Prisma.StringFieldUpdateOperationsInput | string
|
front?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
back?: Prisma.StringFieldUpdateOperationsInput | string
|
back?: Prisma.StringFieldUpdateOperationsInput | string
|
||||||
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
sortOrder?: Prisma.IntFieldUpdateOperationsInput | number
|
||||||
|
spacedRepetitionStates?: Prisma.SpacedRepetitionCardStateUncheckedUpdateManyWithoutFlashcardNestedInput
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FlashcardUncheckedUpdateManyWithoutDeckInput = {
|
export type FlashcardUncheckedUpdateManyWithoutDeckInput = {
|
||||||
|
|
@ -482,6 +560,35 @@ export type FlashcardUncheckedUpdateManyWithoutDeckInput = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Count Type FlashcardCountOutputType
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type FlashcardCountOutputType = {
|
||||||
|
spacedRepetitionStates: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FlashcardCountOutputTypeSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
spacedRepetitionStates?: boolean | FlashcardCountOutputTypeCountSpacedRepetitionStatesArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FlashcardCountOutputType without action
|
||||||
|
*/
|
||||||
|
export type FlashcardCountOutputTypeDefaultArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the FlashcardCountOutputType
|
||||||
|
*/
|
||||||
|
select?: Prisma.FlashcardCountOutputTypeSelect<ExtArgs> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FlashcardCountOutputType without action
|
||||||
|
*/
|
||||||
|
export type FlashcardCountOutputTypeCountSpacedRepetitionStatesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
where?: Prisma.SpacedRepetitionCardStateWhereInput
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export type FlashcardSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type FlashcardSelect<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
id?: boolean
|
id?: boolean
|
||||||
|
|
@ -490,6 +597,8 @@ export type FlashcardSelect<ExtArgs extends runtime.Types.Extensions.InternalArg
|
||||||
back?: boolean
|
back?: boolean
|
||||||
sortOrder?: boolean
|
sortOrder?: boolean
|
||||||
deck?: boolean | Prisma.DeckDefaultArgs<ExtArgs>
|
deck?: boolean | Prisma.DeckDefaultArgs<ExtArgs>
|
||||||
|
spacedRepetitionStates?: boolean | Prisma.Flashcard$spacedRepetitionStatesArgs<ExtArgs>
|
||||||
|
_count?: boolean | Prisma.FlashcardCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}, ExtArgs["result"]["flashcard"]>
|
}, ExtArgs["result"]["flashcard"]>
|
||||||
|
|
||||||
export type FlashcardSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
export type FlashcardSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetSelect<{
|
||||||
|
|
@ -521,6 +630,8 @@ export type FlashcardSelectScalar = {
|
||||||
export type FlashcardOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "deckId" | "front" | "back" | "sortOrder", ExtArgs["result"]["flashcard"]>
|
export type FlashcardOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "deckId" | "front" | "back" | "sortOrder", ExtArgs["result"]["flashcard"]>
|
||||||
export type FlashcardInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type FlashcardInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
deck?: boolean | Prisma.DeckDefaultArgs<ExtArgs>
|
deck?: boolean | Prisma.DeckDefaultArgs<ExtArgs>
|
||||||
|
spacedRepetitionStates?: boolean | Prisma.Flashcard$spacedRepetitionStatesArgs<ExtArgs>
|
||||||
|
_count?: boolean | Prisma.FlashcardCountOutputTypeDefaultArgs<ExtArgs>
|
||||||
}
|
}
|
||||||
export type FlashcardIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
export type FlashcardIncludeCreateManyAndReturn<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
deck?: boolean | Prisma.DeckDefaultArgs<ExtArgs>
|
deck?: boolean | Prisma.DeckDefaultArgs<ExtArgs>
|
||||||
|
|
@ -533,6 +644,7 @@ export type $FlashcardPayload<ExtArgs extends runtime.Types.Extensions.InternalA
|
||||||
name: "Flashcard"
|
name: "Flashcard"
|
||||||
objects: {
|
objects: {
|
||||||
deck: Prisma.$DeckPayload<ExtArgs>
|
deck: Prisma.$DeckPayload<ExtArgs>
|
||||||
|
spacedRepetitionStates: Prisma.$SpacedRepetitionCardStatePayload<ExtArgs>[]
|
||||||
}
|
}
|
||||||
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
scalars: runtime.Types.Extensions.GetPayloadResult<{
|
||||||
id: string
|
id: string
|
||||||
|
|
@ -935,6 +1047,7 @@ readonly fields: FlashcardFieldRefs;
|
||||||
export interface Prisma__FlashcardClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
export interface Prisma__FlashcardClient<T, Null = never, ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> extends Prisma.PrismaPromise<T> {
|
||||||
readonly [Symbol.toStringTag]: "PrismaPromise"
|
readonly [Symbol.toStringTag]: "PrismaPromise"
|
||||||
deck<T extends Prisma.DeckDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.DeckDefaultArgs<ExtArgs>>): Prisma.Prisma__DeckClient<runtime.Types.Result.GetResult<Prisma.$DeckPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
deck<T extends Prisma.DeckDefaultArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.DeckDefaultArgs<ExtArgs>>): Prisma.Prisma__DeckClient<runtime.Types.Result.GetResult<Prisma.$DeckPayload<ExtArgs>, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions>
|
||||||
|
spacedRepetitionStates<T extends Prisma.Flashcard$spacedRepetitionStatesArgs<ExtArgs> = {}>(args?: Prisma.Subset<T, Prisma.Flashcard$spacedRepetitionStatesArgs<ExtArgs>>): Prisma.PrismaPromise<runtime.Types.Result.GetResult<Prisma.$SpacedRepetitionCardStatePayload<ExtArgs>, T, "findMany", GlobalOmitOptions> | Null>
|
||||||
/**
|
/**
|
||||||
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
* Attaches callbacks for the resolution and/or rejection of the Promise.
|
||||||
* @param onfulfilled The callback to execute when the Promise is resolved.
|
* @param onfulfilled The callback to execute when the Promise is resolved.
|
||||||
|
|
@ -1367,6 +1480,30 @@ export type FlashcardDeleteManyArgs<ExtArgs extends runtime.Types.Extensions.Int
|
||||||
limit?: number
|
limit?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flashcard.spacedRepetitionStates
|
||||||
|
*/
|
||||||
|
export type Flashcard$spacedRepetitionStatesArgs<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||||
|
/**
|
||||||
|
* Select specific fields to fetch from the SpacedRepetitionCardState
|
||||||
|
*/
|
||||||
|
select?: Prisma.SpacedRepetitionCardStateSelect<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Omit specific fields from the SpacedRepetitionCardState
|
||||||
|
*/
|
||||||
|
omit?: Prisma.SpacedRepetitionCardStateOmit<ExtArgs> | null
|
||||||
|
/**
|
||||||
|
* Choose, which related nodes to fetch as well
|
||||||
|
*/
|
||||||
|
include?: Prisma.SpacedRepetitionCardStateInclude<ExtArgs> | null
|
||||||
|
where?: Prisma.SpacedRepetitionCardStateWhereInput
|
||||||
|
orderBy?: Prisma.SpacedRepetitionCardStateOrderByWithRelationInput | Prisma.SpacedRepetitionCardStateOrderByWithRelationInput[]
|
||||||
|
cursor?: Prisma.SpacedRepetitionCardStateWhereUniqueInput
|
||||||
|
take?: number
|
||||||
|
skip?: number
|
||||||
|
distinct?: Prisma.SpacedRepetitionCardStateScalarFieldEnum | Prisma.SpacedRepetitionCardStateScalarFieldEnum[]
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flashcard without action
|
* Flashcard without action
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
2004
src/generated/prisma/models/SpacedRepetitionCardState.ts
Normal file
2004
src/generated/prisma/models/SpacedRepetitionCardState.ts
Normal file
File diff suppressed because it is too large
Load diff
1776
src/generated/prisma/models/SpacedRepetitionSet.ts
Normal file
1776
src/generated/prisma/models/SpacedRepetitionSet.ts
Normal file
File diff suppressed because it is too large
Load diff
1470
src/generated/prisma/models/SpacedRepetitionSetDeck.ts
Normal file
1470
src/generated/prisma/models/SpacedRepetitionSetDeck.ts
Normal file
File diff suppressed because it is too large
Load diff
121
src/lib/spacedRepetition.test.ts
Normal file
121
src/lib/spacedRepetition.test.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
calculateQueueCounts,
|
||||||
|
getArizonaDayBounds,
|
||||||
|
getEffectiveDue,
|
||||||
|
getLearnAheadCutoff,
|
||||||
|
previewRatings,
|
||||||
|
scheduleRating,
|
||||||
|
type SpacedRepetitionRating,
|
||||||
|
} from "@/lib/spacedRepetition";
|
||||||
|
|
||||||
|
describe("spaced repetition scheduling", () => {
|
||||||
|
const now = new Date("2026-07-13T20:00:00.000Z");
|
||||||
|
|
||||||
|
it("returns a distinct valid state for every rating", () => {
|
||||||
|
const ratings: SpacedRepetitionRating[] = ["AGAIN", "HARD", "GOOD", "EASY"];
|
||||||
|
for (const rating of ratings) {
|
||||||
|
const state = scheduleRating(null, rating, now);
|
||||||
|
expect(state.reps).toBe(1);
|
||||||
|
expect(state.lastReview?.toISOString()).toBe(now.toISOString());
|
||||||
|
expect(state.due.getTime()).toBeGreaterThan(now.getTime());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("orders first-review previews from Again through Easy", () => {
|
||||||
|
const previews = previewRatings(null, now);
|
||||||
|
const dueTimes = previews.map((preview) => new Date(preview.due).getTime());
|
||||||
|
expect(dueTimes).toHaveLength(4);
|
||||||
|
expect(dueTimes[0]).toBeLessThan(dueTimes[1]);
|
||||||
|
expect(dueTimes[1]).toBeLessThanOrEqual(dueTimes[2]);
|
||||||
|
expect(dueTimes[2]).toBeLessThan(dueTimes[3]);
|
||||||
|
expect(dueTimes.map((due) => Math.round((due - now.getTime()) / 60000))).toEqual([
|
||||||
|
1,
|
||||||
|
6,
|
||||||
|
10,
|
||||||
|
7 * 24 * 60 + 11 * 60,
|
||||||
|
]);
|
||||||
|
expect(new Date(previews[3].due).toISOString()).toBe(
|
||||||
|
"2026-07-21T07:00:00.000Z"
|
||||||
|
);
|
||||||
|
expect(previews[3].scheduledDays).toBe(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("aligns graduated day intervals to Arizona midnight", () => {
|
||||||
|
const learning = scheduleRating(null, "GOOD", now);
|
||||||
|
const graduated = scheduleRating(learning, "GOOD", learning.due);
|
||||||
|
|
||||||
|
expect(graduated.scheduledDays).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(graduated.due.toISOString()).toMatch(/T07:00:00\.000Z$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses Anki's 20-minute learn-ahead window", () => {
|
||||||
|
expect(getLearnAheadCutoff(now).toISOString()).toBe("2026-07-13T20:20:00.000Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("uses midnight in America/Phoenix as the daily boundary", () => {
|
||||||
|
const beforeMidnight = getArizonaDayBounds(new Date("2026-07-14T06:59:59.000Z"));
|
||||||
|
const afterMidnight = getArizonaDayBounds(new Date("2026-07-14T07:00:00.000Z"));
|
||||||
|
expect(beforeMidnight.start.toISOString()).toBe("2026-07-13T07:00:00.000Z");
|
||||||
|
expect(afterMidnight.start.toISOString()).toBe("2026-07-14T07:00:00.000Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("treats legacy day-based due times as due at midnight", () => {
|
||||||
|
expect(getEffectiveDue({
|
||||||
|
due: new Date("2026-07-14T20:00:00.000Z"),
|
||||||
|
scheduledDays: 1,
|
||||||
|
}).toISOString()).toBe("2026-07-14T07:00:00.000Z");
|
||||||
|
expect(getEffectiveDue({
|
||||||
|
due: new Date("2026-07-14T07:10:00.000Z"),
|
||||||
|
scheduledDays: 0,
|
||||||
|
}).toISOString()).toBe("2026-07-14T07:10:00.000Z");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("daily queue counts", () => {
|
||||||
|
it("adds all due cards while capping only new cards", () => {
|
||||||
|
expect(calculateQueueCounts({
|
||||||
|
totalCards: 100,
|
||||||
|
reviewedCards: 40,
|
||||||
|
dueCards: 55,
|
||||||
|
introducedToday: 12,
|
||||||
|
newCardsPerDay: 30,
|
||||||
|
})).toEqual({ unseenCards: 60, newCardsAvailable: 18, learningAheadCards: 0, availableCards: 73 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("can pause new cards without hiding due reviews", () => {
|
||||||
|
expect(calculateQueueCounts({
|
||||||
|
totalCards: 100,
|
||||||
|
reviewedCards: 40,
|
||||||
|
dueCards: 7,
|
||||||
|
introducedToday: 0,
|
||||||
|
newCardsPerDay: 0,
|
||||||
|
})).toEqual({ unseenCards: 60, newCardsAvailable: 0, learningAheadCards: 0, availableCards: 7 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("never offers more new cards than remain unseen", () => {
|
||||||
|
expect(calculateQueueCounts({
|
||||||
|
totalCards: 12,
|
||||||
|
reviewedCards: 10,
|
||||||
|
dueCards: 0,
|
||||||
|
introducedToday: 0,
|
||||||
|
newCardsPerDay: 30,
|
||||||
|
}).newCardsAvailable).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps short learning steps available when no new cards remain", () => {
|
||||||
|
expect(calculateQueueCounts({
|
||||||
|
totalCards: 25,
|
||||||
|
reviewedCards: 25,
|
||||||
|
dueCards: 0,
|
||||||
|
learningAheadCards: 25,
|
||||||
|
introducedToday: 25,
|
||||||
|
newCardsPerDay: 30,
|
||||||
|
})).toEqual({
|
||||||
|
unseenCards: 0,
|
||||||
|
newCardsAvailable: 0,
|
||||||
|
learningAheadCards: 25,
|
||||||
|
availableCards: 25,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
162
src/lib/spacedRepetition.ts
Normal file
162
src/lib/spacedRepetition.ts
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
import {
|
||||||
|
createEmptyCard,
|
||||||
|
fsrs,
|
||||||
|
Rating,
|
||||||
|
State,
|
||||||
|
type Card,
|
||||||
|
type Grade,
|
||||||
|
} from "ts-fsrs";
|
||||||
|
|
||||||
|
export const SPACED_REPETITION_RATINGS = [
|
||||||
|
"AGAIN",
|
||||||
|
"HARD",
|
||||||
|
"GOOD",
|
||||||
|
"EASY",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export type SpacedRepetitionRating =
|
||||||
|
(typeof SPACED_REPETITION_RATINGS)[number];
|
||||||
|
|
||||||
|
export interface StoredScheduleState {
|
||||||
|
due: Date;
|
||||||
|
stability: number;
|
||||||
|
difficulty: number;
|
||||||
|
elapsedDays: number;
|
||||||
|
scheduledDays: number;
|
||||||
|
learningSteps: number;
|
||||||
|
reps: number;
|
||||||
|
lapses: number;
|
||||||
|
state: number;
|
||||||
|
lastReview: Date | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RatingPreview {
|
||||||
|
rating: SpacedRepetitionRating;
|
||||||
|
due: string;
|
||||||
|
scheduledDays: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LEARN_AHEAD_LIMIT_MINUTES = 20;
|
||||||
|
export const INTRADAY_LEARNING_STATES: number[] = [
|
||||||
|
State.Learning,
|
||||||
|
State.Relearning,
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getLearnAheadCutoff(now: Date) {
|
||||||
|
return new Date(now.getTime() + LEARN_AHEAD_LIMIT_MINUTES * 60 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEffectiveDue(state: { due: Date; scheduledDays: number }) {
|
||||||
|
return state.scheduledDays >= 1
|
||||||
|
? getArizonaDayBounds(state.due).start
|
||||||
|
: state.due;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function calculateQueueCounts(input: {
|
||||||
|
totalCards: number;
|
||||||
|
reviewedCards: number;
|
||||||
|
dueCards: number;
|
||||||
|
learningAheadCards?: number;
|
||||||
|
introducedToday: number;
|
||||||
|
newCardsPerDay: number;
|
||||||
|
}) {
|
||||||
|
const unseenCards = Math.max(0, input.totalCards - input.reviewedCards);
|
||||||
|
const newCardsAvailable = Math.min(
|
||||||
|
unseenCards,
|
||||||
|
Math.max(0, input.newCardsPerDay - input.introducedToday)
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
unseenCards,
|
||||||
|
newCardsAvailable,
|
||||||
|
learningAheadCards: input.learningAheadCards ?? 0,
|
||||||
|
availableCards:
|
||||||
|
input.dueCards + newCardsAvailable + (input.learningAheadCards ?? 0),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduler = fsrs({ request_retention: 0.9, enable_fuzz: false });
|
||||||
|
|
||||||
|
const ratingMap: Record<SpacedRepetitionRating, Grade> = {
|
||||||
|
AGAIN: Rating.Again,
|
||||||
|
HARD: Rating.Hard,
|
||||||
|
GOOD: Rating.Good,
|
||||||
|
EASY: Rating.Easy,
|
||||||
|
};
|
||||||
|
|
||||||
|
function toCard(state: StoredScheduleState | null, now: Date): Card {
|
||||||
|
if (!state) return createEmptyCard(now);
|
||||||
|
|
||||||
|
return {
|
||||||
|
due: state.due,
|
||||||
|
stability: state.stability,
|
||||||
|
difficulty: state.difficulty,
|
||||||
|
elapsed_days: state.elapsedDays,
|
||||||
|
scheduled_days: state.scheduledDays,
|
||||||
|
learning_steps: state.learningSteps,
|
||||||
|
reps: state.reps,
|
||||||
|
lapses: state.lapses,
|
||||||
|
state: state.state as State,
|
||||||
|
last_review: state.lastReview ?? undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function fromCard(card: Card): StoredScheduleState {
|
||||||
|
return {
|
||||||
|
due: card.due,
|
||||||
|
stability: card.stability,
|
||||||
|
difficulty: card.difficulty,
|
||||||
|
elapsedDays: card.elapsed_days,
|
||||||
|
scheduledDays: card.scheduled_days,
|
||||||
|
learningSteps: card.learning_steps,
|
||||||
|
reps: card.reps,
|
||||||
|
lapses: card.lapses,
|
||||||
|
state: card.state,
|
||||||
|
lastReview: card.last_review ?? null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function alignDayIntervalToArizonaMidnight(card: Card, now: Date): Card {
|
||||||
|
if (card.scheduled_days < 1) return card;
|
||||||
|
|
||||||
|
const { start } = getArizonaDayBounds(now);
|
||||||
|
return {
|
||||||
|
...card,
|
||||||
|
due: new Date(
|
||||||
|
start.getTime() + card.scheduled_days * 24 * 60 * 60 * 1000
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function previewRatings(
|
||||||
|
state: StoredScheduleState | null,
|
||||||
|
now: Date
|
||||||
|
): RatingPreview[] {
|
||||||
|
const preview = scheduler.repeat(toCard(state, now), now);
|
||||||
|
return SPACED_REPETITION_RATINGS.map((rating) => {
|
||||||
|
const card = alignDayIntervalToArizonaMidnight(
|
||||||
|
preview[ratingMap[rating]].card,
|
||||||
|
now
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
rating,
|
||||||
|
due: card.due.toISOString(),
|
||||||
|
scheduledDays: card.scheduled_days,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function scheduleRating(
|
||||||
|
state: StoredScheduleState | null,
|
||||||
|
rating: SpacedRepetitionRating,
|
||||||
|
now: Date
|
||||||
|
): StoredScheduleState {
|
||||||
|
const next = scheduler.next(toCard(state, now), now, ratingMap[rating]).card;
|
||||||
|
return fromCard(alignDayIntervalToArizonaMidnight(next, now));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getArizonaDayBounds(now: Date): { start: Date; end: Date } {
|
||||||
|
const arizonaNow = new Date(now.getTime() - 7 * 60 * 60 * 1000);
|
||||||
|
const dateKey = arizonaNow.toISOString().slice(0, 10);
|
||||||
|
const start = new Date(`${dateKey}T07:00:00.000Z`);
|
||||||
|
return { start, end: new Date(start.getTime() + 24 * 60 * 60 * 1000) };
|
||||||
|
}
|
||||||
9
src/lib/spacedRepetitionApi.ts
Normal file
9
src/lib/spacedRepetitionApi.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { NextResponse } from "next/server";
|
||||||
|
import { SpacedRepetitionServiceError } from "@/services/spacedRepetitionService";
|
||||||
|
|
||||||
|
export function spacedRepetitionErrorResponse(error: unknown) {
|
||||||
|
if (error instanceof SpacedRepetitionServiceError) {
|
||||||
|
return NextResponse.json({ error: error.message }, { status: error.status });
|
||||||
|
}
|
||||||
|
return NextResponse.json({ error: "Spaced repetition request failed" }, { status: 500 });
|
||||||
|
}
|
||||||
44
src/lib/validation/spacedRepetitionSchemas.ts
Normal file
44
src/lib/validation/spacedRepetitionSchemas.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import { z } from "zod";
|
||||||
|
import { SPACED_REPETITION_RATINGS } from "@/lib/spacedRepetition";
|
||||||
|
|
||||||
|
const nameSchema = z.string().trim().min(1).max(120);
|
||||||
|
const descriptionSchema = z.string().trim().max(5000).nullable().optional();
|
||||||
|
const newCardsPerDaySchema = z.number().int().min(0).max(999);
|
||||||
|
|
||||||
|
export const createSpacedRepetitionSetSchema = z.object({
|
||||||
|
classId: z.string().min(1),
|
||||||
|
name: nameSchema,
|
||||||
|
description: descriptionSchema,
|
||||||
|
newCardsPerDay: newCardsPerDaySchema.default(30),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateSpacedRepetitionSetSchema = z
|
||||||
|
.object({
|
||||||
|
name: nameSchema.optional(),
|
||||||
|
description: descriptionSchema,
|
||||||
|
newCardsPerDay: newCardsPerDaySchema.optional(),
|
||||||
|
})
|
||||||
|
.refine((value) => Object.values(value).some((item) => item !== undefined), {
|
||||||
|
message: "At least one field is required",
|
||||||
|
});
|
||||||
|
|
||||||
|
export const addSpacedRepetitionDeckSchema = z.object({
|
||||||
|
deckId: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const reorderSpacedRepetitionDecksSchema = z.object({
|
||||||
|
deckIds: z.array(z.string().min(1)).max(1000),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const reviewSpacedRepetitionCardSchema = z.object({
|
||||||
|
flashcardId: z.string().min(1),
|
||||||
|
rating: z.enum(SPACED_REPETITION_RATINGS),
|
||||||
|
expectedStateVersion: z.string().datetime().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type CreateSpacedRepetitionSetInput = z.infer<
|
||||||
|
typeof createSpacedRepetitionSetSchema
|
||||||
|
>;
|
||||||
|
export type UpdateSpacedRepetitionSetInput = z.infer<
|
||||||
|
typeof updateSpacedRepetitionSetSchema
|
||||||
|
>;
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { prisma } from "@/lib/db";
|
import { prisma } from "@/lib/db";
|
||||||
|
import { getStudyAvailabilityByClass } from "@/services/spacedRepetitionService";
|
||||||
|
|
||||||
function slugify(name: string): string {
|
function slugify(name: string): string {
|
||||||
return name
|
return name
|
||||||
|
|
@ -8,13 +9,27 @@ function slugify(name: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listClasses() {
|
export async function listClasses() {
|
||||||
return prisma.class.findMany({
|
const [classes, studyAvailability] = await Promise.all([
|
||||||
orderBy: { sortOrder: "asc" },
|
prisma.class.findMany({
|
||||||
include: {
|
orderBy: { sortOrder: "asc" },
|
||||||
_count: {
|
include: {
|
||||||
select: { decks: true, quizSets: true },
|
_count: {
|
||||||
|
select: { decks: true, quizSets: true },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
|
getStudyAvailabilityByClass(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return classes.map((classItem) => {
|
||||||
|
const availability = studyAvailability[classItem.id];
|
||||||
|
return {
|
||||||
|
...classItem,
|
||||||
|
spacedRepetitionDueCards: availability?.dueCards ?? 0,
|
||||||
|
spacedRepetitionLearningCards: availability?.learningCards ?? 0,
|
||||||
|
spacedRepetitionNewCards: availability?.newCards ?? 0,
|
||||||
|
spacedRepetitionReadyCards: availability?.readyCards ?? 0,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
448
src/services/spacedRepetitionService.ts
Normal file
448
src/services/spacedRepetitionService.ts
Normal file
|
|
@ -0,0 +1,448 @@
|
||||||
|
import { prisma } from "@/lib/db";
|
||||||
|
import {
|
||||||
|
calculateQueueCounts,
|
||||||
|
getArizonaDayBounds,
|
||||||
|
getEffectiveDue,
|
||||||
|
getLearnAheadCutoff,
|
||||||
|
INTRADAY_LEARNING_STATES,
|
||||||
|
previewRatings,
|
||||||
|
scheduleRating,
|
||||||
|
type SpacedRepetitionRating,
|
||||||
|
type StoredScheduleState,
|
||||||
|
} from "@/lib/spacedRepetition";
|
||||||
|
import type {
|
||||||
|
CreateSpacedRepetitionSetInput,
|
||||||
|
UpdateSpacedRepetitionSetInput,
|
||||||
|
} from "@/lib/validation/spacedRepetitionSchemas";
|
||||||
|
|
||||||
|
export class SpacedRepetitionServiceError extends Error {
|
||||||
|
constructor(message: string, public status: 400 | 404 | 409) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cardStateSelect = {
|
||||||
|
id: true,
|
||||||
|
due: true,
|
||||||
|
stability: true,
|
||||||
|
difficulty: true,
|
||||||
|
elapsedDays: true,
|
||||||
|
scheduledDays: true,
|
||||||
|
learningSteps: true,
|
||||||
|
reps: true,
|
||||||
|
lapses: true,
|
||||||
|
state: true,
|
||||||
|
lastReview: true,
|
||||||
|
updatedAt: true,
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
function asStoredState(state: {
|
||||||
|
due: Date;
|
||||||
|
stability: number;
|
||||||
|
difficulty: number;
|
||||||
|
elapsedDays: number;
|
||||||
|
scheduledDays: number;
|
||||||
|
learningSteps: number;
|
||||||
|
reps: number;
|
||||||
|
lapses: number;
|
||||||
|
state: number;
|
||||||
|
lastReview: Date | null;
|
||||||
|
}): StoredScheduleState {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSetSummary(setId: string, now: Date) {
|
||||||
|
const { start, end } = getArizonaDayBounds(now);
|
||||||
|
const set = await prisma.spacedRepetitionSet.findUnique({
|
||||||
|
where: { id: setId },
|
||||||
|
include: {
|
||||||
|
class: { select: { id: true, slug: true, name: true } },
|
||||||
|
decks: {
|
||||||
|
orderBy: { sortOrder: "asc" },
|
||||||
|
include: {
|
||||||
|
deck: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
description: true,
|
||||||
|
_count: { select: { cards: true } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cardStates: {
|
||||||
|
select: {
|
||||||
|
due: true,
|
||||||
|
firstReviewedAt: true,
|
||||||
|
scheduledDays: true,
|
||||||
|
state: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!set) throw new SpacedRepetitionServiceError("Repetition set not found", 404);
|
||||||
|
|
||||||
|
const totalCards = set.decks.reduce(
|
||||||
|
(sum, membership) => sum + membership.deck._count.cards,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const reviewedCards = set.cardStates.length;
|
||||||
|
const dueCards = set.cardStates.filter(
|
||||||
|
(state) => getEffectiveDue(state) <= now
|
||||||
|
).length;
|
||||||
|
const learnAheadCutoff = getLearnAheadCutoff(now);
|
||||||
|
const learningAheadCards = set.cardStates.filter(
|
||||||
|
(state) =>
|
||||||
|
getEffectiveDue(state) > now &&
|
||||||
|
getEffectiveDue(state) <= learnAheadCutoff &&
|
||||||
|
INTRADAY_LEARNING_STATES.includes(state.state)
|
||||||
|
).length;
|
||||||
|
const introducedToday = set.cardStates.filter(
|
||||||
|
(state) => state.firstReviewedAt >= start && state.firstReviewedAt < end
|
||||||
|
).length;
|
||||||
|
const { unseenCards, newCardsAvailable, availableCards } = calculateQueueCounts({
|
||||||
|
totalCards,
|
||||||
|
reviewedCards,
|
||||||
|
dueCards,
|
||||||
|
learningAheadCards,
|
||||||
|
introducedToday,
|
||||||
|
newCardsPerDay: set.newCardsPerDay,
|
||||||
|
});
|
||||||
|
const nextDue = set.cardStates
|
||||||
|
.map((state) => getEffectiveDue(state))
|
||||||
|
.filter((due) => due > now)
|
||||||
|
.sort((a, b) => a.getTime() - b.getTime())[0];
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: set.id,
|
||||||
|
classId: set.classId,
|
||||||
|
name: set.name,
|
||||||
|
description: set.description,
|
||||||
|
newCardsPerDay: set.newCardsPerDay,
|
||||||
|
sortOrder: set.sortOrder,
|
||||||
|
createdAt: set.createdAt,
|
||||||
|
updatedAt: set.updatedAt,
|
||||||
|
class: set.class,
|
||||||
|
decks: set.decks.map((membership) => ({
|
||||||
|
...membership.deck,
|
||||||
|
sortOrder: membership.sortOrder,
|
||||||
|
})),
|
||||||
|
stats: {
|
||||||
|
totalCards,
|
||||||
|
reviewedCards,
|
||||||
|
unseenCards,
|
||||||
|
dueCards,
|
||||||
|
learningAheadCards,
|
||||||
|
newCardsAvailable,
|
||||||
|
availableCards,
|
||||||
|
nextDue: nextDue?.toISOString() ?? null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listSetsByClass(classId: string, now = new Date()) {
|
||||||
|
const ids = await prisma.spacedRepetitionSet.findMany({
|
||||||
|
where: { classId },
|
||||||
|
orderBy: { sortOrder: "asc" },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
return Promise.all(ids.map(({ id }) => getSetSummary(id, now)));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStudyAvailabilityByClass(now = new Date()) {
|
||||||
|
const { start, end } = getArizonaDayBounds(now);
|
||||||
|
const learnAheadCutoff = getLearnAheadCutoff(now);
|
||||||
|
const sets = await prisma.spacedRepetitionSet.findMany({
|
||||||
|
select: {
|
||||||
|
classId: true,
|
||||||
|
newCardsPerDay: true,
|
||||||
|
decks: {
|
||||||
|
select: {
|
||||||
|
deck: { select: { _count: { select: { cards: true } } } },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cardStates: {
|
||||||
|
select: {
|
||||||
|
due: true,
|
||||||
|
firstReviewedAt: true,
|
||||||
|
scheduledDays: true,
|
||||||
|
state: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return sets.reduce<Record<string, {
|
||||||
|
dueCards: number;
|
||||||
|
learningCards: number;
|
||||||
|
newCards: number;
|
||||||
|
readyCards: number;
|
||||||
|
}>>((availability, set) => {
|
||||||
|
const totalCards = set.decks.reduce(
|
||||||
|
(sum, membership) => sum + membership.deck._count.cards,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const dueCards = set.cardStates.filter(
|
||||||
|
(state) => getEffectiveDue(state) <= now
|
||||||
|
).length;
|
||||||
|
const learningCards = set.cardStates.filter((state) => {
|
||||||
|
const effectiveDue = getEffectiveDue(state);
|
||||||
|
return effectiveDue > now &&
|
||||||
|
effectiveDue <= learnAheadCutoff &&
|
||||||
|
INTRADAY_LEARNING_STATES.includes(state.state);
|
||||||
|
}).length;
|
||||||
|
const introducedToday = set.cardStates.filter(
|
||||||
|
(state) => state.firstReviewedAt >= start && state.firstReviewedAt < end
|
||||||
|
).length;
|
||||||
|
const counts = calculateQueueCounts({
|
||||||
|
totalCards,
|
||||||
|
reviewedCards: set.cardStates.length,
|
||||||
|
dueCards,
|
||||||
|
learningAheadCards: learningCards,
|
||||||
|
introducedToday,
|
||||||
|
newCardsPerDay: set.newCardsPerDay,
|
||||||
|
});
|
||||||
|
const current = availability[set.classId] ?? {
|
||||||
|
dueCards: 0,
|
||||||
|
learningCards: 0,
|
||||||
|
newCards: 0,
|
||||||
|
readyCards: 0,
|
||||||
|
};
|
||||||
|
availability[set.classId] = {
|
||||||
|
dueCards: current.dueCards + dueCards,
|
||||||
|
learningCards: current.learningCards + learningCards,
|
||||||
|
newCards: current.newCards + counts.newCardsAvailable,
|
||||||
|
readyCards: current.readyCards + counts.availableCards,
|
||||||
|
};
|
||||||
|
return availability;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createSet(data: CreateSpacedRepetitionSetInput) {
|
||||||
|
const classExists = await prisma.class.findUnique({
|
||||||
|
where: { id: data.classId },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
if (!classExists) throw new SpacedRepetitionServiceError("Class not found", 404);
|
||||||
|
|
||||||
|
const maximum = await prisma.spacedRepetitionSet.aggregate({
|
||||||
|
where: { classId: data.classId },
|
||||||
|
_max: { sortOrder: true },
|
||||||
|
});
|
||||||
|
return prisma.spacedRepetitionSet.create({
|
||||||
|
data: {
|
||||||
|
classId: data.classId,
|
||||||
|
name: data.name,
|
||||||
|
description: data.description || null,
|
||||||
|
newCardsPerDay: data.newCardsPerDay,
|
||||||
|
sortOrder: (maximum._max.sortOrder ?? -1) + 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSet(id: string, now = new Date()) {
|
||||||
|
return getSetSummary(id, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateSet(id: string, data: UpdateSpacedRepetitionSetInput) {
|
||||||
|
const exists = await prisma.spacedRepetitionSet.findUnique({ where: { id } });
|
||||||
|
if (!exists) throw new SpacedRepetitionServiceError("Repetition set not found", 404);
|
||||||
|
return prisma.spacedRepetitionSet.update({
|
||||||
|
where: { id },
|
||||||
|
data: {
|
||||||
|
name: data.name,
|
||||||
|
description: data.description === undefined ? undefined : data.description || null,
|
||||||
|
newCardsPerDay: data.newCardsPerDay,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteSet(id: string) {
|
||||||
|
const result = await prisma.spacedRepetitionSet.deleteMany({ where: { id } });
|
||||||
|
if (!result.count) throw new SpacedRepetitionServiceError("Repetition set not found", 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addDeck(setId: string, deckId: string) {
|
||||||
|
const [set, deck, existing] = await Promise.all([
|
||||||
|
prisma.spacedRepetitionSet.findUnique({ where: { id: setId } }),
|
||||||
|
prisma.deck.findUnique({ where: { id: deckId } }),
|
||||||
|
prisma.spacedRepetitionSetDeck.findUnique({
|
||||||
|
where: { setId_deckId: { setId, deckId } },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
if (!set) throw new SpacedRepetitionServiceError("Repetition set not found", 404);
|
||||||
|
if (!deck) throw new SpacedRepetitionServiceError("Deck not found", 404);
|
||||||
|
if (set.classId !== deck.classId) {
|
||||||
|
throw new SpacedRepetitionServiceError("Deck belongs to another class", 400);
|
||||||
|
}
|
||||||
|
if (existing) throw new SpacedRepetitionServiceError("Deck is already in this set", 409);
|
||||||
|
|
||||||
|
const maximum = await prisma.spacedRepetitionSetDeck.aggregate({
|
||||||
|
where: { setId },
|
||||||
|
_max: { sortOrder: true },
|
||||||
|
});
|
||||||
|
return prisma.spacedRepetitionSetDeck.create({
|
||||||
|
data: { setId, deckId, sortOrder: (maximum._max.sortOrder ?? -1) + 1 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeDeck(setId: string, deckId: string) {
|
||||||
|
const membership = await prisma.spacedRepetitionSetDeck.findUnique({
|
||||||
|
where: { setId_deckId: { setId, deckId } },
|
||||||
|
});
|
||||||
|
if (!membership) throw new SpacedRepetitionServiceError("Deck membership not found", 404);
|
||||||
|
|
||||||
|
await prisma.$transaction([
|
||||||
|
prisma.spacedRepetitionCardState.deleteMany({
|
||||||
|
where: { setId, flashcard: { deckId } },
|
||||||
|
}),
|
||||||
|
prisma.spacedRepetitionSetDeck.delete({
|
||||||
|
where: { setId_deckId: { setId, deckId } },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function reorderDecks(setId: string, deckIds: string[]) {
|
||||||
|
if (new Set(deckIds).size !== deckIds.length) {
|
||||||
|
throw new SpacedRepetitionServiceError("Deck order contains duplicates", 400);
|
||||||
|
}
|
||||||
|
const memberships = await prisma.spacedRepetitionSetDeck.findMany({
|
||||||
|
where: { setId },
|
||||||
|
select: { deckId: true },
|
||||||
|
});
|
||||||
|
const existing = new Set(memberships.map((item) => item.deckId));
|
||||||
|
if (existing.size !== deckIds.length || deckIds.some((id) => !existing.has(id))) {
|
||||||
|
throw new SpacedRepetitionServiceError("Deck order must include every set deck", 400);
|
||||||
|
}
|
||||||
|
await prisma.$transaction(
|
||||||
|
deckIds.map((deckId, sortOrder) =>
|
||||||
|
prisma.spacedRepetitionSetDeck.update({
|
||||||
|
where: { setId_deckId: { setId, deckId } },
|
||||||
|
data: { sortOrder },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function findNextCard(setId: string, now: Date, allowNew: boolean) {
|
||||||
|
const { end } = getArizonaDayBounds(now);
|
||||||
|
const due = await prisma.spacedRepetitionCardState.findFirst({
|
||||||
|
where: {
|
||||||
|
setId,
|
||||||
|
OR: [
|
||||||
|
{ scheduledDays: 0, due: { lte: now } },
|
||||||
|
{ scheduledDays: { gte: 1 }, due: { lt: end } },
|
||||||
|
],
|
||||||
|
flashcard: { deck: { spacedRepetitionMemberships: { some: { setId } } } },
|
||||||
|
},
|
||||||
|
orderBy: { due: "asc" },
|
||||||
|
include: { flashcard: { include: { deck: { select: { name: true } } } } },
|
||||||
|
});
|
||||||
|
if (due) {
|
||||||
|
return {
|
||||||
|
flashcard: due.flashcard,
|
||||||
|
updatedAt: due.updatedAt,
|
||||||
|
state: asStoredState(due),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (allowNew) {
|
||||||
|
const memberships = await prisma.spacedRepetitionSetDeck.findMany({
|
||||||
|
where: { setId },
|
||||||
|
orderBy: { sortOrder: "asc" },
|
||||||
|
select: { deckId: true },
|
||||||
|
});
|
||||||
|
for (const membership of memberships) {
|
||||||
|
const card = await prisma.flashcard.findFirst({
|
||||||
|
where: {
|
||||||
|
deckId: membership.deckId,
|
||||||
|
spacedRepetitionStates: { none: { setId } },
|
||||||
|
},
|
||||||
|
orderBy: { sortOrder: "asc" },
|
||||||
|
include: { deck: { select: { name: true } } },
|
||||||
|
});
|
||||||
|
if (card) return { flashcard: card, updatedAt: null, state: null };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const learningAhead = await prisma.spacedRepetitionCardState.findFirst({
|
||||||
|
where: {
|
||||||
|
setId,
|
||||||
|
state: { in: INTRADAY_LEARNING_STATES },
|
||||||
|
due: { gt: now, lte: getLearnAheadCutoff(now) },
|
||||||
|
flashcard: { deck: { spacedRepetitionMemberships: { some: { setId } } } },
|
||||||
|
},
|
||||||
|
orderBy: { due: "asc" },
|
||||||
|
include: { flashcard: { include: { deck: { select: { name: true } } } } },
|
||||||
|
});
|
||||||
|
if (learningAhead) {
|
||||||
|
return {
|
||||||
|
flashcard: learningAhead.flashcard,
|
||||||
|
updatedAt: learningAhead.updatedAt,
|
||||||
|
state: asStoredState(learningAhead),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStudyState(setId: string, now = new Date()) {
|
||||||
|
const summary = await getSetSummary(setId, now);
|
||||||
|
const next = await findNextCard(setId, now, summary.stats.newCardsAvailable > 0);
|
||||||
|
return {
|
||||||
|
set: summary,
|
||||||
|
currentCard: next
|
||||||
|
? {
|
||||||
|
id: next.flashcard.id,
|
||||||
|
front: next.flashcard.front,
|
||||||
|
back: next.flashcard.back,
|
||||||
|
sourceDeckName: next.flashcard.deck.name,
|
||||||
|
stateVersion: next.updatedAt?.toISOString() ?? null,
|
||||||
|
previews: previewRatings(next.state, now),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function reviewCard(data: {
|
||||||
|
setId: string;
|
||||||
|
flashcardId: string;
|
||||||
|
rating: SpacedRepetitionRating;
|
||||||
|
expectedStateVersion: string | null;
|
||||||
|
now?: Date;
|
||||||
|
}) {
|
||||||
|
const now = data.now ?? new Date();
|
||||||
|
const card = await prisma.flashcard.findFirst({
|
||||||
|
where: {
|
||||||
|
id: data.flashcardId,
|
||||||
|
deck: { spacedRepetitionMemberships: { some: { setId: data.setId } } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!card) throw new SpacedRepetitionServiceError("Card is not in this repetition set", 404);
|
||||||
|
|
||||||
|
const current = await prisma.spacedRepetitionCardState.findUnique({
|
||||||
|
where: { setId_flashcardId: { setId: data.setId, flashcardId: data.flashcardId } },
|
||||||
|
select: { ...cardStateSelect, firstReviewedAt: true },
|
||||||
|
});
|
||||||
|
const currentVersion = current?.updatedAt.toISOString() ?? null;
|
||||||
|
if (currentVersion !== data.expectedStateVersion) {
|
||||||
|
throw new SpacedRepetitionServiceError("This card was already reviewed. Refreshing the queue is required.", 409);
|
||||||
|
}
|
||||||
|
|
||||||
|
const next = scheduleRating(current ? asStoredState(current) : null, data.rating, now);
|
||||||
|
await prisma.$transaction(async (tx) => {
|
||||||
|
if (current) {
|
||||||
|
const updated = await tx.spacedRepetitionCardState.updateMany({
|
||||||
|
where: { id: current.id, updatedAt: current.updatedAt },
|
||||||
|
data: next,
|
||||||
|
});
|
||||||
|
if (!updated.count) throw new SpacedRepetitionServiceError("Card state changed", 409);
|
||||||
|
} else {
|
||||||
|
await tx.spacedRepetitionCardState.create({
|
||||||
|
data: { setId: data.setId, flashcardId: data.flashcardId, ...next, firstReviewedAt: now },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await tx.studyActivity.create({ data: { type: "FLASHCARD", occurredAt: now } });
|
||||||
|
});
|
||||||
|
|
||||||
|
return getStudyState(data.setId, now);
|
||||||
|
}
|
||||||
13
vitest.config.ts
Normal file
13
vitest.config.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
test: {
|
||||||
|
environment: "node",
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue