From 631dfa592399f04cd5c5e771f15d7246c10bc56c Mon Sep 17 00:00:00 2001 From: Elijah Date: Sat, 11 Jul 2026 13:48:02 -0700 Subject: [PATCH 01/13] New: Ui Redesign --- dev.db | Bin 155648 -> 155648 bytes .../[classSlug]/flashcards/[deckId]/page.tsx | 9 +- .../[classSlug]/flashcards/page.tsx | 30 +- src/app/(protected)/[classSlug]/layout.tsx | 2 +- .../[classSlug]/quizzes/[quizId]/page.tsx | 8 +- .../(protected)/[classSlug]/quizzes/page.tsx | 30 +- src/app/(protected)/layout.tsx | 4 +- src/app/(protected)/page.tsx | 298 +++++------------ src/app/globals.css | 300 +++++++++--------- src/app/layout.tsx | 37 ++- src/app/login/page.tsx | 40 +-- .../[classSlug]/[type]/[token]/page.tsx | 19 +- src/components/flashcards/CardManager.tsx | 6 +- src/components/flashcards/FlashcardViewer.tsx | 20 +- src/components/import/ImportModal.tsx | 7 +- src/components/quizzes/QuizResults.tsx | 6 +- src/components/quizzes/QuizViewer.tsx | 12 +- src/components/ui/ClassHeader.tsx | 12 +- src/components/ui/ClassTabs.tsx | 8 +- src/components/ui/Navbar.tsx | 117 +++++-- src/components/ui/ShareMenu.tsx | 17 +- src/components/ui/ThemeToggle.tsx | 6 +- 22 files changed, 464 insertions(+), 524 deletions(-) diff --git a/dev.db b/dev.db index 2d75c1547249665ea4693d62e468bb341c790859..a0c488e18c0c9b57e5f24fc9a6a47493f96bdd44 100644 GIT binary patch delta 1012 zcmcgrT}TvB6ux(Mc5`>#y}PthlK8U>p|ztkb7yvT7o=1~Z2i}@MKhgse-zPm)l>}8 zD3l-}orggpNPLJOknmx^d|2o;Sn@^tA)k5()S!sSerUEe=pm@5I1lIYalUiz`R@H@ zlg8Pk@%Aa`Sj?+}jz>K!?~v&;AhMFc8q7+XS^e_5-F8#IzWlLAPXeIpmcJeHo;ixN z1ad&22wu;b_k$XqbxRm?VY(MHEX@lZj`lF3M29@l2rGspPKpZ6f!Yq)zDZeHtD6JW z4Q;g*b!4YgmQs|?>2k{Zk2AUKbtb<$!VjKPq_gYl&qC7a(-`#2G?JlYklceKL?dUA z%jrY9Mr)&-$%igwEDsjT1urtqpY2gfG7swIuY1lyXV)~xaBi9rX_jr{7$1*)3@-J0 z@mYrPG0b}K`vZv38X(XdKyzp(9hxfjXz3QINC-BR002t5YGFvYCKUK@rr^-yTbxc^ARpYdqN^lM?4`Z%0zM0E2GWR zB%%NB!U&~quQwx%ns7kboNYj6ph>c(Nn=*6h4712)1~C^8$?(`u`U3WX$Pd#OJ#7O z$^PpFF!r%xDK9YU;S4m{Ogf?n7)OaaM3E-dabjjtN8)RZQh2a`sC!r%9i51eguDBs z(LPEViN%Mw{xCIO&U&fAa&1idMC0GSXvGvtQa!%F|t#o9X@8Z2+6C Vo~t9ap>n>Ccn=xX*hqY`{Q$}xBwhdj delta 607 zcmZoTz}awsbAmJ@%S0JxMwX2U3*YnesWC8f9AMy!;QGmNfOY0(L4i)z%`?9yu(Gsi zG4W3R`^#i2AW2>|UtQv$RNRSD2q zG$lX>OuqO_Qr_6y%D_<1+|)wbz`)ADfPsO*C{#3*L6)&ul%dJgefk}KMy1UW|5h^c z1ErYx+Zg!Q^SANe;J*P34PX9AJuK4``5FCKG?+sfCv*N*VPu+qgP&1{kCmm6fpnqFh9-I@#wOc;)iZJla4<2=X5cJlnhi9qpK1H-NsO7i98CO|82I1vU)n6F zaEyQZrS**Z0uYC0Gw{#l&*neRe;%mZn}2%rc}6D|LzYn4=?l*@%1!=I!OjVbH>T<5 z&odf9v|VT5|IGiC|2qF-pox?DS&dmj6&WXA{Hj0w`h7+|7G4$x+3hdyGs-2fGw@F2 u=j0Ownr_Im-QI`kIwNNxGiPY1W1}eJba_9f6~LhQ=)>f+oy(u;n*abL`LF!| diff --git a/src/app/(protected)/[classSlug]/flashcards/[deckId]/page.tsx b/src/app/(protected)/[classSlug]/flashcards/[deckId]/page.tsx index 39cae8a..726de4e 100644 --- a/src/app/(protected)/[classSlug]/flashcards/[deckId]/page.tsx +++ b/src/app/(protected)/[classSlug]/flashcards/[deckId]/page.tsx @@ -90,9 +90,9 @@ export default function DeckStudyPage() { } return ( -
+
{/* Header */} -
+
-

{deck.name}

+

Flashcard session

+

{deck.name}

{deck.description && (

{deck.description}

)} @@ -126,7 +127,7 @@ export default function DeckStudyPage() {
-
+
-
{isCreatingGroup && ( -
+
-
-

Edit Deck

+
+

Edit deck

{groupedDecks.map(group => ( -
+
{editingGroupId === group.id ? (
@@ -456,7 +456,7 @@ export default function FlashcardsPage() { -

{group.name}

+

{group.name}

)} @@ -484,7 +484,7 @@ export default function FlashcardsPage() { ))} {/* Uncategorized */} -
+
-
{isCreatingGroup && ( -
+
-
-

Edit Quiz

+
+

Edit quiz

{groupedQuizzes.map(group => ( -
+
{editingGroupId === group.id ? (
@@ -431,7 +431,7 @@ export default function QuizzesPage() { -

{group.name}

+

{group.name}

)} @@ -459,7 +459,7 @@ export default function QuizzesPage() { ))} {/* Uncategorized */} -
+
- -
- - {/* Create class inline form */} - {showCreate && ( -
-
- setNewName(e.target.value)} - placeholder="e.g. PHA 109, Anatomy, Nursing Fundamentals..." - autoFocus - className="flex-1 px-3.5 py-2.5 rounded-lg border border-border bg-bg-surface-alt/50 text-text-heading placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-primary/30 focus:border-primary transition-all duration-200" - /> - - -
-
- )} - - {/* Loading state */} - {loading && ( -
- {[1, 2, 3].map((i) => ( -
-
-
+
+ {[[classes.length, "Classes"], [deckTotal, "Decks"], [quizTotal, "Quizzes"]].map(([value, label]) => ( +
+
{value}
+
{label}
))}
+ + + {showCreate && ( +
+
+

Create a class

+

Give this study space a short, memorable name.

+
+
+ setNewName(e.target.value)} placeholder="e.g. Pathophysiology" autoFocus className="min-h-12 flex-1 rounded-xl border border-border bg-bg-surface-alt/60 px-4 text-text-heading placeholder:text-text-muted focus:border-primary" /> + + +
+
)} - {/* Empty state */} - {!loading && classes.length === 0 && ( -
-
- - - -
-

- No classes yet -

-

- Create your first class to start studying -

- +
+
+

Study spaces

+

Your classes

+
+

Flashcards and quizzes, organized by class.

+
+ + {loading && ( +
+ {[1, 2, 3].map((i) =>
)} +
+ )} + + {!loading && classes.length === 0 && ( +
+
+
+

Your desk is ready

+

Create your first class, then add flashcards or practice quizzes.

+
)} - {/* Class grid */} {!loading && classes.length > 0 && ( -
- {classes.map((cls) => ( -
- +
+ {classes.map((cls, index) => ( +
+
+ +
+
{cls.name.slice(0, 2).toUpperCase()}
+ +
{editingId === cls.id ? ( - { - e.preventDefault(); - e.stopPropagation(); - }} - onChange={(e) => setEditName(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") { - e.preventDefault(); - e.stopPropagation(); - handleSaveEdit(cls.id); - } else if (e.key === "Escape") { - e.preventDefault(); - e.stopPropagation(); - setEditingId(null); - } - }} - onBlur={() => handleSaveEdit(cls.id)} - autoFocus - disabled={savingEdit} - className="w-[calc(100%-2.5rem)] text-lg font-semibold text-text-heading px-2 py-1 -ml-2 rounded bg-bg-surface-alt border border-primary/30 focus:outline-none focus:ring-1 focus:ring-primary disabled:opacity-50" - /> - ) : ( -

- {cls.name} -

- )} -
- - - - - {cls._count.decks} {cls._count.decks === 1 ? "deck" : "decks"} - - - - - - {cls._count.quizSets} {cls._count.quizSets === 1 ? "quiz" : "quizzes"} - + { e.preventDefault(); e.stopPropagation(); }} onChange={(e) => setEditName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); handleSaveEdit(cls.id); } else if (e.key === "Escape") setEditingId(null); }} onBlur={() => handleSaveEdit(cls.id)} autoFocus disabled={savingEdit} className="w-full rounded-lg border border-primary bg-bg-surface-alt px-2 py-1 text-lg font-bold text-text-heading" /> + ) :

{cls.name}

} +
+ {cls._count.decks} {cls._count.decks === 1 ? "deck" : "decks"} + {cls._count.quizSets} {cls._count.quizSets === 1 ? "quiz" : "quizzes"}
- - {/* Actions */} -
- {/* Delete button */} - - {/* Edit button */} - +
+ +
-
+
))}
)} diff --git a/src/app/globals.css b/src/app/globals.css index cfd5955..53aab02 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,64 +2,104 @@ @layer base { :root { - --theme-bg-base: #f0f4f2; - --theme-bg-surface: #ffffff; - --theme-bg-surface-alt: #f4f7f5; - --theme-bg-callout: #e8f4f0; - --theme-primary: #2a7d7d; - --theme-primary-hover: #1f6363; - --theme-primary-light: #3a9e9e; - --theme-text-heading: #1a2f2f; - --theme-text-body: #2d4a4a; - --theme-text-secondary: #4a6a6a; - --theme-text-muted: #7a9a9a; - --theme-border: #c8d8d0; - --theme-border-light: #dce8e2; - --theme-success: #2d8a4e; - --theme-success-bg: #e8f5ed; - --theme-error: #c44c4c; - --theme-error-bg: #fce8e8; - --theme-badge-bg: #e0eeea; - --theme-badge-text: #2a6a5a; - --theme-danger: #dc3545; - --theme-danger-hover: #c82333; - - --theme-shadow-card: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04); - --theme-shadow-card-hover: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04); - --theme-shadow-modal: 0 20px 60px rgba(0, 0, 0, 0.15), 0 8px 20px rgba(0, 0, 0, 0.1); + --theme-bg-base: #f5f2ea; + --theme-bg-surface: #fffdf8; + --theme-bg-surface-alt: #eeeae0; + --theme-bg-callout: #ebe9ff; + --theme-primary: #4f46e5; + --theme-primary-hover: #3f37c9; + --theme-primary-light: #756ef0; + --theme-accent: #f97360; + --theme-text-heading: #172033; + --theme-text-body: #344054; + --theme-text-secondary: #667085; + --theme-text-muted: #8b91a0; + --theme-border: #d7d2c7; + --theme-border-light: #e7e2d8; + --theme-success: #25845f; + --theme-success-bg: #e3f4ec; + --theme-error: #c44747; + --theme-error-bg: #fae8e5; + --theme-badge-bg: #e8e5ff; + --theme-badge-text: #4840bb; + --theme-danger: #c44747; + --theme-danger-hover: #a93636; + --theme-shadow-card: 0 1px 2px rgba(23, 32, 51, 0.04), 0 8px 24px rgba(23, 32, 51, 0.055); + --theme-shadow-card-hover: 0 2px 4px rgba(23, 32, 51, 0.05), 0 18px 40px rgba(23, 32, 51, 0.1); + --theme-shadow-modal: 0 28px 80px rgba(23, 32, 51, 0.22); } :root.dark { - --theme-bg-base: #121212; - --theme-bg-surface: #1e1e1e; - --theme-bg-surface-alt: #2d2d2d; - --theme-bg-callout: #1a2723; - --theme-primary: #4ab2b2; - --theme-primary-hover: #5ec7c7; - --theme-primary-light: #6cd1d1; - --theme-text-heading: #f9fafb; - --theme-text-body: #e5e7eb; - --theme-text-secondary: #9ca3af; - --theme-text-muted: #6b7280; - --theme-border: #374151; - --theme-border-light: #2d3748; - --theme-success: #4ade80; - --theme-success-bg: #064e3b; - --theme-error: #f87171; - --theme-error-bg: #7f1d1d; - --theme-badge-bg: #1f2937; - --theme-badge-text: #5ec7c7; - --theme-danger: #ef4444; - --theme-danger-hover: #f87171; + --theme-bg-base: #0d1117; + --theme-bg-surface: #151b23; + --theme-bg-surface-alt: #1c2430; + --theme-bg-callout: #25234a; + --theme-primary: #8b83ff; + --theme-primary-hover: #a49eff; + --theme-primary-light: #b2adff; + --theme-accent: #ff8a75; + --theme-text-heading: #f6f3ec; + --theme-text-body: #d8dde6; + --theme-text-secondary: #aab3c0; + --theme-text-muted: #7f8998; + --theme-border: #354050; + --theme-border-light: #293342; + --theme-success: #58c596; + --theme-success-bg: #173c30; + --theme-error: #ff8177; + --theme-error-bg: #472424; + --theme-badge-bg: #2d2959; + --theme-badge-text: #bbb7ff; + --theme-danger: #ff8177; + --theme-danger-hover: #ffa098; + --theme-shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), 0 10px 30px rgba(0, 0, 0, 0.18); + --theme-shadow-card-hover: 0 2px 4px rgba(0, 0, 0, 0.36), 0 20px 44px rgba(0, 0, 0, 0.28); + --theme-shadow-modal: 0 30px 90px rgba(0, 0, 0, 0.55); + } - --theme-shadow-card: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2); - --theme-shadow-card-hover: 0 4px 12px rgba(0, 0, 0, 0.6), 0 2px 4px rgba(0, 0, 0, 0.4); - --theme-shadow-modal: 0 20px 60px rgba(0, 0, 0, 0.5), 0 8px 20px rgba(0, 0, 0, 0.3); + * { + border-color: var(--theme-border-light); + } + + html { + background: var(--theme-bg-base); + scroll-behavior: smooth; + } + + body { + min-width: 320px; + background: + radial-gradient(circle at 88% -10%, color-mix(in srgb, var(--theme-primary) 9%, transparent), transparent 32rem), + var(--theme-bg-base); + color: var(--theme-text-body); + font-family: var(--font-manrope), ui-sans-serif, system-ui, sans-serif; + -webkit-font-smoothing: antialiased; + text-rendering: optimizeLegibility; + } + + button, a, input, textarea, select { + outline-color: var(--theme-primary); + } + + :focus-visible { + outline: 3px solid color-mix(in srgb, var(--theme-primary) 45%, transparent); + outline-offset: 3px; + } + + button, [role="button"], a { + -webkit-tap-highlight-color: transparent; + } + + button:not(:disabled), [role="button"], a { + cursor: pointer; + } + + ::selection { + background: color-mix(in srgb, var(--theme-primary) 24%, transparent); } } @theme inline { - /* ── Sage / Teal palette mapped from CSS variables ── */ --color-bg-base: var(--theme-bg-base); --color-bg-surface: var(--theme-bg-surface); --color-bg-surface-alt: var(--theme-bg-surface-alt); @@ -67,6 +107,7 @@ --color-primary: var(--theme-primary); --color-primary-hover: var(--theme-primary-hover); --color-primary-light: var(--theme-primary-light); + --color-accent: var(--theme-accent); --color-text-heading: var(--theme-text-heading); --color-text-body: var(--theme-text-body); --color-text-secondary: var(--theme-text-secondary); @@ -81,123 +122,72 @@ --color-badge-text: var(--theme-badge-text); --color-danger: var(--theme-danger); --color-danger-hover: var(--theme-danger-hover); - - /* Typography */ - --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif; - - /* Shadows */ + --font-sans: var(--font-manrope), ui-sans-serif, system-ui, sans-serif; + --font-serif: var(--font-newsreader), ui-serif, Georgia, serif; --shadow-card: var(--theme-shadow-card); --shadow-card-hover: var(--theme-shadow-card-hover); --shadow-modal: var(--theme-shadow-modal); - - /* Animations */ - --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); + --ease-spring: cubic-bezier(0.22, 1, 0.36, 1); } -/* ── Base Styles ── */ -body { - background: var(--color-bg-base); - color: var(--color-text-body); - font-family: var(--font-sans); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; +::-webkit-scrollbar { width: 8px; height: 8px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--color-border); border: 2px solid transparent; border-radius: 999px; background-clip: padding-box; } + +.app-page { + width: 100%; + max-width: 1280px; + margin-inline: auto; + padding: 2rem clamp(1rem, 3vw, 3rem) 5rem; } -/* ── Scrollbar Styling ── */ -::-webkit-scrollbar { - width: 6px; -} -::-webkit-scrollbar-track { - background: transparent; -} -::-webkit-scrollbar-thumb { - background: var(--color-border); - border-radius: 3px; -} -::-webkit-scrollbar-thumb:hover { - background: var(--color-text-muted); +.editorial-title { + font-family: var(--font-newsreader), Georgia, serif; + font-weight: 600; + letter-spacing: -0.035em; } -/* ── Card flip animation ── */ -.perspective-1000 { - perspective: 1000px; +.paper-grid { + background-image: radial-gradient(color-mix(in srgb, var(--theme-text-muted) 18%, transparent) 0.7px, transparent 0.7px); + background-size: 18px 18px; } -.preserve-3d { - transform-style: preserve-3d; -} +.perspective-1000 { perspective: 1000px; } +.preserve-3d { transform-style: preserve-3d; } +.backface-hidden { backface-visibility: hidden; } +.rotate-y-180 { transform: rotateY(180deg); } +.rotate-x-180 { transform: rotateX(180deg); } -.backface-hidden { - backface-visibility: hidden; -} +@keyframes swipeLeft { to { transform: translateX(-44px) rotate(-6deg); opacity: 0; } } +@keyframes swipeRight { to { transform: translateX(44px) rotate(6deg); opacity: 0; } } +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } +@keyframes slideUp { from { opacity: 0; transform: translateY(14px) scale(.985); } to { opacity: 1; transform: translateY(0) scale(1); } } +@keyframes subtlePulse { 0%, 100% { opacity: 1; } 50% { opacity: .58; } } -.rotate-y-180 { - transform: rotateY(180deg); -} +.animate-swipe-left { animation: swipeLeft .36s var(--ease-spring) forwards; } +.animate-swipe-right { animation: swipeRight .36s var(--ease-spring) forwards; } +.animate-fade-in { animation: fadeIn .2s ease-out; } +.animate-slide-up { animation: slideUp .32s var(--ease-spring); } +.animate-subtle-pulse { animation: subtlePulse 1.7s ease-in-out infinite; } -.rotate-x-180 { - transform: rotateX(180deg); -} - -/* ── Swipe animations ── */ -@keyframes swipeLeft { - 0% { transform: translateX(0) rotate(0deg); opacity: 1; } - 100% { transform: translateX(-40px) rotate(-8deg); opacity: 0; } -} - -@keyframes swipeRight { - 0% { transform: translateX(0) rotate(0deg); opacity: 1; } - 100% { transform: translateX(40px) rotate(8deg); opacity: 0; } -} - -.animate-swipe-left { - animation: swipeLeft 0.4s var(--ease-spring) forwards; -} - -.animate-swipe-right { - animation: swipeRight 0.4s var(--ease-spring) forwards; -} - -/* ── Modal animation ── */ -@keyframes fadeIn { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes slideUp { - from { opacity: 0; transform: translateY(20px) scale(0.98); } - to { opacity: 1; transform: translateY(0) scale(1); } -} - -.animate-fade-in { - animation: fadeIn 0.2s ease-out; -} - -.animate-slide-up { - animation: slideUp 0.3s var(--ease-spring); -} - -/* ── Subtle pulse for loading states ── */ -@keyframes subtlePulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } -} - -.animate-subtle-pulse { - animation: subtlePulse 2s ease-in-out infinite; -} - -/* ── Markdown content styles ── */ -.markdown-content h1 { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.5rem; color: var(--color-text-heading); } -.markdown-content h2 { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--color-text-heading); } -.markdown-content h3 { font-size: 1.125rem; font-weight: 600; margin-bottom: 0.5rem; color: var(--color-text-heading); } -.markdown-content p { margin-bottom: 0.75rem; line-height: 1.7; } -.markdown-content ul { list-style: disc inside; padding-left: 0; margin-bottom: 0.75rem; text-align: inherit; } -.markdown-content ol { list-style: decimal inside; padding-left: 0; margin-bottom: 0.75rem; text-align: inherit; } -.markdown-content li { margin-bottom: 0.25rem; line-height: 1.6; } -.markdown-content strong { font-weight: 600; color: var(--color-text-heading); } -.markdown-content code { background: var(--color-bg-surface-alt); padding: 0.125rem 0.375rem; border-radius: 0.25rem; font-size: 0.875rem; } -.markdown-content table { border-collapse: collapse; width: 100%; margin-bottom: 0.75rem; } -.markdown-content th { background: var(--color-bg-surface-alt); font-weight: 600; text-align: left; padding: 0.5rem 0.75rem; border: 1px solid var(--color-border-light); } -.markdown-content td { padding: 0.5rem 0.75rem; border: 1px solid var(--color-border-light); } +.markdown-content h1 { font-size: 1.5rem; font-weight: 700; margin-bottom: .5rem; color: var(--color-text-heading); } +.markdown-content h2 { font-size: 1.25rem; font-weight: 650; margin-bottom: .5rem; color: var(--color-text-heading); } +.markdown-content h3 { font-size: 1.125rem; font-weight: 650; margin-bottom: .5rem; color: var(--color-text-heading); } +.markdown-content p { margin-bottom: .75rem; line-height: 1.72; } +.markdown-content ul { list-style: disc; padding-left: 1.35rem; margin-bottom: .75rem; } +.markdown-content ol { list-style: decimal; padding-left: 1.35rem; margin-bottom: .75rem; } +.markdown-content li { margin-bottom: .25rem; line-height: 1.65; } +.markdown-content strong { font-weight: 700; color: var(--color-text-heading); } +.markdown-content code { background: var(--color-bg-surface-alt); padding: .15rem .4rem; border-radius: .35rem; font-size: .875rem; } +.markdown-content table { border-collapse: collapse; width: 100%; margin-bottom: .75rem; } +.markdown-content th { background: var(--color-bg-surface-alt); font-weight: 650; text-align: left; padding: .6rem .8rem; border: 1px solid var(--color-border-light); } +.markdown-content td { padding: .6rem .8rem; border: 1px solid var(--color-border-light); } .markdown-content *:last-child { margin-bottom: 0; } + +@media (max-width: 767px) { + .app-page { padding-top: 1.25rem; padding-bottom: 6rem; } +} + +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { scroll-behavior: auto !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d0e2e7a..f92c5fb 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,16 +1,23 @@ import type { Metadata } from "next"; -import { Inter } from "next/font/google"; +import { Manrope, Newsreader } from "next/font/google"; +import Script from "next/script"; import "./globals.css"; -const inter = Inter({ - variable: "--font-inter", +const manrope = Manrope({ + variable: "--font-manrope", + subsets: ["latin"], + display: "swap", +}); + +const newsreader = Newsreader({ + variable: "--font-newsreader", subsets: ["latin"], display: "swap", }); export const metadata: Metadata = { - title: "Study App", - description: "Practice exams and quizzes", + title: "Study Desk", + description: "A focused workspace for flashcards and practice quizzes", }; export default function RootLayout({ @@ -19,23 +26,21 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - - {children} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 0df09b9..30b8ab0 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; +import { ThemeToggle } from "@/components/ui/ThemeToggle"; export default function LoginPage() { const [password, setPassword] = useState(""); @@ -55,37 +56,28 @@ export default function LoginPage() { } return ( -
-
+
+
+
+
+
+
{/* Logo / Title area */} -
-
- - - -
-

- {isSetup ? "Welcome to Study App!" : "Study App"} +
+
S
+

Personal workspace

+

+ {isSetup ? "Welcome to Study Desk" : "Welcome back"}

{isSetup ? "Please set your admin password for the first time." - : "Enter your password to continue"} + : "Open your notes, decks, and practice quizzes."}

{/* Login card */} -
+
@@ -127,7 +119,7 @@ export default function LoginPage() { + Quizzes +
+ )} + + +
+
+ Appearance +
+
- + + ); + + return ( + <> + + +
+ + + Study Desk + + +
+ + {open && ( +
+ + {nav} + +
+ )} + ); } diff --git a/src/components/ui/ShareMenu.tsx b/src/components/ui/ShareMenu.tsx index 0349656..f24535c 100644 --- a/src/components/ui/ShareMenu.tsx +++ b/src/components/ui/ShareMenu.tsx @@ -45,8 +45,15 @@ export function ShareMenu({ targetType, contentId, classSlug }: ShareMenuProps) async function handleCopy() { if (!token) return; - const typeStr = targetType === "GROUP" ? "groups" : (targetType === "DECK" ? "flashcards" : "quizzes"); - const url = `${window.location.origin}/shared/${classSlug}/${typeStr}/${token}`; + const typeStr = isGroupShared + ? "groups" + : targetType === "GROUP" + ? "groups" + : targetType === "DECK" + ? "flashcards" + : "quizzes"; + const itemQuery = isGroupShared ? `?itemId=${encodeURIComponent(contentId)}` : ""; + const url = `${window.location.origin}/shared/${classSlug}/${typeStr}/${token}${itemQuery}`; await navigator.clipboard.writeText(url); setCopied(true); setTimeout(() => setCopied(false), 2000); @@ -56,7 +63,7 @@ export function ShareMenu({ targetType, contentId, classSlug }: ShareMenuProps)
+

diff --git a/src/app/(protected)/[classSlug]/quizzes/page.tsx b/src/app/(protected)/[classSlug]/quizzes/page.tsx index cd45b56..d007514 100644 --- a/src/app/(protected)/[classSlug]/quizzes/page.tsx +++ b/src/app/(protected)/[classSlug]/quizzes/page.tsx @@ -93,6 +93,7 @@ function SortableQuizCard({ quiz, onEdit, onDelete, classSlug }: any) { +
diff --git a/src/components/ui/ShareMenu.tsx b/src/components/ui/ShareMenu.tsx index f24535c..9fa0814 100644 --- a/src/components/ui/ShareMenu.tsx +++ b/src/components/ui/ShareMenu.tsx @@ -6,9 +6,10 @@ interface ShareMenuProps { targetType: "DECK" | "QUIZ" | "GROUP"; contentId: string; classSlug: string; + compact?: boolean; } -export function ShareMenu({ targetType, contentId, classSlug }: ShareMenuProps) { +export function ShareMenu({ targetType, contentId, classSlug, compact = false }: ShareMenuProps) { const [token, setToken] = useState(null); const [loading, setLoading] = useState(true); const [open, setOpen] = useState(false); @@ -63,7 +64,7 @@ export function ShareMenu({ targetType, contentId, classSlug }: ShareMenuProps)
- )} - - )} - - + + )} + + {stage === "token" && ( +
+
+ + setToken(event.target.value)} + autoComplete="one-time-code" + autoFocus + className="min-h-12 w-full rounded-xl border border-border bg-bg-surface-alt/60 px-4 font-mono text-text-heading placeholder:text-text-muted focus:border-primary" + placeholder="Paste the token from Docker logs" + /> +
+ + Validate Token + Back to sign in + + )} + + {stage === "password" && ( +
+
+ + = 8} + validText="Password meets the length requirement." + invalidText="Password must be at least 8 characters." + /> +
+
+ 0 && confirmPassword !== password} + /> + {confirmPassword && ( + + )} +
+ + + Set New Password + + Cancel + + )} + + {stage === "success" && ( +
+
+ Your password has been reset successfully. +
+ Sign In +
+ )}
-
+
); } + +function PasswordField({ + id, + label, + value, + onChange, + placeholder, + describedBy, + invalid = false, + autoFocus = false, +}: { + id: string; + label: string; + value: string; + onChange: (value: string) => void; + placeholder?: string; + describedBy?: string; + invalid?: boolean; + autoFocus?: boolean; +}) { + return ( +
+ + onChange(event.target.value)} + placeholder={placeholder} + autoComplete={id === "password" ? "current-password" : "new-password"} + aria-describedby={describedBy} + aria-invalid={invalid} + autoFocus={autoFocus} + className="min-h-12 w-full rounded-xl border border-border bg-bg-surface-alt/60 px-4 text-text-heading placeholder:text-text-muted focus:border-primary" + /> +
+ ); +} + +function ValidationMessage({ id, valid, validText, invalidText }: { + id: string; + valid: boolean; + validText: string; + invalidText: string; +}) { + return ( +

+ {valid ? validText : invalidText} +

+ ); +} + +function ErrorMessage({ message }: { message: string }) { + if (!message) return null; + return
{message}
; +} + +function PrimaryButton({ loading, disabled = false, onClick, children }: { + loading: boolean; + disabled?: boolean; + onClick?: () => void; + children: React.ReactNode; +}) { + return ( + + ); +} + +function SecondaryButton({ onClick, children }: { onClick: () => void; children: React.ReactNode }) { + return ( + + ); +} diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 2a3182d..c3e4bae 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -3,6 +3,12 @@ import { cookies } from "next/headers"; export interface SessionData { isAuthenticated: boolean; + sessionGeneration?: number; +} + +interface PasswordResetSessionData { + nonce?: string; + expiresAt?: string; } const sessionOptions: SessionOptions = { @@ -15,14 +21,27 @@ const sessionOptions: SessionOptions = { }, }; +const passwordResetSessionOptions: SessionOptions = { + password: sessionOptions.password, + cookieName: "study-app-password-reset", + cookieOptions: { + secure: sessionOptions.cookieOptions?.secure, + httpOnly: true, + sameSite: "lax" as const, + maxAge: 15 * 60, + path: "/", + }, +}; + export async function getSession() { const cookieStore = await cookies(); return getIronSession(cookieStore, sessionOptions); } -export async function createSession() { +export async function createSession(sessionGeneration: number) { const session = await getSession(); session.isAuthenticated = true; + session.sessionGeneration = sessionGeneration; await session.save(); } @@ -35,3 +54,23 @@ export async function isAuthenticated() { const session = await getSession(); return session.isAuthenticated === true; } + +export async function getPasswordResetSession() { + const cookieStore = await cookies(); + return getIronSession( + cookieStore, + passwordResetSessionOptions + ); +} + +export async function authorizePasswordReset(nonce: string, expiresAt: string) { + const session = await getPasswordResetSession(); + session.nonce = nonce; + session.expiresAt = expiresAt; + await session.save(); +} + +export async function destroyPasswordResetAuthorization() { + const session = await getPasswordResetSession(); + session.destroy(); +} diff --git a/src/lib/rateLimiter.ts b/src/lib/rateLimiter.ts index 0bf1b4b..9513c69 100644 --- a/src/lib/rateLimiter.ts +++ b/src/lib/rateLimiter.ts @@ -12,20 +12,25 @@ const store = new Map(); const WINDOW_MS = 60 * 1000; // 1 minute const MAX_REQUESTS = 10; -export function checkRateLimit(ip: string): { allowed: boolean; retryAfterMs: number } { +export function checkRateLimit( + key: string, + options: { windowMs?: number; maxRequests?: number } = {} +): { allowed: boolean; retryAfterMs: number } { const now = Date.now(); - const entry = store.get(ip) ?? { timestamps: [] }; + const windowMs = options.windowMs ?? WINDOW_MS; + const maxRequests = options.maxRequests ?? MAX_REQUESTS; + const entry = store.get(key) ?? { timestamps: [] }; // Remove timestamps outside the window - entry.timestamps = entry.timestamps.filter((t) => now - t < WINDOW_MS); + entry.timestamps = entry.timestamps.filter((t) => now - t < windowMs); - if (entry.timestamps.length >= MAX_REQUESTS) { + if (entry.timestamps.length >= maxRequests) { const oldestInWindow = entry.timestamps[0]; - const retryAfterMs = WINDOW_MS - (now - oldestInWindow); + const retryAfterMs = windowMs - (now - oldestInWindow); return { allowed: false, retryAfterMs }; } entry.timestamps.push(now); - store.set(ip, entry); + store.set(key, entry); return { allowed: true, retryAfterMs: 0 }; } diff --git a/src/lib/validation/authSchemas.ts b/src/lib/validation/authSchemas.ts new file mode 100644 index 0000000..bb4c32c --- /dev/null +++ b/src/lib/validation/authSchemas.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; + +export const loginSchema = z.object({ + password: z.string().min(8, "Password must be at least 8 characters"), +}); + +export const resetTokenSchema = z.object({ + token: z.string().min(1, "Reset token is required"), +}); + +export const completePasswordResetSchema = z + .object({ + password: z.string().min(8, "Password must be at least 8 characters"), + confirmPassword: z.string(), + }) + .refine((value) => value.password === value.confirmPassword, { + message: "Passwords do not match", + path: ["confirmPassword"], + }); diff --git a/src/middleware.ts b/src/proxy.ts similarity index 64% rename from src/middleware.ts rename to src/proxy.ts index 2bbe2d6..8a22eb4 100644 --- a/src/middleware.ts +++ b/src/proxy.ts @@ -1,6 +1,7 @@ import { NextRequest, NextResponse } from "next/server"; import { getIronSession } from "iron-session"; import type { SessionData } from "@/lib/auth"; +import { prisma } from "@/lib/db"; const sessionOptions = { password: @@ -9,18 +10,15 @@ const sessionOptions = { cookieName: "study-app-session", }; -// Routes that don't require authentication const publicPaths = ["/login", "/api/auth", "/shared"]; -export async function middleware(request: NextRequest) { +export async function proxy(request: NextRequest) { const { pathname } = request.nextUrl; - // Allow public routes if (publicPaths.some((path) => pathname.startsWith(path))) { return NextResponse.next(); } - // Allow static assets and Next.js internals if ( pathname.startsWith("/_next") || pathname.startsWith("/favicon") || @@ -29,15 +27,18 @@ export async function middleware(request: NextRequest) { return NextResponse.next(); } - // Check session const response = NextResponse.next(); - const session = await getIronSession( - request, - response, - sessionOptions - ); + const session = await getIronSession(request, response, sessionOptions); + const generationSetting = await prisma.setting.findUnique({ + where: { key: "auth_session_generation" }, + }); + const currentGeneration = Number.parseInt(generationSetting?.value ?? "1", 10); - if (!session.isAuthenticated) { + if ( + !session.isAuthenticated || + session.sessionGeneration !== currentGeneration + ) { + session.destroy(); return NextResponse.redirect(new URL("/login", request.url)); } diff --git a/src/services/authService.ts b/src/services/authService.ts new file mode 100644 index 0000000..c54f9c5 --- /dev/null +++ b/src/services/authService.ts @@ -0,0 +1,227 @@ +import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto"; +import { prisma } from "@/lib/db"; +import { + authorizePasswordReset, + createSession, + destroyPasswordResetAuthorization, + getPasswordResetSession, +} from "@/lib/auth"; + +const PASSWORD_HASH_KEY = "admin_password_hash"; +const RESET_TOKEN_KEY = "admin_password_reset"; +const SESSION_GENERATION_KEY = "auth_session_generation"; +const RESET_TOKEN_LIFETIME_MS = 15 * 60 * 1000; + +interface ResetTokenRecord { + digest: string; + nonce: string; + expiresAt: string; +} + +export type LoginResult = + | { ok: true } + | { ok: false; status: 401 | 423; error: string }; + +function getLockoutDuration(failedAttempts: number): number { + if (failedAttempts >= 20) return 24 * 60 * 60 * 1000; + if (failedAttempts >= 15) return 30 * 60 * 1000; + if (failedAttempts >= 10) return 5 * 60 * 1000; + if (failedAttempts >= 5) return 60 * 1000; + return 0; +} + +function digestToken(token: string) { + return createHash("sha256").update(token, "utf8").digest(); +} + +function parseResetTokenRecord(value: string | undefined): ResetTokenRecord | null { + if (!value) return null; + + try { + const parsed: unknown = JSON.parse(value); + if ( + typeof parsed === "object" && + parsed !== null && + "digest" in parsed && + "nonce" in parsed && + "expiresAt" in parsed && + typeof parsed.digest === "string" && + typeof parsed.nonce === "string" && + typeof parsed.expiresAt === "string" + ) { + return parsed as ResetTokenRecord; + } + } catch { + // A malformed reset record is treated as invalid. + } + + return null; +} + +async function getSessionGeneration(): Promise { + const setting = await prisma.setting.findUnique({ + where: { key: SESSION_GENERATION_KEY }, + }); + const generation = Number.parseInt(setting?.value ?? "1", 10); + return Number.isSafeInteger(generation) && generation > 0 ? generation : 1; +} + +async function getOrCreateSecurityRecord() { + return prisma.authSecurity.upsert({ + where: { id: 1 }, + update: {}, + create: { id: 1 }, + }); +} + +export async function login(password: string): Promise { + const security = await getOrCreateSecurityRecord(); + + if (security.lockedUntil && security.lockedUntil > new Date()) { + const remainingMin = Math.ceil( + (security.lockedUntil.getTime() - Date.now()) / 60_000 + ); + return { + ok: false, + status: 423, + error: `Account locked. Try again in ${remainingMin} minute${remainingMin === 1 ? "" : "s"}.`, + }; + } + + const passwordSetting = await prisma.setting.findUnique({ + where: { key: PASSWORD_HASH_KEY }, + }); + const argon2 = await import("argon2"); + let isValid = false; + + if (!passwordSetting) { + await prisma.setting.create({ + data: { key: PASSWORD_HASH_KEY, value: await argon2.hash(password) }, + }); + isValid = true; + } else { + try { + isValid = await argon2.verify(passwordSetting.value, password); + } catch { + isValid = false; + } + } + + if (!isValid) { + const failedAttempts = security.failedAttempts + 1; + const lockoutMs = getLockoutDuration(failedAttempts); + await prisma.authSecurity.update({ + where: { id: 1 }, + data: { + failedAttempts, + lockedUntil: lockoutMs ? new Date(Date.now() + lockoutMs) : null, + lastAttemptAt: new Date(), + }, + }); + return { ok: false, status: 401, error: "Invalid password" }; + } + + await prisma.authSecurity.update({ + where: { id: 1 }, + data: { failedAttempts: 0, lockedUntil: null, lastAttemptAt: new Date() }, + }); + await createSession(await getSessionGeneration()); + return { ok: true }; +} + +export async function requestPasswordReset(): Promise { + const passwordSetting = await prisma.setting.findUnique({ + where: { key: PASSWORD_HASH_KEY }, + }); + if (!passwordSetting) return false; + + const token = randomBytes(24).toString("base64url"); + const expiresAt = new Date(Date.now() + RESET_TOKEN_LIFETIME_MS).toISOString(); + const record: ResetTokenRecord = { + digest: digestToken(token).toString("hex"), + nonce: randomUUID(), + expiresAt, + }; + + await prisma.setting.upsert({ + where: { key: RESET_TOKEN_KEY }, + update: { value: JSON.stringify(record) }, + create: { key: RESET_TOKEN_KEY, value: JSON.stringify(record) }, + }); + + console.info(`[password-reset] Token: ${token} (expires ${expiresAt})`); + return true; +} + +export async function verifyPasswordResetToken(token: string): Promise { + const setting = await prisma.setting.findUnique({ where: { key: RESET_TOKEN_KEY } }); + const record = parseResetTokenRecord(setting?.value); + if (!record || new Date(record.expiresAt).getTime() <= Date.now()) return false; + + const submittedDigest = digestToken(token); + const storedDigest = Buffer.from(record.digest, "hex"); + if ( + submittedDigest.length !== storedDigest.length || + !timingSafeEqual(submittedDigest, storedDigest) + ) { + return false; + } + + await authorizePasswordReset(record.nonce, record.expiresAt); + return true; +} + +export async function completePasswordReset(password: string): Promise { + const resetSession = await getPasswordResetSession(); + if (!resetSession.nonce || !resetSession.expiresAt) { + await destroyPasswordResetAuthorization(); + return false; + } + + const argon2 = await import("argon2"); + const passwordHash = await argon2.hash(password); + const completed = await prisma.$transaction(async (transaction) => { + const setting = await transaction.setting.findUnique({ + where: { key: RESET_TOKEN_KEY }, + }); + const record = parseResetTokenRecord(setting?.value); + if ( + !record || + resetSession.nonce !== record.nonce || + resetSession.expiresAt !== record.expiresAt || + new Date(record.expiresAt).getTime() <= Date.now() + ) { + return false; + } + + const generationSetting = await transaction.setting.findUnique({ + where: { key: SESSION_GENERATION_KEY }, + }); + const parsedGeneration = Number.parseInt(generationSetting?.value ?? "1", 10); + const currentGeneration = + Number.isSafeInteger(parsedGeneration) && parsedGeneration > 0 + ? parsedGeneration + : 1; + + await transaction.setting.upsert({ + where: { key: PASSWORD_HASH_KEY }, + update: { value: passwordHash }, + create: { key: PASSWORD_HASH_KEY, value: passwordHash }, + }); + await transaction.setting.upsert({ + where: { key: SESSION_GENERATION_KEY }, + update: { value: String(currentGeneration + 1) }, + create: { key: SESSION_GENERATION_KEY, value: String(currentGeneration + 1) }, + }); + await transaction.setting.delete({ where: { key: RESET_TOKEN_KEY } }); + await transaction.authSecurity.upsert({ + where: { id: 1 }, + update: { failedAttempts: 0, lockedUntil: null, lastAttemptAt: new Date() }, + create: { id: 1 }, + }); + return true; + }); + + await destroyPasswordResetAuthorization(); + return completed; +} From dad62c9914d6ac5ae39dc9b1d940b26921483c4e Mon Sep 17 00:00:00 2001 From: Elijah Date: Sun, 12 Jul 2026 11:44:26 -0700 Subject: [PATCH 06/13] Add study activity tracking and refresh class header UI --- .gitignore | 6 + dev.db | Bin 155648 -> 0 bytes .../migration.sql | 9 + prisma/schema.prisma | 8 + src/app/(protected)/[classSlug]/layout.tsx | 4 +- src/app/(protected)/page.tsx | 27 +- src/app/api/activity/route.ts | 17 + src/components/activity/ActivityBanner.tsx | 199 +++ src/components/flashcards/FlashcardViewer.tsx | 13 +- src/components/quizzes/QuizViewer.tsx | 12 +- src/components/ui/ClassHeader.tsx | 10 +- src/generated/prisma/browser.ts | 5 + src/generated/prisma/client.ts | 5 + src/generated/prisma/internal/class.ts | 18 +- .../prisma/internal/prismaNamespace.ts | 87 +- .../prisma/internal/prismaNamespaceBrowser.ts | 10 + src/generated/prisma/models.ts | 1 + src/generated/prisma/models/StudyActivity.ts | 1091 +++++++++++++++++ src/lib/activityClient.ts | 9 + src/lib/validation/activitySchemas.ts | 7 + src/services/activityService.ts | 104 ++ 21 files changed, 1598 insertions(+), 44 deletions(-) delete mode 100644 dev.db create mode 100644 prisma/migrations/20260712113000_add_study_activity/migration.sql create mode 100644 src/app/api/activity/route.ts create mode 100644 src/components/activity/ActivityBanner.tsx create mode 100644 src/generated/prisma/models/StudyActivity.ts create mode 100644 src/lib/activityClient.ts create mode 100644 src/lib/validation/activitySchemas.ts create mode 100644 src/services/activityService.ts diff --git a/.gitignore b/.gitignore index 5ef6a52..03f032b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,12 @@ # testing /coverage +# local SQLite study data +/dev.db +/dev.db-journal +/dev.db-shm +/dev.db-wal + # next.js /.next/ /out/ diff --git a/dev.db b/dev.db deleted file mode 100644 index 3dc8b2c0b46461bb0b6bead63e88903fe84fc62a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155648 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 bz-IyhGVAA2pNZUld9)J)WdF~jKl=Xx8;TkM diff --git a/prisma/migrations/20260712113000_add_study_activity/migration.sql b/prisma/migrations/20260712113000_add_study_activity/migration.sql new file mode 100644 index 0000000..817ba79 --- /dev/null +++ b/prisma/migrations/20260712113000_add_study_activity/migration.sql @@ -0,0 +1,9 @@ +-- CreateTable +CREATE TABLE "StudyActivity" ( + "id" TEXT NOT NULL PRIMARY KEY, + "type" TEXT NOT NULL, + "occurredAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +-- CreateIndex +CREATE INDEX "StudyActivity_occurredAt_idx" ON "StudyActivity"("occurredAt"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index dc8a941..be359a1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -141,6 +141,14 @@ model Setting { value String } +model StudyActivity { + id String @id @default(uuid()) + type String // "FLASHCARD" | "QUIZ_QUESTION" + occurredAt DateTime @default(now()) + + @@index([occurredAt]) +} + model MaterialGroup { id String @id @default(uuid()) classId String diff --git a/src/app/(protected)/[classSlug]/layout.tsx b/src/app/(protected)/[classSlug]/layout.tsx index 09c5c86..85d64bd 100644 --- a/src/app/(protected)/[classSlug]/layout.tsx +++ b/src/app/(protected)/[classSlug]/layout.tsx @@ -12,8 +12,8 @@ export default async function ClassLayout(props: LayoutProps<"/[classSlug]">) { return (
- {/* Dynamic Header & Tabs */} - + {/* Dynamic class header */} + {/* Page content */} {props.children} diff --git a/src/app/(protected)/page.tsx b/src/app/(protected)/page.tsx index 88ece8c..ba8de20 100644 --- a/src/app/(protected)/page.tsx +++ b/src/app/(protected)/page.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import Link from "next/link"; +import { ActivityBanner } from "@/components/activity/ActivityBanner"; interface ClassItem { id: string; @@ -56,33 +57,9 @@ export default function HomePage() { } finally { setSavingEdit(false); } } - const deckTotal = classes.reduce((sum, item) => sum + item._count.decks, 0); - const quizTotal = classes.reduce((sum, item) => sum + item._count.quizSets, 0); - return (
-
-
-
-
-
-

Personal library

-

What are we learning today?

-

Pick up where you left off, sharpen a weak topic, or build something new.

-
- -
-
- {[[classes.length, "Classes"], [deckTotal, "Decks"], [quizTotal, "Quizzes"]].map(([value, label]) => ( -
-
{value}
-
{label}
-
- ))} -
-
+ setShowCreate(true)} /> {showCreate && (
diff --git a/src/app/api/activity/route.ts b/src/app/api/activity/route.ts new file mode 100644 index 0000000..570793f --- /dev/null +++ b/src/app/api/activity/route.ts @@ -0,0 +1,17 @@ +import { NextRequest, NextResponse } from "next/server"; +import { studyActivitySchema } from "@/lib/validation/activitySchemas"; +import * as activityService from "@/services/activityService"; + +export async function GET() { + return NextResponse.json(await activityService.getActivitySummary()); +} + +export async function POST(request: NextRequest) { + const parsed = studyActivitySchema.safeParse(await request.json().catch(() => null)); + if (!parsed.success) { + return NextResponse.json({ error: "Invalid activity type" }, { status: 400 }); + } + + await activityService.recordActivity(parsed.data.type); + return NextResponse.json({ success: true }, { status: 201 }); +} diff --git a/src/components/activity/ActivityBanner.tsx b/src/components/activity/ActivityBanner.tsx new file mode 100644 index 0000000..0bcaf0a --- /dev/null +++ b/src/components/activity/ActivityBanner.tsx @@ -0,0 +1,199 @@ +"use client"; + +import { useEffect, useMemo, useRef, useState } from "react"; + +interface DailyActivity { + date: string; + flashcards: number; + questions: number; + total: number; + level: 0 | 1 | 2 | 3 | 4; +} + +interface ActivitySummary { + days: DailyActivity[]; + today: string; + activeDays: number; + flashcards: number; + questions: number; + currentStreak: number; +} + +const levelClasses = [ + "bg-white/10", + "bg-primary/45", + "bg-primary", + "bg-accent", + "bg-[#fbbf24]", +] as const; + +export function ActivityBanner({ onNewClass }: { onNewClass: () => void }) { + const [summary, setSummary] = useState(null); + const [error, setError] = useState(false); + + useEffect(() => { + fetch("/api/activity") + .then((response) => { + if (!response.ok) throw new Error("Failed to load activity"); + return response.json(); + }) + .then(setSummary) + .catch(() => setError(true)); + }, []); + + return ( +
+
+
+
+
+

Personal library

+

Study activity

+
+ +
+ + {error && ( +
+ Study activity could not be loaded. Your library is still available below. +
+ )} + {!summary && !error &&
} + {summary && ( +
+
+ +
+ + + +
+
+ +
+ )} +
+ ); +} + +function ActivityHeatmap({ days, today }: { days: DailyActivity[]; today: string }) { + const scrollRef = useRef(null); + const [activeDay, setActiveDay] = useState(null); + const weeks = useMemo( + () => Array.from({ length: 53 }, (_, index) => days.slice(index * 7, index * 7 + 7)), + [days] + ); + + useEffect(() => { + const container = scrollRef.current; + if (container) container.scrollLeft = container.scrollWidth; + }, [days]); + + return ( +
+
+

Last 53 weeks · Arizona time

+
+ {activeDay && } +
+
+
+
+
+
+
Mon
+
+
Wed
+
+
Fri
+
+
+
+ {weeks.map((week, index) => ( +
+ {getMonthLabel(week, weeks[index - 1])} +
+ ))} +
+
+ {weeks.map((week, weekIndex) => ( +
+ {week.map((day) => day.date > today ? ( + + ) : ( +
+ ))} +
+
+
+
+
+ Less + {levelClasses.map((className, index) => )} + More +
+
+ ); +} + +function DayTooltip({ day }: { day: DailyActivity }) { + const formatted = new Date(`${day.date}T12:00:00Z`).toLocaleDateString("en-US", { + timeZone: "America/Phoenix", + month: "short", + day: "numeric", + year: "numeric", + }); + return ( +
+
{formatted} · {day.total} total
+
{day.flashcards} flashcards · {day.questions} questions
+
+ ); +} + +function StreakDisplay({ streak }: { streak: number }) { + const size = 24 + Math.min(streak, 30) * 0.6; + const color = streak === 0 ? "text-white/30" : streak < 7 ? "text-accent" : streak < 30 ? "text-[#ff7a36]" : "text-[#fbbf24]"; + return ( +
+ + + +
+
{streak}
+
day streak
+
20 activities per day
+
+
+ ); +} + +function SummaryStat({ value, label }: { value: number; label: string }) { + return
{value}
{label}
; +} + +function getMonthLabel(week: DailyActivity[], previousWeek?: DailyActivity[]) { + const firstOfMonth = week.find((day) => Number(day.date.slice(8, 10)) <= 7); + if (!firstOfMonth) return ""; + const month = firstOfMonth.date.slice(5, 7); + if (previousWeek?.some((day) => day.date.slice(5, 7) === month)) return ""; + return new Date(`${firstOfMonth.date}T12:00:00Z`).toLocaleDateString("en-US", { month: "short", timeZone: "America/Phoenix" }); +} + +function getDayAriaLabel(day: DailyActivity) { + return `${day.date}: ${day.flashcards} flashcards and ${day.questions} questions, ${day.total} total activities`; +} diff --git a/src/components/flashcards/FlashcardViewer.tsx b/src/components/flashcards/FlashcardViewer.tsx index b2f9625..ecb6f15 100644 --- a/src/components/flashcards/FlashcardViewer.tsx +++ b/src/components/flashcards/FlashcardViewer.tsx @@ -3,6 +3,7 @@ import { useState, useEffect, useCallback, useRef } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; +import { recordStudyActivity } from "@/lib/activityClient"; interface Card { id: string; @@ -55,6 +56,7 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre ); const [swipeClass, setSwipeClass] = useState(""); const cardRef = useRef(null); + const gradingRef = useRef(false); // Touch/drag state const dragRef = useRef({ startX: 0, currentX: 0, isDragging: false }); @@ -120,7 +122,12 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre // Grade the current card const gradeCard = useCallback( (grade: CardResult) => { - if (!currentCard || !hasFlippedOnce) return; + if (!currentCard || !hasFlippedOnce || gradingRef.current) return; + gradingRef.current = true; + + if (!isShared) { + recordStudyActivity("FLASHCARD").catch(() => {}); + } setToastMessage(null); const newResults = { ...results, [currentCard.id]: grade }; @@ -130,6 +137,7 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre setSwipeClass(grade === "correct" ? "animate-swipe-right" : "animate-swipe-left"); setTimeout(() => { + gradingRef.current = false; setSwipeClass(""); setIsFlipped(false); setHasFlippedOnce(false); @@ -144,7 +152,7 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre } }, 350); }, - [currentCard, hasFlippedOnce, results, currentIndex, order, isShuffled] + [currentCard, hasFlippedOnce, results, currentIndex, order, isShuffled, isShared] ); // Keyboard shortcuts @@ -236,6 +244,7 @@ export function FlashcardViewer({ cards, deckId, isShared = false, initialProgre // Restart handlers function restartFullSet() { + gradingRef.current = false; let newOrder: string[]; if (isShuffled) { newOrder = [...cards.map((c) => c.id)].sort(() => Math.random() - 0.5); diff --git a/src/components/quizzes/QuizViewer.tsx b/src/components/quizzes/QuizViewer.tsx index e14108f..ac2bd97 100644 --- a/src/components/quizzes/QuizViewer.tsx +++ b/src/components/quizzes/QuizViewer.tsx @@ -1,10 +1,11 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useRef } from "react"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { scoreQuestion } from "@/lib/scoring"; import { QuizResults } from "./QuizResults"; +import { recordStudyActivity } from "@/lib/activityClient"; interface Option { id: string; @@ -39,6 +40,7 @@ interface QuizViewerProps { } export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: QuizViewerProps) { + const submittingQuestionRef = useRef(null); const [order, setOrder] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); const [answers, setAnswers] = useState>({}); @@ -194,7 +196,11 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu } function handleSubmit() { - if (isSubmitted || !currentQId) return; + if (isSubmitted || !currentQId || submittingQuestionRef.current === currentQId) return; + submittingQuestionRef.current = currentQId; + if (!isShared) { + recordStudyActivity("QUIZ_QUESTION").catch(() => {}); + } const newSubmitted = [...submittedAnswers, currentQId]; setSubmittedAnswers(newSubmitted); saveProgress(currentIndex, answers); @@ -202,6 +208,7 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu function handleNext() { if (currentIndex + 1 < order.length) { + submittingQuestionRef.current = null; const nextIdx = currentIndex + 1; setCurrentIndex(nextIdx); saveProgress(nextIdx, answers); @@ -290,6 +297,7 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu setCurrentIndex(0); setAnswers({}); setSubmittedAnswers([]); + submittingQuestionRef.current = null; setResultsData(null); // Re-shuffle options for the new attempt diff --git a/src/components/ui/ClassHeader.tsx b/src/components/ui/ClassHeader.tsx index 2fe8f63..b07ed29 100644 --- a/src/components/ui/ClassHeader.tsx +++ b/src/components/ui/ClassHeader.tsx @@ -2,14 +2,12 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; -import { ClassTabs } from "./ClassTabs"; interface ClassHeaderProps { - classSlug: string; className: string; } -export function ClassHeader({ classSlug, className }: ClassHeaderProps) { +export function ClassHeader({ className }: ClassHeaderProps) { const pathname = usePathname(); // If we are deeper than /[classSlug]/[tab], hide this header to save space @@ -23,8 +21,7 @@ export function ClassHeader({ classSlug, className }: ClassHeaderProps) { } return ( - <> -
+

Choose a mode, then settle in for a focused study session.

- - - ); } diff --git a/src/generated/prisma/browser.ts b/src/generated/prisma/browser.ts index bbd13c9..cffdc48 100644 --- a/src/generated/prisma/browser.ts +++ b/src/generated/prisma/browser.ts @@ -72,6 +72,11 @@ export type AuthSecurity = Prisma.AuthSecurityModel * */ export type Setting = Prisma.SettingModel +/** + * Model StudyActivity + * + */ +export type StudyActivity = Prisma.StudyActivityModel /** * Model MaterialGroup * diff --git a/src/generated/prisma/client.ts b/src/generated/prisma/client.ts index c502699..27a7cbd 100644 --- a/src/generated/prisma/client.ts +++ b/src/generated/prisma/client.ts @@ -96,6 +96,11 @@ export type AuthSecurity = Prisma.AuthSecurityModel * */ export type Setting = Prisma.SettingModel +/** + * Model StudyActivity + * + */ +export type StudyActivity = Prisma.StudyActivityModel /** * Model MaterialGroup * diff --git a/src/generated/prisma/internal/class.ts b/src/generated/prisma/internal/class.ts index 43381b9..de318cc 100644 --- a/src/generated/prisma/internal/class.ts +++ b/src/generated/prisma/internal/class.ts @@ -20,7 +20,7 @@ const config: runtime.GetPrismaClientConfig = { "clientVersion": "7.8.0", "engineVersion": "3c6e192761c0362d496ed980de936e2f3cebcd3a", "activeProvider": "sqlite", - "inlineSchema": "datasource db {\n provider = \"sqlite\"\n}\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../src/generated/prisma\"\n}\n\nmodel Class {\n id String @id @default(uuid())\n slug String @unique\n name String\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n\n decks Deck[]\n quizSets QuizSet[]\n materialGroups MaterialGroup[]\n}\n\nmodel Deck {\n id String @id @default(uuid())\n classId String\n name String\n description String?\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n groupId String?\n group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)\n cards Flashcard[]\n shareLink ShareLink?\n progress StudyProgress[]\n}\n\nmodel Flashcard {\n id String @id @default(uuid())\n deckId String\n front String\n back String\n sortOrder Int @default(0)\n\n deck Deck @relation(fields: [deckId], references: [id], onDelete: Cascade)\n}\n\nmodel QuizSet {\n id String @id @default(uuid())\n classId String\n name String\n description String?\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n groupId String?\n group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)\n questions Question[]\n attempts QuizAttempt[]\n shareLink ShareLink?\n progress StudyProgress[]\n}\n\nmodel Question {\n id String @id @default(uuid())\n quizSetId String\n type String // \"MULTIPLE_CHOICE\" | \"SATA\"\n prompt String\n rationale String\n category String\n sortOrder Int @default(0)\n\n quizSet QuizSet @relation(fields: [quizSetId], references: [id], onDelete: Cascade)\n options AnswerOption[]\n}\n\nmodel AnswerOption {\n id String @id @default(uuid())\n questionId String\n text String\n isCorrect Boolean @default(false)\n sortOrder Int @default(0)\n\n question Question @relation(fields: [questionId], references: [id], onDelete: Cascade)\n}\n\nmodel StudyProgress {\n id String @id @default(uuid())\n contentType String // \"DECK\" | \"QUIZ\"\n deckId String?\n quizSetId String?\n mode String // \"SEQUENTIAL\" | \"SHUFFLED\"\n currentIndex Int @default(0)\n orderJson String // JSON array of card/question ids\n answersJson String? // in-progress quiz answers\n cardResultsJson String? // per-card grades for decks\n updatedAt DateTime @updatedAt\n\n deck Deck? @relation(fields: [deckId], references: [id], onDelete: Cascade)\n quizSet QuizSet? @relation(fields: [quizSetId], references: [id], onDelete: Cascade)\n\n @@unique([deckId, mode])\n @@unique([quizSetId, mode])\n}\n\nmodel QuizAttempt {\n id String @id @default(uuid())\n quizSetId String\n score Float\n maxScore Int\n answersJson String\n isPartialRetake Boolean @default(false)\n completedAt DateTime @default(now())\n\n quizSet QuizSet @relation(fields: [quizSetId], references: [id], onDelete: Cascade)\n}\n\nmodel ShareLink {\n id String @id @default(uuid())\n targetType String // \"DECK\" | \"QUIZ\" | \"GROUP\"\n deckId String? @unique\n quizSetId String? @unique\n groupId String? @unique\n createdAt DateTime @default(now())\n\n deck Deck? @relation(fields: [deckId], references: [id], onDelete: Cascade)\n quizSet QuizSet? @relation(fields: [quizSetId], references: [id], onDelete: Cascade)\n group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: Cascade)\n}\n\nmodel AuthSecurity {\n id Int @id @default(1)\n failedAttempts Int @default(0)\n lockedUntil DateTime?\n lastAttemptAt DateTime?\n}\n\nmodel Setting {\n key String @id\n value String\n}\n\nmodel MaterialGroup {\n id String @id @default(uuid())\n classId String\n name String\n type String // \"DECK\" | \"QUIZ\"\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n decks Deck[]\n quizSets QuizSet[]\n shareLink ShareLink?\n}\n", + "inlineSchema": "datasource db {\n provider = \"sqlite\"\n}\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../src/generated/prisma\"\n}\n\nmodel Class {\n id String @id @default(uuid())\n slug String @unique\n name String\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n\n decks Deck[]\n quizSets QuizSet[]\n materialGroups MaterialGroup[]\n}\n\nmodel Deck {\n id String @id @default(uuid())\n classId String\n name String\n description String?\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n groupId String?\n group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)\n cards Flashcard[]\n shareLink ShareLink?\n progress StudyProgress[]\n}\n\nmodel Flashcard {\n id String @id @default(uuid())\n deckId String\n front String\n back String\n sortOrder Int @default(0)\n\n deck Deck @relation(fields: [deckId], references: [id], onDelete: Cascade)\n}\n\nmodel QuizSet {\n id String @id @default(uuid())\n classId String\n name String\n description String?\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n groupId String?\n group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: SetNull)\n questions Question[]\n attempts QuizAttempt[]\n shareLink ShareLink?\n progress StudyProgress[]\n}\n\nmodel Question {\n id String @id @default(uuid())\n quizSetId String\n type String // \"MULTIPLE_CHOICE\" | \"SATA\"\n prompt String\n rationale String\n category String\n sortOrder Int @default(0)\n\n quizSet QuizSet @relation(fields: [quizSetId], references: [id], onDelete: Cascade)\n options AnswerOption[]\n}\n\nmodel AnswerOption {\n id String @id @default(uuid())\n questionId String\n text String\n isCorrect Boolean @default(false)\n sortOrder Int @default(0)\n\n question Question @relation(fields: [questionId], references: [id], onDelete: Cascade)\n}\n\nmodel StudyProgress {\n id String @id @default(uuid())\n contentType String // \"DECK\" | \"QUIZ\"\n deckId String?\n quizSetId String?\n mode String // \"SEQUENTIAL\" | \"SHUFFLED\"\n currentIndex Int @default(0)\n orderJson String // JSON array of card/question ids\n answersJson String? // in-progress quiz answers\n cardResultsJson String? // per-card grades for decks\n updatedAt DateTime @updatedAt\n\n deck Deck? @relation(fields: [deckId], references: [id], onDelete: Cascade)\n quizSet QuizSet? @relation(fields: [quizSetId], references: [id], onDelete: Cascade)\n\n @@unique([deckId, mode])\n @@unique([quizSetId, mode])\n}\n\nmodel QuizAttempt {\n id String @id @default(uuid())\n quizSetId String\n score Float\n maxScore Int\n answersJson String\n isPartialRetake Boolean @default(false)\n completedAt DateTime @default(now())\n\n quizSet QuizSet @relation(fields: [quizSetId], references: [id], onDelete: Cascade)\n}\n\nmodel ShareLink {\n id String @id @default(uuid())\n targetType String // \"DECK\" | \"QUIZ\" | \"GROUP\"\n deckId String? @unique\n quizSetId String? @unique\n groupId String? @unique\n createdAt DateTime @default(now())\n\n deck Deck? @relation(fields: [deckId], references: [id], onDelete: Cascade)\n quizSet QuizSet? @relation(fields: [quizSetId], references: [id], onDelete: Cascade)\n group MaterialGroup? @relation(fields: [groupId], references: [id], onDelete: Cascade)\n}\n\nmodel AuthSecurity {\n id Int @id @default(1)\n failedAttempts Int @default(0)\n lockedUntil DateTime?\n lastAttemptAt DateTime?\n}\n\nmodel Setting {\n key String @id\n value String\n}\n\nmodel StudyActivity {\n id String @id @default(uuid())\n type String // \"FLASHCARD\" | \"QUIZ_QUESTION\"\n occurredAt DateTime @default(now())\n\n @@index([occurredAt])\n}\n\nmodel MaterialGroup {\n id String @id @default(uuid())\n classId String\n name String\n type String // \"DECK\" | \"QUIZ\"\n sortOrder Int @default(0)\n createdAt DateTime @default(now())\n\n class Class @relation(fields: [classId], references: [id], onDelete: Cascade)\n decks Deck[]\n quizSets QuizSet[]\n shareLink ShareLink?\n}\n", "runtimeDataModel": { "models": {}, "enums": {}, @@ -32,10 +32,10 @@ const config: runtime.GetPrismaClientConfig = { } } -config.runtimeDataModel = JSON.parse("{\"models\":{\"Class\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"slug\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"decks\",\"kind\":\"object\",\"type\":\"Deck\",\"relationName\":\"ClassToDeck\"},{\"name\":\"quizSets\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"ClassToQuizSet\"},{\"name\":\"materialGroups\",\"kind\":\"object\",\"type\":\"MaterialGroup\",\"relationName\":\"ClassToMaterialGroup\"}],\"dbName\":null},\"Deck\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"classId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"class\",\"kind\":\"object\",\"type\":\"Class\",\"relationName\":\"ClassToDeck\"},{\"name\":\"groupId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"group\",\"kind\":\"object\",\"type\":\"MaterialGroup\",\"relationName\":\"DeckToMaterialGroup\"},{\"name\":\"cards\",\"kind\":\"object\",\"type\":\"Flashcard\",\"relationName\":\"DeckToFlashcard\"},{\"name\":\"shareLink\",\"kind\":\"object\",\"type\":\"ShareLink\",\"relationName\":\"DeckToShareLink\"},{\"name\":\"progress\",\"kind\":\"object\",\"type\":\"StudyProgress\",\"relationName\":\"DeckToStudyProgress\"}],\"dbName\":null},\"Flashcard\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"deckId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"front\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"back\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deck\",\"kind\":\"object\",\"type\":\"Deck\",\"relationName\":\"DeckToFlashcard\"}],\"dbName\":null},\"QuizSet\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"classId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"class\",\"kind\":\"object\",\"type\":\"Class\",\"relationName\":\"ClassToQuizSet\"},{\"name\":\"groupId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"group\",\"kind\":\"object\",\"type\":\"MaterialGroup\",\"relationName\":\"MaterialGroupToQuizSet\"},{\"name\":\"questions\",\"kind\":\"object\",\"type\":\"Question\",\"relationName\":\"QuestionToQuizSet\"},{\"name\":\"attempts\",\"kind\":\"object\",\"type\":\"QuizAttempt\",\"relationName\":\"QuizAttemptToQuizSet\"},{\"name\":\"shareLink\",\"kind\":\"object\",\"type\":\"ShareLink\",\"relationName\":\"QuizSetToShareLink\"},{\"name\":\"progress\",\"kind\":\"object\",\"type\":\"StudyProgress\",\"relationName\":\"QuizSetToStudyProgress\"}],\"dbName\":null},\"Question\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"quizSetId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"prompt\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rationale\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"category\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"quizSet\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"QuestionToQuizSet\"},{\"name\":\"options\",\"kind\":\"object\",\"type\":\"AnswerOption\",\"relationName\":\"AnswerOptionToQuestion\"}],\"dbName\":null},\"AnswerOption\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"questionId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"text\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isCorrect\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"question\",\"kind\":\"object\",\"type\":\"Question\",\"relationName\":\"AnswerOptionToQuestion\"}],\"dbName\":null},\"StudyProgress\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"contentType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"deckId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"quizSetId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"mode\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"currentIndex\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"orderJson\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"answersJson\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"cardResultsJson\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deck\",\"kind\":\"object\",\"type\":\"Deck\",\"relationName\":\"DeckToStudyProgress\"},{\"name\":\"quizSet\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"QuizSetToStudyProgress\"}],\"dbName\":null},\"QuizAttempt\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"quizSetId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"score\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"maxScore\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"answersJson\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isPartialRetake\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"completedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"quizSet\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"QuizAttemptToQuizSet\"}],\"dbName\":null},\"ShareLink\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"targetType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"deckId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"quizSetId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"groupId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deck\",\"kind\":\"object\",\"type\":\"Deck\",\"relationName\":\"DeckToShareLink\"},{\"name\":\"quizSet\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"QuizSetToShareLink\"},{\"name\":\"group\",\"kind\":\"object\",\"type\":\"MaterialGroup\",\"relationName\":\"MaterialGroupToShareLink\"}],\"dbName\":null},\"AuthSecurity\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"failedAttempts\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"lockedUntil\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"lastAttemptAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Setting\":{\"fields\":[{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"MaterialGroup\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"classId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"class\",\"kind\":\"object\",\"type\":\"Class\",\"relationName\":\"ClassToMaterialGroup\"},{\"name\":\"decks\",\"kind\":\"object\",\"type\":\"Deck\",\"relationName\":\"DeckToMaterialGroup\"},{\"name\":\"quizSets\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"MaterialGroupToQuizSet\"},{\"name\":\"shareLink\",\"kind\":\"object\",\"type\":\"ShareLink\",\"relationName\":\"MaterialGroupToShareLink\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.runtimeDataModel = JSON.parse("{\"models\":{\"Class\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"slug\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"decks\",\"kind\":\"object\",\"type\":\"Deck\",\"relationName\":\"ClassToDeck\"},{\"name\":\"quizSets\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"ClassToQuizSet\"},{\"name\":\"materialGroups\",\"kind\":\"object\",\"type\":\"MaterialGroup\",\"relationName\":\"ClassToMaterialGroup\"}],\"dbName\":null},\"Deck\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"classId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"class\",\"kind\":\"object\",\"type\":\"Class\",\"relationName\":\"ClassToDeck\"},{\"name\":\"groupId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"group\",\"kind\":\"object\",\"type\":\"MaterialGroup\",\"relationName\":\"DeckToMaterialGroup\"},{\"name\":\"cards\",\"kind\":\"object\",\"type\":\"Flashcard\",\"relationName\":\"DeckToFlashcard\"},{\"name\":\"shareLink\",\"kind\":\"object\",\"type\":\"ShareLink\",\"relationName\":\"DeckToShareLink\"},{\"name\":\"progress\",\"kind\":\"object\",\"type\":\"StudyProgress\",\"relationName\":\"DeckToStudyProgress\"}],\"dbName\":null},\"Flashcard\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"deckId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"front\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"back\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"deck\",\"kind\":\"object\",\"type\":\"Deck\",\"relationName\":\"DeckToFlashcard\"}],\"dbName\":null},\"QuizSet\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"classId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"class\",\"kind\":\"object\",\"type\":\"Class\",\"relationName\":\"ClassToQuizSet\"},{\"name\":\"groupId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"group\",\"kind\":\"object\",\"type\":\"MaterialGroup\",\"relationName\":\"MaterialGroupToQuizSet\"},{\"name\":\"questions\",\"kind\":\"object\",\"type\":\"Question\",\"relationName\":\"QuestionToQuizSet\"},{\"name\":\"attempts\",\"kind\":\"object\",\"type\":\"QuizAttempt\",\"relationName\":\"QuizAttemptToQuizSet\"},{\"name\":\"shareLink\",\"kind\":\"object\",\"type\":\"ShareLink\",\"relationName\":\"QuizSetToShareLink\"},{\"name\":\"progress\",\"kind\":\"object\",\"type\":\"StudyProgress\",\"relationName\":\"QuizSetToStudyProgress\"}],\"dbName\":null},\"Question\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"quizSetId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"prompt\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rationale\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"category\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"quizSet\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"QuestionToQuizSet\"},{\"name\":\"options\",\"kind\":\"object\",\"type\":\"AnswerOption\",\"relationName\":\"AnswerOptionToQuestion\"}],\"dbName\":null},\"AnswerOption\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"questionId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"text\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isCorrect\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"question\",\"kind\":\"object\",\"type\":\"Question\",\"relationName\":\"AnswerOptionToQuestion\"}],\"dbName\":null},\"StudyProgress\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"contentType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"deckId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"quizSetId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"mode\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"currentIndex\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"orderJson\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"answersJson\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"cardResultsJson\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deck\",\"kind\":\"object\",\"type\":\"Deck\",\"relationName\":\"DeckToStudyProgress\"},{\"name\":\"quizSet\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"QuizSetToStudyProgress\"}],\"dbName\":null},\"QuizAttempt\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"quizSetId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"score\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"maxScore\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"answersJson\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"isPartialRetake\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"completedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"quizSet\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"QuizAttemptToQuizSet\"}],\"dbName\":null},\"ShareLink\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"targetType\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"deckId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"quizSetId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"groupId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"deck\",\"kind\":\"object\",\"type\":\"Deck\",\"relationName\":\"DeckToShareLink\"},{\"name\":\"quizSet\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"QuizSetToShareLink\"},{\"name\":\"group\",\"kind\":\"object\",\"type\":\"MaterialGroup\",\"relationName\":\"MaterialGroupToShareLink\"}],\"dbName\":null},\"AuthSecurity\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"failedAttempts\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"lockedUntil\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"lastAttemptAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"Setting\":{\"fields\":[{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"StudyActivity\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"occurredAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null},\"MaterialGroup\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"classId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"type\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sortOrder\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"class\",\"kind\":\"object\",\"type\":\"Class\",\"relationName\":\"ClassToMaterialGroup\"},{\"name\":\"decks\",\"kind\":\"object\",\"type\":\"Deck\",\"relationName\":\"DeckToMaterialGroup\"},{\"name\":\"quizSets\",\"kind\":\"object\",\"type\":\"QuizSet\",\"relationName\":\"MaterialGroupToQuizSet\"},{\"name\":\"shareLink\",\"kind\":\"object\",\"type\":\"ShareLink\",\"relationName\":\"MaterialGroupToShareLink\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") config.parameterizationSchema = { - strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"class\",\"decks\",\"group\",\"quizSet\",\"question\",\"options\",\"_count\",\"questions\",\"attempts\",\"deck\",\"shareLink\",\"progress\",\"quizSets\",\"cards\",\"materialGroups\",\"Class.findUnique\",\"Class.findUniqueOrThrow\",\"Class.findFirst\",\"Class.findFirstOrThrow\",\"Class.findMany\",\"data\",\"Class.createOne\",\"Class.createMany\",\"Class.createManyAndReturn\",\"Class.updateOne\",\"Class.updateMany\",\"Class.updateManyAndReturn\",\"create\",\"update\",\"Class.upsertOne\",\"Class.deleteOne\",\"Class.deleteMany\",\"having\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"Class.groupBy\",\"Class.aggregate\",\"Deck.findUnique\",\"Deck.findUniqueOrThrow\",\"Deck.findFirst\",\"Deck.findFirstOrThrow\",\"Deck.findMany\",\"Deck.createOne\",\"Deck.createMany\",\"Deck.createManyAndReturn\",\"Deck.updateOne\",\"Deck.updateMany\",\"Deck.updateManyAndReturn\",\"Deck.upsertOne\",\"Deck.deleteOne\",\"Deck.deleteMany\",\"Deck.groupBy\",\"Deck.aggregate\",\"Flashcard.findUnique\",\"Flashcard.findUniqueOrThrow\",\"Flashcard.findFirst\",\"Flashcard.findFirstOrThrow\",\"Flashcard.findMany\",\"Flashcard.createOne\",\"Flashcard.createMany\",\"Flashcard.createManyAndReturn\",\"Flashcard.updateOne\",\"Flashcard.updateMany\",\"Flashcard.updateManyAndReturn\",\"Flashcard.upsertOne\",\"Flashcard.deleteOne\",\"Flashcard.deleteMany\",\"Flashcard.groupBy\",\"Flashcard.aggregate\",\"QuizSet.findUnique\",\"QuizSet.findUniqueOrThrow\",\"QuizSet.findFirst\",\"QuizSet.findFirstOrThrow\",\"QuizSet.findMany\",\"QuizSet.createOne\",\"QuizSet.createMany\",\"QuizSet.createManyAndReturn\",\"QuizSet.updateOne\",\"QuizSet.updateMany\",\"QuizSet.updateManyAndReturn\",\"QuizSet.upsertOne\",\"QuizSet.deleteOne\",\"QuizSet.deleteMany\",\"QuizSet.groupBy\",\"QuizSet.aggregate\",\"Question.findUnique\",\"Question.findUniqueOrThrow\",\"Question.findFirst\",\"Question.findFirstOrThrow\",\"Question.findMany\",\"Question.createOne\",\"Question.createMany\",\"Question.createManyAndReturn\",\"Question.updateOne\",\"Question.updateMany\",\"Question.updateManyAndReturn\",\"Question.upsertOne\",\"Question.deleteOne\",\"Question.deleteMany\",\"Question.groupBy\",\"Question.aggregate\",\"AnswerOption.findUnique\",\"AnswerOption.findUniqueOrThrow\",\"AnswerOption.findFirst\",\"AnswerOption.findFirstOrThrow\",\"AnswerOption.findMany\",\"AnswerOption.createOne\",\"AnswerOption.createMany\",\"AnswerOption.createManyAndReturn\",\"AnswerOption.updateOne\",\"AnswerOption.updateMany\",\"AnswerOption.updateManyAndReturn\",\"AnswerOption.upsertOne\",\"AnswerOption.deleteOne\",\"AnswerOption.deleteMany\",\"AnswerOption.groupBy\",\"AnswerOption.aggregate\",\"StudyProgress.findUnique\",\"StudyProgress.findUniqueOrThrow\",\"StudyProgress.findFirst\",\"StudyProgress.findFirstOrThrow\",\"StudyProgress.findMany\",\"StudyProgress.createOne\",\"StudyProgress.createMany\",\"StudyProgress.createManyAndReturn\",\"StudyProgress.updateOne\",\"StudyProgress.updateMany\",\"StudyProgress.updateManyAndReturn\",\"StudyProgress.upsertOne\",\"StudyProgress.deleteOne\",\"StudyProgress.deleteMany\",\"StudyProgress.groupBy\",\"StudyProgress.aggregate\",\"QuizAttempt.findUnique\",\"QuizAttempt.findUniqueOrThrow\",\"QuizAttempt.findFirst\",\"QuizAttempt.findFirstOrThrow\",\"QuizAttempt.findMany\",\"QuizAttempt.createOne\",\"QuizAttempt.createMany\",\"QuizAttempt.createManyAndReturn\",\"QuizAttempt.updateOne\",\"QuizAttempt.updateMany\",\"QuizAttempt.updateManyAndReturn\",\"QuizAttempt.upsertOne\",\"QuizAttempt.deleteOne\",\"QuizAttempt.deleteMany\",\"QuizAttempt.groupBy\",\"QuizAttempt.aggregate\",\"ShareLink.findUnique\",\"ShareLink.findUniqueOrThrow\",\"ShareLink.findFirst\",\"ShareLink.findFirstOrThrow\",\"ShareLink.findMany\",\"ShareLink.createOne\",\"ShareLink.createMany\",\"ShareLink.createManyAndReturn\",\"ShareLink.updateOne\",\"ShareLink.updateMany\",\"ShareLink.updateManyAndReturn\",\"ShareLink.upsertOne\",\"ShareLink.deleteOne\",\"ShareLink.deleteMany\",\"ShareLink.groupBy\",\"ShareLink.aggregate\",\"AuthSecurity.findUnique\",\"AuthSecurity.findUniqueOrThrow\",\"AuthSecurity.findFirst\",\"AuthSecurity.findFirstOrThrow\",\"AuthSecurity.findMany\",\"AuthSecurity.createOne\",\"AuthSecurity.createMany\",\"AuthSecurity.createManyAndReturn\",\"AuthSecurity.updateOne\",\"AuthSecurity.updateMany\",\"AuthSecurity.updateManyAndReturn\",\"AuthSecurity.upsertOne\",\"AuthSecurity.deleteOne\",\"AuthSecurity.deleteMany\",\"AuthSecurity.groupBy\",\"AuthSecurity.aggregate\",\"Setting.findUnique\",\"Setting.findUniqueOrThrow\",\"Setting.findFirst\",\"Setting.findFirstOrThrow\",\"Setting.findMany\",\"Setting.createOne\",\"Setting.createMany\",\"Setting.createManyAndReturn\",\"Setting.updateOne\",\"Setting.updateMany\",\"Setting.updateManyAndReturn\",\"Setting.upsertOne\",\"Setting.deleteOne\",\"Setting.deleteMany\",\"Setting.groupBy\",\"Setting.aggregate\",\"MaterialGroup.findUnique\",\"MaterialGroup.findUniqueOrThrow\",\"MaterialGroup.findFirst\",\"MaterialGroup.findFirstOrThrow\",\"MaterialGroup.findMany\",\"MaterialGroup.createOne\",\"MaterialGroup.createMany\",\"MaterialGroup.createManyAndReturn\",\"MaterialGroup.updateOne\",\"MaterialGroup.updateMany\",\"MaterialGroup.updateManyAndReturn\",\"MaterialGroup.upsertOne\",\"MaterialGroup.deleteOne\",\"MaterialGroup.deleteMany\",\"MaterialGroup.groupBy\",\"MaterialGroup.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"classId\",\"name\",\"type\",\"sortOrder\",\"createdAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"contains\",\"startsWith\",\"endsWith\",\"key\",\"value\",\"failedAttempts\",\"lockedUntil\",\"lastAttemptAt\",\"targetType\",\"deckId\",\"quizSetId\",\"groupId\",\"score\",\"maxScore\",\"answersJson\",\"isPartialRetake\",\"completedAt\",\"contentType\",\"mode\",\"currentIndex\",\"orderJson\",\"cardResultsJson\",\"updatedAt\",\"questionId\",\"text\",\"isCorrect\",\"prompt\",\"rationale\",\"category\",\"description\",\"front\",\"back\",\"slug\",\"every\",\"some\",\"none\",\"deckId_mode\",\"quizSetId_mode\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"disconnect\",\"delete\",\"connect\",\"createMany\",\"set\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), - graph: "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_QEBAOgCACH-AQIA7wIAIf8BAQDoAgAhgAIBAJMDACGBAkAA9gIAIQYGAADNBAAgDAAAzAQAIPQBAADCBAAg9QEAAMIEACD5AQAAwgQAIIACAADCBAAgEQYAAPgCACAMAAD3AgAg2gEAAJIDADDbAQAAIQAQ3AEAAJIDADDdAQEAAAAB9AEBAJMDACH1AQEAkwMAIfkBAQCTAwAh_AEBAOgCACH9AQEA6AIAIf4BAgDvAgAh_wEBAOgCACGAAgEAkwMAIYECQAD2AgAhjwIAAJADACCQAgAAkQMAIAMAAAAhACABAAAiADACAAAjACABAAAAAwAgAQAAAAoAIAEAAAAPACABAAAAGAAgAQAAACEAIAEAAAAcACABAAAAAwAgAQAAAAoAIAkMAACPAwAg2gEAAI4DADDbAQAALQAQ3AEAAI4DADDdAQEA6AIAIeEBAgDvAgAh9AEBAOgCACGJAgEA6AIAIYoCAQDoAgAhAQwAAMwEACAJDAAAjwMAINoBAACOAwAw2wEAAC0AENwBAACOAwAw3QEBAAAAAeEBAgDvAgAh9AEBAOgCACGJAgEA6AIAIYoCAQDoAgAhAwAAAC0AIAEAAC4AMAIAAC8AIAEAAAAcACADAAAAIQAgAQAAIgAwAgAAIwAgAQAAAC0AIAEAAAAhACADAAAACgAgAQAACwAwAgAADAAgBAMAAKoFACAEAACnBQAgDQAAqwUAIA8AAKgFACANAwAAjAMAIAQAAIgDACANAACNAwAgDwAAiQMAINoBAACLAwAw2wEAAAcAENwBAACLAwAw3QEBAAAAAd4BAQDoAgAh3wEBAOgCACHgAQEA6AIAIeEBAgDvAgAh4gFAAPYCACEDAAAABwAgAQAANgAwAgAANwAgAQAAAAMAIAEAAAAKACABAAAABwAgAQAAAAEAIAsEAACIAwAgDwAAiQMAIBEAAIoDACDaAQAAhwMAMNsBAAA9ABDcAQAAhwMAMN0BAQDoAgAh3wEBAOgCACHhAQIA7wIAIeIBQAD2AgAhiwIBAOgCACEDBAAApwUAIA8AAKgFACARAACpBQAgAwAAAD0AIAEAAD4AMAIAAAEAIAMAAAA9ACABAAA-ADACAAABACADAAAAPQAgAQAAPgAwAgAAAQAgCAQAAKQFACAPAAClBQAgEQAApgUAIN0BAQAAAAHfAQEAAAAB4QECAAAAAeIBQAAAAAGLAgEAAAABARcAAEIAIAXdAQEAAAAB3wEBAAAAAeEBAgAAAAHiAUAAAAABiwIBAAAAAQEXAABEADABFwAARAAwCAQAAIMFACAPAACEBQAgEQAAhQUAIN0BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAhiwIBAKcDACECAAAAAQAgFwAARwAgBd0BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAhiwIBAKcDACECAAAAPQAgFwAASQAgAgAAAD0AIBcAAEkAIAMAAAABACAeAABCACAfAABHACABAAAAAQAgAQAAAD0AIAUJAAD-BAAgJAAA_wQAICUAAIIFACAmAACBBQAgJwAAgAUAIAjaAQAAhgMAMNsBAABQABDcAQAAhgMAMN0BAQDcAgAh3wEBANwCACHhAQIA3QIAIeIBQADeAgAhiwIBANwCACEDAAAAPQAgAQAATwAwIwAAUAAgAwAAAD0AIAEAAD4AMAIAAAEAIAEAAAAFACABAAAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIAMAAAADACABAAAEADACAAAFACADAAAAAwAgAQAABAAwAgAABQAgDAMAALcEACAFAAD9BAAgDQAAuQQAIA4AALoEACAQAAC4BAAg3QEBAAAAAd4BAQAAAAHfAQEAAAAB4QECAAAAAeIBQAAAAAH2AQEAAAABiAIBAAAAAQEXAABYACAH3QEBAAAAAd4BAQAAAAHfAQEAAAAB4QECAAAAAeIBQAAAAAH2AQEAAAABiAIBAAAAAQEXAABaADABFwAAWgAwAQAAAAcAIAwDAACWBAAgBQAA_AQAIA0AAJgEACAOAACZBAAgEAAAlwQAIN0BAQCnAwAh3gEBAKcDACHfAQEApwMAIeEBAgCoAwAh4gFAAKkDACH2AQEAtQMAIYgCAQC1AwAhAgAAAAUAIBcAAF4AIAfdAQEApwMAId4BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAh9gEBALUDACGIAgEAtQMAIQIAAAADACAXAABgACACAAAAAwAgFwAAYAAgAQAAAAcAIAMAAAAFACAeAABYACAfAABeACABAAAABQAgAQAAAAMAIAcJAAD3BAAgJAAA-AQAICUAAPsEACAmAAD6BAAgJwAA-QQAIPYBAADCBAAgiAIAAMIEACAK2gEAAIUDADDbAQAAaAAQ3AEAAIUDADDdAQEA3AIAId4BAQDcAgAh3wEBANwCACHhAQIA3QIAIeIBQADeAgAh9gEBAPICACGIAgEA8gIAIQMAAAADACABAABnADAjAABoACADAAAAAwAgAQAABAAwAgAABQAgAQAAAC8AIAEAAAAvACADAAAALQAgAQAALgAwAgAALwAgAwAAAC0AIAEAAC4AMAIAAC8AIAMAAAAtACABAAAuADACAAAvACAGDAAA9gQAIN0BAQAAAAHhAQIAAAAB9AEBAAAAAYkCAQAAAAGKAgEAAAABARcAAHAAIAXdAQEAAAAB4QECAAAAAfQBAQAAAAGJAgEAAAABigIBAAAAAQEXAAByADABFwAAcgAwBgwAAPUEACDdAQEApwMAIeEBAgCoAwAh9AEBAKcDACGJAgEApwMAIYoCAQCnAwAhAgAAAC8AIBcAAHUAIAXdAQEApwMAIeEBAgCoAwAh9AEBAKcDACGJAgEApwMAIYoCAQCnAwAhAgAAAC0AIBcAAHcAIAIAAAAtACAXAAB3ACADAAAALwAgHgAAcAAgHwAAdQAgAQAAAC8AIAEAAAAtACAFCQAA8AQAICQAAPEEACAlAAD0BAAgJgAA8wQAICcAAPIEACAI2gEAAIQDADDbAQAAfgAQ3AEAAIQDADDdAQEA3AIAIeEBAgDdAgAh9AEBANwCACGJAgEA3AIAIYoCAQDcAgAhAwAAAC0AIAEAAH0AMCMAAH4AIAMAAAAtACABAAAuADACAAAvACABAAAADAAgAQAAAAwAIAMAAAAKACABAAALADACAAAMACADAAAACgAgAQAACwAwAgAADAAgAwAAAAoAIAEAAAsAMAIAAAwAIA0DAACGBAAgBQAA7wQAIAoAAIcEACALAACIBAAgDQAAiQQAIA4AAIoEACDdAQEAAAAB3gEBAAAAAd8BAQAAAAHhAQIAAAAB4gFAAAAAAfYBAQAAAAGIAgEAAAABARcAAIYBACAH3QEBAAAAAd4BAQAAAAHfAQEAAAAB4QECAAAAAeIBQAAAAAH2AQEAAAABiAIBAAAAAQEXAACIAQAwARcAAIgBADABAAAABwAgDQMAAMMDACAFAADuBAAgCgAAxAMAIAsAAMUDACANAADGAwAgDgAAxwMAIN0BAQCnAwAh3gEBAKcDACHfAQEApwMAIeEBAgCoAwAh4gFAAKkDACH2AQEAtQMAIYgCAQC1AwAhAgAAAAwAIBcAAIwBACAH3QEBAKcDACHeAQEApwMAId8BAQCnAwAh4QECAKgDACHiAUAAqQMAIfYBAQC1AwAhiAIBALUDACECAAAACgAgFwAAjgEAIAIAAAAKACAXAACOAQAgAQAAAAcAIAMAAAAMACAeAACGAQAgHwAAjAEAIAEAAAAMACABAAAACgAgBwkAAOkEACAkAADqBAAgJQAA7QQAICYAAOwEACAnAADrBAAg9gEAAMIEACCIAgAAwgQAIAraAQAAgwMAMNsBAACWAQAQ3AEAAIMDADDdAQEA3AIAId4BAQDcAgAh3wEBANwCACHhAQIA3QIAIeIBQADeAgAh9gEBAPICACGIAgEA8gIAIQMAAAAKACABAACVAQAwIwAAlgEAIAMAAAAKACABAAALADACAAAMACABAAAAEQAgAQAAABEAIAMAAAAPACABAAAQADACAAARACADAAAADwAgAQAAEAAwAgAAEQAgAwAAAA8AIAEAABAAMAIAABEAIAkGAADoBAAgCAAAhAQAIN0BAQAAAAHgAQEAAAAB4QECAAAAAfUBAQAAAAGFAgEAAAABhgIBAAAAAYcCAQAAAAEBFwAAngEAIAfdAQEAAAAB4AEBAAAAAeEBAgAAAAH1AQEAAAABhQIBAAAAAYYCAQAAAAGHAgEAAAABARcAAKABADABFwAAoAEAMAkGAADnBAAgCAAA9gMAIN0BAQCnAwAh4AEBAKcDACHhAQIAqAMAIfUBAQCnAwAhhQIBAKcDACGGAgEApwMAIYcCAQCnAwAhAgAAABEAIBcAAKMBACAH3QEBAKcDACHgAQEApwMAIeEBAgCoAwAh9QEBAKcDACGFAgEApwMAIYYCAQCnAwAhhwIBAKcDACECAAAADwAgFwAApQEAIAIAAAAPACAXAAClAQAgAwAAABEAIB4AAJ4BACAfAACjAQAgAQAAABEAIAEAAAAPACAFCQAA4gQAICQAAOMEACAlAADmBAAgJgAA5QQAICcAAOQEACAK2gEAAIIDADDbAQAArAEAENwBAACCAwAw3QEBANwCACHgAQEA3AIAIeEBAgDdAgAh9QEBANwCACGFAgEA3AIAIYYCAQDcAgAhhwIBANwCACEDAAAADwAgAQAAqwEAMCMAAKwBACADAAAADwAgAQAAEAAwAgAAEQAgAQAAABUAIAEAAAAVACADAAAAEwAgAQAAFAAwAgAAFQAgAwAAABMAIAEAABQAMAIAABUAIAMAAAATACABAAAUADACAAAVACAGBwAA4QQAIN0BAQAAAAHhAQIAAAABggIBAAAAAYMCAQAAAAGEAiAAAAABARcAALQBACAF3QEBAAAAAeEBAgAAAAGCAgEAAAABgwIBAAAAAYQCIAAAAAEBFwAAtgEAMAEXAAC2AQAwBgcAAOAEACDdAQEApwMAIeEBAgCoAwAhggIBAKcDACGDAgEApwMAIYQCIADoAwAhAgAAABUAIBcAALkBACAF3QEBAKcDACHhAQIAqAMAIYICAQCnAwAhgwIBAKcDACGEAiAA6AMAIQIAAAATACAXAAC7AQAgAgAAABMAIBcAALsBACADAAAAFQAgHgAAtAEAIB8AALkBACABAAAAFQAgAQAAABMAIAUJAADbBAAgJAAA3AQAICUAAN8EACAmAADeBAAgJwAA3QQAIAjaAQAAgQMAMNsBAADCAQAQ3AEAAIEDADDdAQEA3AIAIeEBAgDdAgAhggIBANwCACGDAgEA3AIAIYQCIAD8AgAhAwAAABMAIAEAAMEBADAjAADCAQAgAwAAABMAIAEAABQAMAIAABUAIAEAAAAjACABAAAAIwAgAwAAACEAIAEAACIAMAIAACMAIAMAAAAhACABAAAiADACAAAjACADAAAAIQAgAQAAIgAwAgAAIwAgDAYAAKQEACAMAADVAwAg3QEBAAAAAfQBAQAAAAH1AQEAAAAB-QEBAAAAAfwBAQAAAAH9AQEAAAAB_gECAAAAAf8BAQAAAAGAAgEAAAABgQJAAAAAAQEXAADKAQAgCt0BAQAAAAH0AQEAAAAB9QEBAAAAAfkBAQAAAAH8AQEAAAAB_QEBAAAAAf4BAgAAAAH_AQEAAAABgAIBAAAAAYECQAAAAAEBFwAAzAEAMAEXAADMAQAwAQAAAAMAIAEAAAAKACAMBgAAogQAIAwAANMDACDdAQEApwMAIfQBAQC1AwAh9QEBALUDACH5AQEAtQMAIfwBAQCnAwAh_QEBAKcDACH-AQIAqAMAIf8BAQCnAwAhgAIBALUDACGBAkAAqQMAIQIAAAAjACAXAADRAQAgCt0BAQCnAwAh9AEBALUDACH1AQEAtQMAIfkBAQC1AwAh_AEBAKcDACH9AQEApwMAIf4BAgCoAwAh_wEBAKcDACGAAgEAtQMAIYECQACpAwAhAgAAACEAIBcAANMBACACAAAAIQAgFwAA0wEAIAEAAAADACABAAAACgAgAwAAACMAIB4AAMoBACAfAADRAQAgAQAAACMAIAEAAAAhACAJCQAA1gQAICQAANcEACAlAADaBAAgJgAA2QQAICcAANgEACD0AQAAwgQAIPUBAADCBAAg-QEAAMIEACCAAgAAwgQAIA3aAQAAgAMAMNsBAADcAQAQ3AEAAIADADDdAQEA3AIAIfQBAQDyAgAh9QEBAPICACH5AQEA8gIAIfwBAQDcAgAh_QEBANwCACH-AQIA3QIAIf8BAQDcAgAhgAIBAPICACGBAkAA3gIAIQMAAAAhACABAADbAQAwIwAA3AEAIAMAAAAhACABAAAiADACAAAjACABAAAAGgAgAQAAABoAIAMAAAAYACABAAAZADACAAAaACADAAAAGAAgAQAAGQAwAgAAGgAgAwAAABgAIAEAABkAMAIAABoAIAgGAADVBAAg3QEBAAAAAfUBAQAAAAH3AQgAAAAB-AECAAAAAfkBAQAAAAH6ASAAAAAB-wFAAAAAAQEXAADkAQAgB90BAQAAAAH1AQEAAAAB9wEIAAAAAfgBAgAAAAH5AQEAAAAB-gEgAAAAAfsBQAAAAAEBFwAA5gEAMAEXAADmAQAwCAYAANQEACDdAQEApwMAIfUBAQCnAwAh9wEIAOcDACH4AQIAqAMAIfkBAQCnAwAh-gEgAOgDACH7AUAAqQMAIQIAAAAaACAXAADpAQAgB90BAQCnAwAh9QEBAKcDACH3AQgA5wMAIfgBAgCoAwAh-QEBAKcDACH6ASAA6AMAIfsBQACpAwAhAgAAABgAIBcAAOsBACACAAAAGAAgFwAA6wEAIAMAAAAaACAeAADkAQAgHwAA6QEAIAEAAAAaACABAAAAGAAgBQkAAM8EACAkAADQBAAgJQAA0wQAICYAANIEACAnAADRBAAgCtoBAAD6AgAw2wEAAPIBABDcAQAA-gIAMN0BAQDcAgAh9QEBANwCACH3AQgA-wIAIfgBAgDdAgAh-QEBANwCACH6ASAA_AIAIfsBQADeAgAhAwAAABgAIAEAAPEBADAjAADyAQAgAwAAABgAIAEAABkAMAIAABoAIAwFAAD5AgAgBgAA-AIAIAwAAPcCACDaAQAA9QIAMNsBAAAcABDcAQAA9QIAMN0BAQAAAAHiAUAA9gIAIfMBAQDoAgAh9AEBAAAAAfUBAQAAAAH2AQEAAAABAQAAAPUBACABAAAA9QEAIAYFAADOBAAgBgAAzQQAIAwAAMwEACD0AQAAwgQAIPUBAADCBAAg9gEAAMIEACADAAAAHAAgAQAA-AEAMAIAAPUBACADAAAAHAAgAQAA-AEAMAIAAPUBACADAAAAHAAgAQAA-AEAMAIAAPUBACAJBQAA3AMAIAYAALcDACAMAAC2AwAg3QEBAAAAAeIBQAAAAAHzAQEAAAAB9AEBAAAAAfUBAQAAAAH2AQEAAAABARcAAPwBACAG3QEBAAAAAeIBQAAAAAHzAQEAAAAB9AEBAAAAAfUBAQAAAAH2AQEAAAABARcAAP4BADABFwAA_gEAMAEAAAADACABAAAACgAgAQAAAAcAIAkFAADbAwAgBgAAtAMAIAwAALMDACDdAQEApwMAIeIBQACpAwAh8wEBAKcDACH0AQEAtQMAIfUBAQC1AwAh9gEBALUDACECAAAA9QEAIBcAAIQCACAG3QEBAKcDACHiAUAAqQMAIfMBAQCnAwAh9AEBALUDACH1AQEAtQMAIfYBAQC1AwAhAgAAABwAIBcAAIYCACACAAAAHAAgFwAAhgIAIAEAAAADACABAAAACgAgAQAAAAcAIAMAAAD1AQAgHgAA_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-gIAMN0BAQDcAgAh9QEBANwCACH3AQgA-wIAIfgBAgDdAgAh-QEBANwCACH6ASAA_AIAIfsBQADeAgAhDQkAAOACACAkAADjAgAgJQAA4wIAICYAAOMCACAnAADjAgAg4wEIAAAAAeQBCAAAAATlAQgAAAAE5gEIAAAAAecBCAAAAAHoAQgAAAAB6QEIAAAAAeoBCAD_AgAhBQkAAOACACAmAAD-AgAgJwAA_gIAIOMBIAAAAAHqASAA_QIAIQUJAADgAgAgJgAA_gIAICcAAP4CACDjASAAAAAB6gEgAP0CACEC4wEgAAAAAeoBIAD-AgAhDQkAAOACACAkAADjAgAgJQAA4wIAICYAAOMCACAnAADjAgAg4wEIAAAAAeQBCAAAAATlAQgAAAAE5gEIAAAAAecBCAAAAAHoAQgAAAAB6QEIAAAAAeoBCAD_AgAhDdoBAACAAwAw2wEAANwBABDcAQAAgAMAMN0BAQDcAgAh9AEBAPICACH1AQEA8gIAIfkBAQDyAgAh_AEBANwCACH9AQEA3AIAIf4BAgDdAgAh_wEBANwCACGAAgEA8gIAIYECQADeAgAhCNoBAACBAwAw2wEAAMIBABDcAQAAgQMAMN0BAQDcAgAh4QECAN0CACGCAgEA3AIAIYMCAQDcAgAhhAIgAPwCACEK2gEAAIIDADDbAQAArAEAENwBAACCAwAw3QEBANwCACHgAQEA3AIAIeEBAgDdAgAh9QEBANwCACGFAgEA3AIAIYYCAQDcAgAhhwIBANwCACEK2gEAAIMDADDbAQAAlgEAENwBAACDAwAw3QEBANwCACHeAQEA3AIAId8BAQDcAgAh4QECAN0CACHiAUAA3gIAIfYBAQDyAgAhiAIBAPICACEI2gEAAIQDADDbAQAAfgAQ3AEAAIQDADDdAQEA3AIAIeEBAgDdAgAh9AEBANwCACGJAgEA3AIAIYoCAQDcAgAhCtoBAACFAwAw2wEAAGgAENwBAACFAwAw3QEBANwCACHeAQEA3AIAId8BAQDcAgAh4QECAN0CACHiAUAA3gIAIfYBAQDyAgAhiAIBAPICACEI2gEAAIYDADDbAQAAUAAQ3AEAAIYDADDdAQEA3AIAId8BAQDcAgAh4QECAN0CACHiAUAA3gIAIYsCAQDcAgAhCwQAAIgDACAPAACJAwAgEQAAigMAINoBAACHAwAw2wEAAD0AENwBAACHAwAw3QEBAOgCACHfAQEA6AIAIeEBAgDvAgAh4gFAAPYCACGLAgEA6AIAIQOMAgAAAwAgjQIAAAMAII4CAAADACADjAIAAAoAII0CAAAKACCOAgAACgAgA4wCAAAHACCNAgAABwAgjgIAAAcAIA0DAACMAwAgBAAAiAMAIA0AAI0DACAPAACJAwAg2gEAAIsDADDbAQAABwAQ3AEAAIsDADDdAQEA6AIAId4BAQDoAgAh3wEBAOgCACHgAQEA6AIAIeEBAgDvAgAh4gFAAPYCACENBAAAiAMAIA8AAIkDACARAACKAwAg2gEAAIcDADDbAQAAPQAQ3AEAAIcDADDdAQEA6AIAId8BAQDoAgAh4QECAO8CACHiAUAA9gIAIYsCAQDoAgAhkQIAAD0AIJICAAA9ACAOBQAA-QIAIAYAAPgCACAMAAD3AgAg2gEAAPUCADDbAQAAHAAQ3AEAAPUCADDdAQEA6AIAIeIBQAD2AgAh8wEBAOgCACH0AQEAkwMAIfUBAQCTAwAh9gEBAJMDACGRAgAAHAAgkgIAABwAIAkMAACPAwAg2gEAAI4DADDbAQAALQAQ3AEAAI4DADDdAQEA6AIAIeEBAgDvAgAh9AEBAOgCACGJAgEA6AIAIYoCAQDoAgAhEQMAAIwDACAFAAD5AgAgDQAAjQMAIA4AAJ8DACAQAAChAwAg2gEAAKADADDbAQAAAwAQ3AEAAKADADDdAQEA6AIAId4BAQDoAgAh3wEBAOgCACHhAQIA7wIAIeIBQAD2AgAh9gEBAJMDACGIAgEAkwMAIZECAAADACCSAgAAAwAgAvQBAQAAAAH9AQEAAAABAvUBAQAAAAH9AQEAAAABDwYAAPgCACAMAAD3AgAg2gEAAJIDADDbAQAAIQAQ3AEAAJIDADDdAQEA6AIAIfQBAQCTAwAh9QEBAJMDACH5AQEAkwMAIfwBAQDoAgAh_QEBAOgCACH-AQIA7wIAIf8BAQDoAgAhgAIBAJMDACGBAkAA9gIAIQvjAQEAAAAB5AEBAAAABeUBAQAAAAXmAQEAAAAB5wEBAAAAAegBAQAAAAHpAQEAAAAB6gEBAPQCACHrAQEAAAAB7AEBAAAAAe0BAQAAAAELBgAAlwMAINoBAACUAwAw2wEAABgAENwBAACUAwAw3QEBAOgCACH1AQEA6AIAIfcBCACVAwAh-AECAO8CACH5AQEA6AIAIfoBIACWAwAh-wFAAPYCACEI4wEIAAAAAeQBCAAAAATlAQgAAAAE5gEIAAAAAecBCAAAAAHoAQgAAAAB6QEIAAAAAeoBCADjAgAhAuMBIAAAAAHqASAA_gIAIRIDAACMAwAgBQAA-QIAIAoAAJ0DACALAACeAwAgDQAAjQMAIA4AAJ8DACDaAQAAnAMAMNsBAAAKABDcAQAAnAMAMN0BAQDoAgAh3gEBAOgCACHfAQEA6AIAIeEBAgDvAgAh4gFAAPYCACH2AQEAkwMAIYgCAQCTAwAhkQIAAAoAIJICAAAKACAJBwAAmQMAINoBAACYAwAw2wEAABMAENwBAACYAwAw3QEBAOgCACHhAQIA7wIAIYICAQDoAgAhgwIBAOgCACGEAiAAlgMAIQ4GAACXAwAgCAAAmwMAINoBAACaAwAw2wEAAA8AENwBAACaAwAw3QEBAOgCACHgAQEA6AIAIeEBAgDvAgAh9QEBAOgCACGFAgEA6AIAIYYCAQDoAgAhhwIBAOgCACGRAgAADwAgkgIAAA8AIAwGAACXAwAgCAAAmwMAINoBAACaAwAw2wEAAA8AENwBAACaAwAw3QEBAOgCACHgAQEA6AIAIeEBAgDvAgAh9QEBAOgCACGFAgEA6AIAIYYCAQDoAgAhhwIBAOgCACEDjAIAABMAII0CAAATACCOAgAAEwAgEAMAAIwDACAFAAD5AgAgCgAAnQMAIAsAAJ4DACANAACNAwAgDgAAnwMAINoBAACcAwAw2wEAAAoAENwBAACcAwAw3QEBAOgCACHeAQEA6AIAId8BAQDoAgAh4QECAO8CACHiAUAA9gIAIfYBAQCTAwAhiAIBAJMDACEDjAIAAA8AII0CAAAPACCOAgAADwAgA4wCAAAYACCNAgAAGAAgjgIAABgAIAOMAgAAIQAgjQIAACEAII4CAAAhACAPAwAAjAMAIAUAAPkCACANAACNAwAgDgAAnwMAIBAAAKEDACDaAQAAoAMAMNsBAAADABDcAQAAoAMAMN0BAQDoAgAh3gEBAOgCACHfAQEA6AIAIeEBAgDvAgAh4gFAAPYCACH2AQEAkwMAIYgCAQCTAwAhA4wCAAAtACCNAgAALQAgjgIAAC0AIAAAAAAAAZkCAQAAAAEFmQICAAAAAZwCAgAAAAGdAgIAAAABngICAAAAAZ8CAgAAAAEBmQJAAAAAAQUeAADTBQAgHwAAgQYAIJMCAADUBQAglAIAAIAGACCXAgAAAQAgCx4AAIsEADAfAACQBAAwkwIAAIwEADCUAgAAjQQAMJUCAACPBAAwlgIAAI8EADCXAgAAjwQAMJgCAACOBAAgmQIAAI8EADCaAgAAkQQAMJsCAACSBAAwCx4AALgDADAfAAC9AwAwkwIAALkDADCUAgAAugMAMJUCAAC8AwAwlgIAALwDADCXAgAAvAMAMJgCAAC7AwAgmQIAALwDADCaAgAAvgMAMJsCAAC_AwAwBx4AAK4DACAfAACxAwAgkwIAAK8DACCUAgAAsAMAIJUCAAAcACCWAgAAHAAglwIAAPUBACAHBgAAtwMAIAwAALYDACDdAQEAAAAB4gFAAAAAAfMBAQAAAAH0AQEAAAAB9QEBAAAAAQIAAAD1AQAgHgAArgMAIAMAAAAcACAeAACuAwAgHwAAsgMAIAkAAAAcACAGAAC0AwAgDAAAswMAIBcAALIDACDdAQEApwMAIeIBQACpAwAh8wEBAKcDACH0AQEAtQMAIfUBAQC1AwAhBwYAALQDACAMAACzAwAg3QEBAKcDACHiAUAAqQMAIfMBAQCnAwAh9AEBALUDACH1AQEAtQMAIQceAAD4BQAgHwAA_gUAIJMCAAD5BQAglAIAAP0FACCVAgAAAwAglgIAAAMAIJcCAAAFACAHHgAA9gUAIB8AAPsFACCTAgAA9wUAIJQCAAD6BQAglQIAAAoAIJYCAAAKACCXAgAADAAgAZkCAQAAAAEDHgAA-AUAIJMCAAD5BQAglwIAAAUAIAMeAAD2BQAgkwIAAPcFACCXAgAADAAgCwMAAIYEACAKAACHBAAgCwAAiAQAIA0AAIkEACAOAACKBAAg3QEBAAAAAd4BAQAAAAHfAQEAAAAB4QECAAAAAeIBQAAAAAGIAgEAAAABAgAAAAwAIB4AAIUEACADAAAADAAgHgAAhQQAIB8AAMIDACABFwAA9QUAMBADAACMAwAgBQAA-QIAIAoAAJ0DACALAACeAwAgDQAAjQMAIA4AAJ8DACDaAQAAnAMAMNsBAAAKABDcAQAAnAMAMN0BAQAAAAHeAQEA6AIAId8BAQDoAgAh4QECAO8CACHiAUAA9gIAIfYBAQCTAwAhiAIBAJMDACECAAAADAAgFwAAwgMAIAIAAADAAwAgFwAAwQMAIAraAQAAvwMAMNsBAADAAwAQ3AEAAL8DADDdAQEA6AIAId4BAQDoAgAh3wEBAOgCACHhAQIA7wIAIeIBQAD2AgAh9gEBAJMDACGIAgEAkwMAIQraAQAAvwMAMNsBAADAAwAQ3AEAAL8DADDdAQEA6AIAId4BAQDoAgAh3wEBAOgCACHhAQIA7wIAIeIBQAD2AgAh9gEBAJMDACGIAgEAkwMAIQbdAQEApwMAId4BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAhiAIBALUDACELAwAAwwMAIAoAAMQDACALAADFAwAgDQAAxgMAIA4AAMcDACDdAQEApwMAId4BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAhiAIBALUDACEFHgAA4gUAIB8AAPMFACCTAgAA4wUAIJQCAADyBQAglwIAAAEAIAseAADrAwAwHwAA8AMAMJMCAADsAwAwlAIAAO0DADCVAgAA7wMAMJYCAADvAwAwlwIAAO8DADCYAgAA7gMAIJkCAADvAwAwmgIAAPEDADCbAgAA8gMAMAseAADdAwAwHwAA4gMAMJMCAADeAwAwlAIAAN8DADCVAgAA4QMAMJYCAADhAwAwlwIAAOEDADCYAgAA4AMAIJkCAADhAwAwmgIAAOMDADCbAgAA5AMAMAceAADWAwAgHwAA2QMAIJMCAADXAwAglAIAANgDACCVAgAAHAAglgIAABwAIJcCAAD1AQAgCx4AAMgDADAfAADNAwAwkwIAAMkDADCUAgAAygMAMJUCAADMAwAwlgIAAMwDADCXAgAAzAMAMJgCAADLAwAgmQIAAMwDADCaAgAAzgMAMJsCAADPAwAwCgwAANUDACDdAQEAAAAB9AEBAAAAAfkBAQAAAAH8AQEAAAAB_QEBAAAAAf4BAgAAAAH_AQEAAAABgAIBAAAAAYECQAAAAAECAAAAIwAgHgAA1AMAIAMAAAAjACAeAADUAwAgHwAA0gMAIAEXAADxBQAwEQYAAPgCACAMAAD3AgAg2gEAAJIDADDbAQAAIQAQ3AEAAJIDADDdAQEAAAAB9AEBAJMDACH1AQEAkwMAIfkBAQCTAwAh_AEBAOgCACH9AQEA6AIAIf4BAgDvAgAh_wEBAOgCACGAAgEAkwMAIYECQAD2AgAhjwIAAJADACCQAgAAkQMAIAIAAAAjACAXAADSAwAgAgAAANADACAXAADRAwAgDdoBAADPAwAw2wEAANADABDcAQAAzwMAMN0BAQDoAgAh9AEBAJMDACH1AQEAkwMAIfkBAQCTAwAh_AEBAOgCACH9AQEA6AIAIf4BAgDvAgAh_wEBAOgCACGAAgEAkwMAIYECQAD2AgAhDdoBAADPAwAw2wEAANADABDcAQAAzwMAMN0BAQDoAgAh9AEBAJMDACH1AQEAkwMAIfkBAQCTAwAh_AEBAOgCACH9AQEA6AIAIf4BAgDvAgAh_wEBAOgCACGAAgEAkwMAIYECQAD2AgAhCd0BAQCnAwAh9AEBALUDACH5AQEAtQMAIfwBAQCnAwAh_QEBAKcDACH-AQIAqAMAIf8BAQCnAwAhgAIBALUDACGBAkAAqQMAIQoMAADTAwAg3QEBAKcDACH0AQEAtQMAIfkBAQC1AwAh_AEBAKcDACH9AQEApwMAIf4BAgCoAwAh_wEBAKcDACGAAgEAtQMAIYECQACpAwAhBx4AAOwFACAfAADvBQAgkwIAAO0FACCUAgAA7gUAIJUCAAADACCWAgAAAwAglwIAAAUAIAoMAADVAwAg3QEBAAAAAfQBAQAAAAH5AQEAAAAB_AEBAAAAAf0BAQAAAAH-AQIAAAAB_wEBAAAAAYACAQAAAAGBAkAAAAABAx4AAOwFACCTAgAA7QUAIJcCAAAFACAHBQAA3AMAIAwAALYDACDdAQEAAAAB4gFAAAAAAfMBAQAAAAH0AQEAAAAB9gEBAAAAAQIAAAD1AQAgHgAA1gMAIAMAAAAcACAeAADWAwAgHwAA2gMAIAkAAAAcACAFAADbAwAgDAAAswMAIBcAANoDACDdAQEApwMAIeIBQACpAwAh8wEBAKcDACH0AQEAtQMAIfYBAQC1AwAhBwUAANsDACAMAACzAwAg3QEBAKcDACHiAUAAqQMAIfMBAQCnAwAh9AEBALUDACH2AQEAtQMAIQceAADnBQAgHwAA6gUAIJMCAADoBQAglAIAAOkFACCVAgAABwAglgIAAAcAIJcCAAA3ACADHgAA5wUAIJMCAADoBQAglwIAADcAIAbdAQEAAAAB9wEIAAAAAfgBAgAAAAH5AQEAAAAB-gEgAAAAAfsBQAAAAAECAAAAGgAgHgAA6gMAIAMAAAAaACAeAADqAwAgHwAA6QMAIAEXAADmBQAwCwYAAJcDACDaAQAAlAMAMNsBAAAYABDcAQAAlAMAMN0BAQAAAAH1AQEA6AIAIfcBCACVAwAh-AECAO8CACH5AQEA6AIAIfoBIACWAwAh-wFAAPYCACECAAAAGgAgFwAA6QMAIAIAAADlAwAgFwAA5gMAIAraAQAA5AMAMNsBAADlAwAQ3AEAAOQDADDdAQEA6AIAIfUBAQDoAgAh9wEIAJUDACH4AQIA7wIAIfkBAQDoAgAh-gEgAJYDACH7AUAA9gIAIQraAQAA5AMAMNsBAADlAwAQ3AEAAOQDADDdAQEA6AIAIfUBAQDoAgAh9wEIAJUDACH4AQIA7wIAIfkBAQDoAgAh-gEgAJYDACH7AUAA9gIAIQbdAQEApwMAIfcBCADnAwAh-AECAKgDACH5AQEApwMAIfoBIADoAwAh-wFAAKkDACEFmQIIAAAAAZwCCAAAAAGdAggAAAABngIIAAAAAZ8CCAAAAAEBmQIgAAAAAQbdAQEApwMAIfcBCADnAwAh-AECAKgDACH5AQEApwMAIfoBIADoAwAh-wFAAKkDACEG3QEBAAAAAfcBCAAAAAH4AQIAAAAB-QEBAAAAAfoBIAAAAAH7AUAAAAABBwgAAIQEACDdAQEAAAAB4AEBAAAAAeEBAgAAAAGFAgEAAAABhgIBAAAAAYcCAQAAAAECAAAAEQAgHgAAgwQAIAMAAAARACAeAACDBAAgHwAA9QMAIAEXAADlBQAwDAYAAJcDACAIAACbAwAg2gEAAJoDADDbAQAADwAQ3AEAAJoDADDdAQEAAAAB4AEBAOgCACHhAQIA7wIAIfUBAQDoAgAhhQIBAOgCACGGAgEA6AIAIYcCAQDoAgAhAgAAABEAIBcAAPUDACACAAAA8wMAIBcAAPQDACAK2gEAAPIDADDbAQAA8wMAENwBAADyAwAw3QEBAOgCACHgAQEA6AIAIeEBAgDvAgAh9QEBAOgCACGFAgEA6AIAIYYCAQDoAgAhhwIBAOgCACEK2gEAAPIDADDbAQAA8wMAENwBAADyAwAw3QEBAOgCACHgAQEA6AIAIeEBAgDvAgAh9QEBAOgCACGFAgEA6AIAIYYCAQDoAgAhhwIBAOgCACEG3QEBAKcDACHgAQEApwMAIeEBAgCoAwAhhQIBAKcDACGGAgEApwMAIYcCAQCnAwAhBwgAAPYDACDdAQEApwMAIeABAQCnAwAh4QECAKgDACGFAgEApwMAIYYCAQCnAwAhhwIBAKcDACELHgAA9wMAMB8AAPwDADCTAgAA-AMAMJQCAAD5AwAwlQIAAPsDADCWAgAA-wMAMJcCAAD7AwAwmAIAAPoDACCZAgAA-wMAMJoCAAD9AwAwmwIAAP4DADAE3QEBAAAAAeEBAgAAAAGDAgEAAAABhAIgAAAAAQIAAAAVACAeAACCBAAgAwAAABUAIB4AAIIEACAfAACBBAAgARcAAOQFADAJBwAAmQMAINoBAACYAwAw2wEAABMAENwBAACYAwAw3QEBAAAAAeEBAgDvAgAhggIBAOgCACGDAgEA6AIAIYQCIACWAwAhAgAAABUAIBcAAIEEACACAAAA_wMAIBcAAIAEACAI2gEAAP4DADDbAQAA_wMAENwBAAD-AwAw3QEBAOgCACHhAQIA7wIAIYICAQDoAgAhgwIBAOgCACGEAiAAlgMAIQjaAQAA_gMAMNsBAAD_AwAQ3AEAAP4DADDdAQEA6AIAIeEBAgDvAgAhggIBAOgCACGDAgEA6AIAIYQCIACWAwAhBN0BAQCnAwAh4QECAKgDACGDAgEApwMAIYQCIADoAwAhBN0BAQCnAwAh4QECAKgDACGDAgEApwMAIYQCIADoAwAhBN0BAQAAAAHhAQIAAAABgwIBAAAAAYQCIAAAAAEHCAAAhAQAIN0BAQAAAAHgAQEAAAAB4QECAAAAAYUCAQAAAAGGAgEAAAABhwIBAAAAAQQeAAD3AwAwkwIAAPgDADCXAgAA-wMAMJgCAAD6AwAgCwMAAIYEACAKAACHBAAgCwAAiAQAIA0AAIkEACAOAACKBAAg3QEBAAAAAd4BAQAAAAHfAQEAAAAB4QECAAAAAeIBQAAAAAGIAgEAAAABAx4AAOIFACCTAgAA4wUAIJcCAAABACAEHgAA6wMAMJMCAADsAwAwlwIAAO8DADCYAgAA7gMAIAQeAADdAwAwkwIAAN4DADCXAgAA4QMAMJgCAADgAwAgAx4AANYDACCTAgAA1wMAIJcCAAD1AQAgBB4AAMgDADCTAgAAyQMAMJcCAADMAwAwmAIAAMsDACAKAwAAtwQAIA0AALkEACAOAAC6BAAgEAAAuAQAIN0BAQAAAAHeAQEAAAAB3wEBAAAAAeEBAgAAAAHiAUAAAAABiAIBAAAAAQIAAAAFACAeAAC2BAAgAwAAAAUAIB4AALYEACAfAACVBAAgARcAAOEFADAPAwAAjAMAIAUAAPkCACANAACNAwAgDgAAnwMAIBAAAKEDACDaAQAAoAMAMNsBAAADABDcAQAAoAMAMN0BAQAAAAHeAQEA6AIAId8BAQDoAgAh4QECAO8CACHiAUAA9gIAIfYBAQCTAwAhiAIBAJMDACECAAAABQAgFwAAlQQAIAIAAACTBAAgFwAAlAQAIAraAQAAkgQAMNsBAACTBAAQ3AEAAJIEADDdAQEA6AIAId4BAQDoAgAh3wEBAOgCACHhAQIA7wIAIeIBQAD2AgAh9gEBAJMDACGIAgEAkwMAIQraAQAAkgQAMNsBAACTBAAQ3AEAAJIEADDdAQEA6AIAId4BAQDoAgAh3wEBAOgCACHhAQIA7wIAIeIBQAD2AgAh9gEBAJMDACGIAgEAkwMAIQbdAQEApwMAId4BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAhiAIBALUDACEKAwAAlgQAIA0AAJgEACAOAACZBAAgEAAAlwQAIN0BAQCnAwAh3gEBAKcDACHfAQEApwMAIeEBAgCoAwAh4gFAAKkDACGIAgEAtQMAIQUeAADVBQAgHwAA3wUAIJMCAADWBQAglAIAAN4FACCXAgAAAQAgCx4AAKoEADAfAACvBAAwkwIAAKsEADCUAgAArAQAMJUCAACuBAAwlgIAAK4EADCXAgAArgQAMJgCAACtBAAgmQIAAK4EADCaAgAAsAQAMJsCAACxBAAwBx4AAKUEACAfAACoBAAgkwIAAKYEACCUAgAApwQAIJUCAAAcACCWAgAAHAAglwIAAPUBACALHgAAmgQAMB8AAJ4EADCTAgAAmwQAMJQCAACcBAAwlQIAAMwDADCWAgAAzAMAMJcCAADMAwAwmAIAAJ0EACCZAgAAzAMAMJoCAACfBAAwmwIAAM8DADAKBgAApAQAIN0BAQAAAAH1AQEAAAAB-QEBAAAAAfwBAQAAAAH9AQEAAAAB_gECAAAAAf8BAQAAAAGAAgEAAAABgQJAAAAAAQIAAAAjACAeAACjBAAgAwAAACMAIB4AAKMEACAfAAChBAAgARcAAN0FADACAAAAIwAgFwAAoQQAIAIAAADQAwAgFwAAoAQAIAndAQEApwMAIfUBAQC1AwAh-QEBALUDACH8AQEApwMAIf0BAQCnAwAh_gECAKgDACH_AQEApwMAIYACAQC1AwAhgQJAAKkDACEKBgAAogQAIN0BAQCnAwAh9QEBALUDACH5AQEAtQMAIfwBAQCnAwAh_QEBAKcDACH-AQIAqAMAIf8BAQCnAwAhgAIBALUDACGBAkAAqQMAIQceAADYBQAgHwAA2wUAIJMCAADZBQAglAIAANoFACCVAgAACgAglgIAAAoAIJcCAAAMACAKBgAApAQAIN0BAQAAAAH1AQEAAAAB-QEBAAAAAfwBAQAAAAH9AQEAAAAB_gECAAAAAf8BAQAAAAGAAgEAAAABgQJAAAAAAQMeAADYBQAgkwIAANkFACCXAgAADAAgBwUAANwDACAGAAC3AwAg3QEBAAAAAeIBQAAAAAHzAQEAAAAB9QEBAAAAAfYBAQAAAAECAAAA9QEAIB4AAKUEACADAAAAHAAgHgAApQQAIB8AAKkEACAJAAAAHAAgBQAA2wMAIAYAALQDACAXAACpBAAg3QEBAKcDACHiAUAAqQMAIfMBAQCnAwAh9QEBALUDACH2AQEAtQMAIQcFAADbAwAgBgAAtAMAIN0BAQCnAwAh4gFAAKkDACHzAQEApwMAIfUBAQC1AwAh9gEBALUDACEE3QEBAAAAAeEBAgAAAAGJAgEAAAABigIBAAAAAQIAAAAvACAeAAC1BAAgAwAAAC8AIB4AALUEACAfAAC0BAAgARcAANcFADAJDAAAjwMAINoBAACOAwAw2wEAAC0AENwBAACOAwAw3QEBAAAAAeEBAgDvAgAh9AEBAOgCACGJAgEA6AIAIYoCAQDoAgAhAgAAAC8AIBcAALQEACACAAAAsgQAIBcAALMEACAI2gEAALEEADDbAQAAsgQAENwBAACxBAAw3QEBAOgCACHhAQIA7wIAIfQBAQDoAgAhiQIBAOgCACGKAgEA6AIAIQjaAQAAsQQAMNsBAACyBAAQ3AEAALEEADDdAQEA6AIAIeEBAgDvAgAh9AEBAOgCACGJAgEA6AIAIYoCAQDoAgAhBN0BAQCnAwAh4QECAKgDACGJAgEApwMAIYoCAQCnAwAhBN0BAQCnAwAh4QECAKgDACGJAgEApwMAIYoCAQCnAwAhBN0BAQAAAAHhAQIAAAABiQIBAAAAAYoCAQAAAAEKAwAAtwQAIA0AALkEACAOAAC6BAAgEAAAuAQAIN0BAQAAAAHeAQEAAAAB3wEBAAAAAeEBAgAAAAHiAUAAAAABiAIBAAAAAQMeAADVBQAgkwIAANYFACCXAgAAAQAgBB4AAKoEADCTAgAAqwQAMJcCAACuBAAwmAIAAK0EACADHgAApQQAIJMCAACmBAAglwIAAPUBACAEHgAAmgQAMJMCAACbBAAwlwIAAMwDADCYAgAAnQQAIAMeAADTBQAgkwIAANQFACCXAgAAAQAgBB4AAIsEADCTAgAAjAQAMJcCAACPBAAwmAIAAI4EACAEHgAAuAMAMJMCAAC5AwAwlwIAALwDADCYAgAAuwMAIAMeAACuAwAgkwIAAK8DACCXAgAA9QEAIAAAAAAAAAAAAAGZAkAAAAABAAAABwMAAKoFACAFAADOBAAgDQAAqwUAIA4AALAFACAQAACxBQAg9gEAAMIEACCIAgAAwgQAIAgDAACqBQAgBQAAzgQAIAoAAK4FACALAACvBQAgDQAAqwUAIA4AALAFACD2AQAAwgQAIIgCAADCBAAgBAMAAKoFACAEAACnBQAgDQAAqwUAIA8AAKgFACAAAAAAAAUeAADOBQAgHwAA0QUAIJMCAADPBQAglAIAANAFACCXAgAADAAgAx4AAM4FACCTAgAAzwUAIJcCAAAMACAAAAAAAAAAAAAABR4AAMkFACAfAADMBQAgkwIAAMoFACCUAgAAywUAIJcCAAARACADHgAAyQUAIJMCAADKBQAglwIAABEAIAAAAAAABR4AAMQFACAfAADHBQAgkwIAAMUFACCUAgAAxgUAIJcCAAAMACADHgAAxAUAIJMCAADFBQAglwIAAAwAIAAAAAAABx4AAL8FACAfAADCBQAgkwIAAMAFACCUAgAAwQUAIJUCAAAHACCWAgAABwAglwIAADcAIAMeAAC_BQAgkwIAAMAFACCXAgAANwAgAAAAAAAFHgAAugUAIB8AAL0FACCTAgAAuwUAIJQCAAC8BQAglwIAAAUAIAMeAAC6BQAgkwIAALsFACCXAgAABQAgAAAAAAAHHgAAtQUAIB8AALgFACCTAgAAtgUAIJQCAAC3BQAglQIAAAcAIJYCAAAHACCXAgAANwAgAx4AALUFACCTAgAAtgUAIJcCAAA3ACAAAAAAAAseAACbBQAwHwAAnwUAMJMCAACcBQAwlAIAAJ0FADCVAgAAjwQAMJYCAACPBAAwlwIAAI8EADCYAgAAngUAIJkCAACPBAAwmgIAAKAFADCbAgAAkgQAMAseAACSBQAwHwAAlgUAMJMCAACTBQAwlAIAAJQFADCVAgAAvAMAMJYCAAC8AwAwlwIAALwDADCYAgAAlQUAIJkCAAC8AwAwmgIAAJcFADCbAgAAvwMAMAseAACGBQAwHwAAiwUAMJMCAACHBQAwlAIAAIgFADCVAgAAigUAMJYCAACKBQAwlwIAAIoFADCYAgAAiQUAIJkCAACKBQAwmgIAAIwFADCbAgAAjQUAMAgEAAC8BAAgDQAAvgQAIA8AAL0EACDdAQEAAAAB3wEBAAAAAeABAQAAAAHhAQIAAAAB4gFAAAAAAQIAAAA3ACAeAACRBQAgAwAAADcAIB4AAJEFACAfAACQBQAgARcAALQFADANAwAAjAMAIAQAAIgDACANAACNAwAgDwAAiQMAINoBAACLAwAw2wEAAAcAENwBAACLAwAw3QEBAAAAAd4BAQDoAgAh3wEBAOgCACHgAQEA6AIAIeEBAgDvAgAh4gFAAPYCACECAAAANwAgFwAAkAUAIAIAAACOBQAgFwAAjwUAIAnaAQAAjQUAMNsBAACOBQAQ3AEAAI0FADDdAQEA6AIAId4BAQDoAgAh3wEBAOgCACHgAQEA6AIAIeEBAgDvAgAh4gFAAPYCACEJ2gEAAI0FADDbAQAAjgUAENwBAACNBQAw3QEBAOgCACHeAQEA6AIAId8BAQDoAgAh4AEBAOgCACHhAQIA7wIAIeIBQAD2AgAhBd0BAQCnAwAh3wEBAKcDACHgAQEApwMAIeEBAgCoAwAh4gFAAKkDACEIBAAAqwMAIA0AAK0DACAPAACsAwAg3QEBAKcDACHfAQEApwMAIeABAQCnAwAh4QECAKgDACHiAUAAqQMAIQgEAAC8BAAgDQAAvgQAIA8AAL0EACDdAQEAAAAB3wEBAAAAAeABAQAAAAHhAQIAAAAB4gFAAAAAAQsFAADvBAAgCgAAhwQAIAsAAIgEACANAACJBAAgDgAAigQAIN0BAQAAAAHfAQEAAAAB4QECAAAAAeIBQAAAAAH2AQEAAAABiAIBAAAAAQIAAAAMACAeAACaBQAgAwAAAAwAIB4AAJoFACAfAACZBQAgARcAALMFADACAAAADAAgFwAAmQUAIAIAAADAAwAgFwAAmAUAIAbdAQEApwMAId8BAQCnAwAh4QECAKgDACHiAUAAqQMAIfYBAQC1AwAhiAIBALUDACELBQAA7gQAIAoAAMQDACALAADFAwAgDQAAxgMAIA4AAMcDACDdAQEApwMAId8BAQCnAwAh4QECAKgDACHiAUAAqQMAIfYBAQC1AwAhiAIBALUDACELBQAA7wQAIAoAAIcEACALAACIBAAgDQAAiQQAIA4AAIoEACDdAQEAAAAB3wEBAAAAAeEBAgAAAAHiAUAAAAAB9gEBAAAAAYgCAQAAAAEKBQAA_QQAIA0AALkEACAOAAC6BAAgEAAAuAQAIN0BAQAAAAHfAQEAAAAB4QECAAAAAeIBQAAAAAH2AQEAAAABiAIBAAAAAQIAAAAFACAeAACjBQAgAwAAAAUAIB4AAKMFACAfAACiBQAgARcAALIFADACAAAABQAgFwAAogUAIAIAAACTBAAgFwAAoQUAIAbdAQEApwMAId8BAQCnAwAh4QECAKgDACHiAUAAqQMAIfYBAQC1AwAhiAIBALUDACEKBQAA_AQAIA0AAJgEACAOAACZBAAgEAAAlwQAIN0BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAh9gEBALUDACGIAgEAtQMAIQoFAAD9BAAgDQAAuQQAIA4AALoEACAQAAC4BAAg3QEBAAAAAd8BAQAAAAHhAQIAAAAB4gFAAAAAAfYBAQAAAAGIAgEAAAABBB4AAJsFADCTAgAAnAUAMJcCAACPBAAwmAIAAJ4FACAEHgAAkgUAMJMCAACTBQAwlwIAALwDADCYAgAAlQUAIAQeAACGBQAwkwIAAIcFADCXAgAAigUAMJgCAACJBQAgAAAAAwQAAKcFACAPAACoBQAgEQAAqQUAIAYFAADOBAAgBgAAzQQAIAwAAMwEACD0AQAAwgQAIPUBAADCBAAg9gEAAMIEACACBgAAzQQAIAgAAK0FACAAAAAAAAbdAQEAAAAB3wEBAAAAAeEBAgAAAAHiAUAAAAAB9gEBAAAAAYgCAQAAAAEG3QEBAAAAAd8BAQAAAAHhAQIAAAAB4gFAAAAAAfYBAQAAAAGIAgEAAAABBd0BAQAAAAHfAQEAAAAB4AEBAAAAAeEBAgAAAAHiAUAAAAABCQMAALsEACANAAC-BAAgDwAAvQQAIN0BAQAAAAHeAQEAAAAB3wEBAAAAAeABAQAAAAHhAQIAAAAB4gFAAAAAAQIAAAA3ACAeAAC1BQAgAwAAAAcAIB4AALUFACAfAAC5BQAgCwAAAAcAIAMAAKoDACANAACtAwAgDwAArAMAIBcAALkFACDdAQEApwMAId4BAQCnAwAh3wEBAKcDACHgAQEApwMAIeEBAgCoAwAh4gFAAKkDACEJAwAAqgMAIA0AAK0DACAPAACsAwAg3QEBAKcDACHeAQEApwMAId8BAQCnAwAh4AEBAKcDACHhAQIAqAMAIeIBQACpAwAhCwMAALcEACAFAAD9BAAgDQAAuQQAIA4AALoEACDdAQEAAAAB3gEBAAAAAd8BAQAAAAHhAQIAAAAB4gFAAAAAAfYBAQAAAAGIAgEAAAABAgAAAAUAIB4AALoFACADAAAAAwAgHgAAugUAIB8AAL4FACANAAAAAwAgAwAAlgQAIAUAAPwEACANAACYBAAgDgAAmQQAIBcAAL4FACDdAQEApwMAId4BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAh9gEBALUDACGIAgEAtQMAIQsDAACWBAAgBQAA_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_AEBAAAAAf0BAQAAAAH-AQIAAAAB_wEBAAAAAYACAQAAAAGBAkAAAAABAwAAAD0AIB4AANUFACAfAADgBQAgCQAAAD0AIA8AAIQFACARAACFBQAgFwAA4AUAIN0BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAhiwIBAKcDACEHDwAAhAUAIBEAAIUFACDdAQEApwMAId8BAQCnAwAh4QECAKgDACHiAUAAqQMAIYsCAQCnAwAhBt0BAQAAAAHeAQEAAAAB3wEBAAAAAeEBAgAAAAHiAUAAAAABiAIBAAAAAQcEAACkBQAgEQAApgUAIN0BAQAAAAHfAQEAAAAB4QECAAAAAeIBQAAAAAGLAgEAAAABAgAAAAEAIB4AAOIFACAE3QEBAAAAAeEBAgAAAAGDAgEAAAABhAIgAAAAAQbdAQEAAAAB4AEBAAAAAeEBAgAAAAGFAgEAAAABhgIBAAAAAYcCAQAAAAEG3QEBAAAAAfcBCAAAAAH4AQIAAAAB-QEBAAAAAfoBIAAAAAH7AUAAAAABCQMAALsEACAEAAC8BAAgDwAAvQQAIN0BAQAAAAHeAQEAAAAB3wEBAAAAAeABAQAAAAHhAQIAAAAB4gFAAAAAAQIAAAA3ACAeAADnBQAgAwAAAAcAIB4AAOcFACAfAADrBQAgCwAAAAcAIAMAAKoDACAEAACrAwAgDwAArAMAIBcAAOsFACDdAQEApwMAId4BAQCnAwAh3wEBAKcDACHgAQEApwMAIeEBAgCoAwAh4gFAAKkDACEJAwAAqgMAIAQAAKsDACAPAACsAwAg3QEBAKcDACHeAQEApwMAId8BAQCnAwAh4AEBAKcDACHhAQIAqAMAIeIBQACpAwAhCwMAALcEACAFAAD9BAAgDQAAuQQAIBAAALgEACDdAQEAAAAB3gEBAAAAAd8BAQAAAAHhAQIAAAAB4gFAAAAAAfYBAQAAAAGIAgEAAAABAgAAAAUAIB4AAOwFACADAAAAAwAgHgAA7AUAIB8AAPAFACANAAAAAwAgAwAAlgQAIAUAAPwEACANAACYBAAgEAAAlwQAIBcAAPAFACDdAQEApwMAId4BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAh9gEBALUDACGIAgEAtQMAIQsDAACWBAAgBQAA_AQAIA0AAJgEACAQAACXBAAg3QEBAKcDACHeAQEApwMAId8BAQCnAwAh4QECAKgDACHiAUAAqQMAIfYBAQC1AwAhiAIBALUDACEJ3QEBAAAAAfQBAQAAAAH5AQEAAAAB_AEBAAAAAf0BAQAAAAH-AQIAAAAB_wEBAAAAAYACAQAAAAGBAkAAAAABAwAAAD0AIB4AAOIFACAfAAD0BQAgCQAAAD0AIAQAAIMFACARAACFBQAgFwAA9AUAIN0BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAhiwIBAKcDACEHBAAAgwUAIBEAAIUFACDdAQEApwMAId8BAQCnAwAh4QECAKgDACHiAUAAqQMAIYsCAQCnAwAhBt0BAQAAAAHeAQEAAAAB3wEBAAAAAeEBAgAAAAHiAUAAAAABiAIBAAAAAQwDAACGBAAgBQAA7wQAIAoAAIcEACALAACIBAAgDgAAigQAIN0BAQAAAAHeAQEAAAAB3wEBAAAAAeEBAgAAAAHiAUAAAAAB9gEBAAAAAYgCAQAAAAECAAAADAAgHgAA9gUAIAsDAAC3BAAgBQAA_QQAIA4AALoEACAQAAC4BAAg3QEBAAAAAd4BAQAAAAHfAQEAAAAB4QECAAAAAeIBQAAAAAH2AQEAAAABiAIBAAAAAQIAAAAFACAeAAD4BQAgAwAAAAoAIB4AAPYFACAfAAD8BQAgDgAAAAoAIAMAAMMDACAFAADuBAAgCgAAxAMAIAsAAMUDACAOAADHAwAgFwAA_AUAIN0BAQCnAwAh3gEBAKcDACHfAQEApwMAIeEBAgCoAwAh4gFAAKkDACH2AQEAtQMAIYgCAQC1AwAhDAMAAMMDACAFAADuBAAgCgAAxAMAIAsAAMUDACAOAADHAwAg3QEBAKcDACHeAQEApwMAId8BAQCnAwAh4QECAKgDACHiAUAAqQMAIfYBAQC1AwAhiAIBALUDACEDAAAAAwAgHgAA-AUAIB8AAP8FACANAAAAAwAgAwAAlgQAIAUAAPwEACAOAACZBAAgEAAAlwQAIBcAAP8FACDdAQEApwMAId4BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAh9gEBALUDACGIAgEAtQMAIQsDAACWBAAgBQAA_AQAIA4AAJkEACAQAACXBAAg3QEBAKcDACHeAQEApwMAId8BAQCnAwAh4QECAKgDACHiAUAAqQMAIfYBAQC1AwAhiAIBALUDACEDAAAAPQAgHgAA0wUAIB8AAIIGACAJAAAAPQAgBAAAgwUAIA8AAIQFACAXAACCBgAg3QEBAKcDACHfAQEApwMAIeEBAgCoAwAh4gFAAKkDACGLAgEApwMAIQcEAACDBQAgDwAAhAUAIN0BAQCnAwAh3wEBAKcDACHhAQIAqAMAIeIBQACpAwAhiwIBAKcDACEEBAYCCQAPDzUEETgDBgMAAQUIAwkADg0xCQ4yChAwDQUDAAEECQIJAAwNKgkPDQQHAwABBQ4DCQALChIFCxsIDR0JDiQKAwYABAgWBgkABwEHAAUBCBcAAQYABAMFIAMGHwQMHgICBiYEDCUCAwonAAsoAA4pAAIEKwAPLAABDAACAg40ABAzAAMEOQAPOgAROwAAAAAFCQAUJAAVJQAWJgAXJwAYAAAAAAAFCQAUJAAVJQAWJgAXJwAYAgMAAQVdAwIDAAEFYwMFCQAdJAAeJQAfJgAgJwAhAAAAAAAFCQAdJAAeJQAfJgAgJwAhAQwAAgEMAAIFCQAmJAAnJQAoJgApJwAqAAAAAAAFCQAmJAAnJQAoJgApJwAqAgMAAQWLAQMCAwABBZEBAwUJAC8kADAlADEmADInADMAAAAAAAUJAC8kADAlADEmADInADMBBgAEAQYABAUJADgkADklADomADsnADwAAAAAAAUJADgkADklADomADsnADwBBwAFAQcABQUJAEEkAEIlAEMmAEQnAEUAAAAAAAUJAEEkAEIlAEMmAEQnAEUCBtABBAzPAQICBtcBBAzWAQIFCQBKJABLJQBMJgBNJwBOAAAAAAAFCQBKJABLJQBMJgBNJwBOAQYABAEGAAQFCQBTJABUJQBVJgBWJwBXAAAAAAAFCQBTJABUJQBVJgBWJwBXAwWDAgMGggIEDIECAgMFiwIDBooCBAyJAgIDCQBcJgBdJwBeAAAAAwkAXCYAXScAXgAAAAUJAGQkAGUlAGYmAGcnAGgAAAAAAAUJAGQkAGUlAGYmAGcnAGgAAAADCQBuJgBvJwBwAAAAAwkAbiYAbycAcAEDAAEBAwABBQkAdSQAdiUAdyYAeCcAeQAAAAAABQkAdSQAdiUAdyYAeCcAeRICARM8ARQ_ARVAARZBARhDARlFEBpGERtIARxKEB1LEiBMASFNASJOEChREylSGSpTAitUAixVAi1WAi5XAi9ZAjBbEDFcGjJfAjNhEDRiGzVkAjZlAjdmEDhpHDlqIjprDTtsDTxtDT1uDT5vDT9xDUBzEEF0I0J2DUN4EER5JEV6DUZ7DUd8EEh_JUmAAStKgQEES4IBBEyDAQRNhAEEToUBBE-HAQRQiQEQUYoBLFKNAQRTjwEQVJABLVWSAQRWkwEEV5QBEFiXAS5ZmAE0WpkBBVuaAQVcmwEFXZwBBV6dAQVfnwEFYKEBEGGiATVipAEFY6YBEGSnATZlqAEFZqkBBWeqARBorQE3aa4BPWqvAQZrsAEGbLEBBm2yAQZuswEGb7UBBnC3ARBxuAE-croBBnO8ARB0vQE_db4BBna_AQZ3wAEQeMMBQHnEAUZ6xQEKe8YBCnzHAQp9yAEKfskBCn_LAQqAAc0BEIEBzgFHggHSAQqDAdQBEIQB1QFIhQHYAQqGAdkBCocB2gEQiAHdAUmJAd4BT4oB3wEIiwHgAQiMAeEBCI0B4gEIjgHjAQiPAeUBCJAB5wEQkQHoAVCSAeoBCJMB7AEQlAHtAVGVAe4BCJYB7wEIlwHwARCYAfMBUpkB9AFYmgH2AQmbAfcBCZwB-QEJnQH6AQmeAfsBCZ8B_QEJoAH_ARChAYACWaIBhQIJowGHAhCkAYgCWqUBjAIJpgGNAgmnAY4CEKgBkQJbqQGSAl-qAZQCYKsBlQJgrAGYAmCtAZkCYK4BmgJgrwGcAmCwAZ4CELEBnwJhsgGhAmCzAaMCELQBpAJitQGlAmC2AaYCYLcBpwIQuAGqAmO5AasCaboBrQJquwGuAmq8AbECar0BsgJqvgGzAmq_AbUCasABtwIQwQG4AmvCAboCasMBvAIQxAG9AmzFAb4CasYBvwJqxwHAAhDIAcMCbckBxAJxygHFAgPLAcYCA8wBxwIDzQHIAgPOAckCA88BywID0AHNAhDRAc4CctIB0AID0wHSAhDUAdMCc9UB1AID1gHVAgPXAdYCENgB2QJ02QHaAno" + strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"class\",\"decks\",\"group\",\"quizSet\",\"question\",\"options\",\"_count\",\"questions\",\"attempts\",\"deck\",\"shareLink\",\"progress\",\"quizSets\",\"cards\",\"materialGroups\",\"Class.findUnique\",\"Class.findUniqueOrThrow\",\"Class.findFirst\",\"Class.findFirstOrThrow\",\"Class.findMany\",\"data\",\"Class.createOne\",\"Class.createMany\",\"Class.createManyAndReturn\",\"Class.updateOne\",\"Class.updateMany\",\"Class.updateManyAndReturn\",\"create\",\"update\",\"Class.upsertOne\",\"Class.deleteOne\",\"Class.deleteMany\",\"having\",\"_avg\",\"_sum\",\"_min\",\"_max\",\"Class.groupBy\",\"Class.aggregate\",\"Deck.findUnique\",\"Deck.findUniqueOrThrow\",\"Deck.findFirst\",\"Deck.findFirstOrThrow\",\"Deck.findMany\",\"Deck.createOne\",\"Deck.createMany\",\"Deck.createManyAndReturn\",\"Deck.updateOne\",\"Deck.updateMany\",\"Deck.updateManyAndReturn\",\"Deck.upsertOne\",\"Deck.deleteOne\",\"Deck.deleteMany\",\"Deck.groupBy\",\"Deck.aggregate\",\"Flashcard.findUnique\",\"Flashcard.findUniqueOrThrow\",\"Flashcard.findFirst\",\"Flashcard.findFirstOrThrow\",\"Flashcard.findMany\",\"Flashcard.createOne\",\"Flashcard.createMany\",\"Flashcard.createManyAndReturn\",\"Flashcard.updateOne\",\"Flashcard.updateMany\",\"Flashcard.updateManyAndReturn\",\"Flashcard.upsertOne\",\"Flashcard.deleteOne\",\"Flashcard.deleteMany\",\"Flashcard.groupBy\",\"Flashcard.aggregate\",\"QuizSet.findUnique\",\"QuizSet.findUniqueOrThrow\",\"QuizSet.findFirst\",\"QuizSet.findFirstOrThrow\",\"QuizSet.findMany\",\"QuizSet.createOne\",\"QuizSet.createMany\",\"QuizSet.createManyAndReturn\",\"QuizSet.updateOne\",\"QuizSet.updateMany\",\"QuizSet.updateManyAndReturn\",\"QuizSet.upsertOne\",\"QuizSet.deleteOne\",\"QuizSet.deleteMany\",\"QuizSet.groupBy\",\"QuizSet.aggregate\",\"Question.findUnique\",\"Question.findUniqueOrThrow\",\"Question.findFirst\",\"Question.findFirstOrThrow\",\"Question.findMany\",\"Question.createOne\",\"Question.createMany\",\"Question.createManyAndReturn\",\"Question.updateOne\",\"Question.updateMany\",\"Question.updateManyAndReturn\",\"Question.upsertOne\",\"Question.deleteOne\",\"Question.deleteMany\",\"Question.groupBy\",\"Question.aggregate\",\"AnswerOption.findUnique\",\"AnswerOption.findUniqueOrThrow\",\"AnswerOption.findFirst\",\"AnswerOption.findFirstOrThrow\",\"AnswerOption.findMany\",\"AnswerOption.createOne\",\"AnswerOption.createMany\",\"AnswerOption.createManyAndReturn\",\"AnswerOption.updateOne\",\"AnswerOption.updateMany\",\"AnswerOption.updateManyAndReturn\",\"AnswerOption.upsertOne\",\"AnswerOption.deleteOne\",\"AnswerOption.deleteMany\",\"AnswerOption.groupBy\",\"AnswerOption.aggregate\",\"StudyProgress.findUnique\",\"StudyProgress.findUniqueOrThrow\",\"StudyProgress.findFirst\",\"StudyProgress.findFirstOrThrow\",\"StudyProgress.findMany\",\"StudyProgress.createOne\",\"StudyProgress.createMany\",\"StudyProgress.createManyAndReturn\",\"StudyProgress.updateOne\",\"StudyProgress.updateMany\",\"StudyProgress.updateManyAndReturn\",\"StudyProgress.upsertOne\",\"StudyProgress.deleteOne\",\"StudyProgress.deleteMany\",\"StudyProgress.groupBy\",\"StudyProgress.aggregate\",\"QuizAttempt.findUnique\",\"QuizAttempt.findUniqueOrThrow\",\"QuizAttempt.findFirst\",\"QuizAttempt.findFirstOrThrow\",\"QuizAttempt.findMany\",\"QuizAttempt.createOne\",\"QuizAttempt.createMany\",\"QuizAttempt.createManyAndReturn\",\"QuizAttempt.updateOne\",\"QuizAttempt.updateMany\",\"QuizAttempt.updateManyAndReturn\",\"QuizAttempt.upsertOne\",\"QuizAttempt.deleteOne\",\"QuizAttempt.deleteMany\",\"QuizAttempt.groupBy\",\"QuizAttempt.aggregate\",\"ShareLink.findUnique\",\"ShareLink.findUniqueOrThrow\",\"ShareLink.findFirst\",\"ShareLink.findFirstOrThrow\",\"ShareLink.findMany\",\"ShareLink.createOne\",\"ShareLink.createMany\",\"ShareLink.createManyAndReturn\",\"ShareLink.updateOne\",\"ShareLink.updateMany\",\"ShareLink.updateManyAndReturn\",\"ShareLink.upsertOne\",\"ShareLink.deleteOne\",\"ShareLink.deleteMany\",\"ShareLink.groupBy\",\"ShareLink.aggregate\",\"AuthSecurity.findUnique\",\"AuthSecurity.findUniqueOrThrow\",\"AuthSecurity.findFirst\",\"AuthSecurity.findFirstOrThrow\",\"AuthSecurity.findMany\",\"AuthSecurity.createOne\",\"AuthSecurity.createMany\",\"AuthSecurity.createManyAndReturn\",\"AuthSecurity.updateOne\",\"AuthSecurity.updateMany\",\"AuthSecurity.updateManyAndReturn\",\"AuthSecurity.upsertOne\",\"AuthSecurity.deleteOne\",\"AuthSecurity.deleteMany\",\"AuthSecurity.groupBy\",\"AuthSecurity.aggregate\",\"Setting.findUnique\",\"Setting.findUniqueOrThrow\",\"Setting.findFirst\",\"Setting.findFirstOrThrow\",\"Setting.findMany\",\"Setting.createOne\",\"Setting.createMany\",\"Setting.createManyAndReturn\",\"Setting.updateOne\",\"Setting.updateMany\",\"Setting.updateManyAndReturn\",\"Setting.upsertOne\",\"Setting.deleteOne\",\"Setting.deleteMany\",\"Setting.groupBy\",\"Setting.aggregate\",\"StudyActivity.findUnique\",\"StudyActivity.findUniqueOrThrow\",\"StudyActivity.findFirst\",\"StudyActivity.findFirstOrThrow\",\"StudyActivity.findMany\",\"StudyActivity.createOne\",\"StudyActivity.createMany\",\"StudyActivity.createManyAndReturn\",\"StudyActivity.updateOne\",\"StudyActivity.updateMany\",\"StudyActivity.updateManyAndReturn\",\"StudyActivity.upsertOne\",\"StudyActivity.deleteOne\",\"StudyActivity.deleteMany\",\"StudyActivity.groupBy\",\"StudyActivity.aggregate\",\"MaterialGroup.findUnique\",\"MaterialGroup.findUniqueOrThrow\",\"MaterialGroup.findFirst\",\"MaterialGroup.findFirstOrThrow\",\"MaterialGroup.findMany\",\"MaterialGroup.createOne\",\"MaterialGroup.createMany\",\"MaterialGroup.createManyAndReturn\",\"MaterialGroup.updateOne\",\"MaterialGroup.updateMany\",\"MaterialGroup.updateManyAndReturn\",\"MaterialGroup.upsertOne\",\"MaterialGroup.deleteOne\",\"MaterialGroup.deleteMany\",\"MaterialGroup.groupBy\",\"MaterialGroup.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"classId\",\"name\",\"type\",\"sortOrder\",\"createdAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"contains\",\"startsWith\",\"endsWith\",\"occurredAt\",\"key\",\"value\",\"failedAttempts\",\"lockedUntil\",\"lastAttemptAt\",\"targetType\",\"deckId\",\"quizSetId\",\"groupId\",\"score\",\"maxScore\",\"answersJson\",\"isPartialRetake\",\"completedAt\",\"contentType\",\"mode\",\"currentIndex\",\"orderJson\",\"cardResultsJson\",\"updatedAt\",\"questionId\",\"text\",\"isCorrect\",\"prompt\",\"rationale\",\"category\",\"description\",\"front\",\"back\",\"slug\",\"every\",\"some\",\"none\",\"deckId_mode\",\"quizSetId_mode\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"disconnect\",\"delete\",\"connect\",\"createMany\",\"set\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), + graph: "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-QQAICQAAPoEACAlAAD9BAAgJgAA_AQAICcAAPsEACAI6gEAAJwDADDrAQAAwgEAEOwBAACcAwAw7QEBAPUCACHxAQIA9gIAIZMCAQD1AgAhlAIBAPUCACGVAiAAlwMAIQMAAAATACABAADBAQAwIwAAwgEAIAMAAAATACABAAAUADACAAAVACABAAAAIwAgAQAAACMAIAMAAAAhACABAAAiADACAAAjACADAAAAIQAgAQAAIgAwAgAAIwAgAwAAACEAIAEAACIAMAIAACMAIAwGAAC_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_AQEAAAABgAIBAIEDACEBAAAArAIAIAEAAACsAgAgBeoBAACEAwAw6wEAAK8CABDsAQAAhAMAMP8BAQCBAwAhgAIBAIEDACEAAwAAAK8CACABAACwAgAwAgAArAIAIAMAAACvAgAgAQAAsAIAMAIAAKwCACADAAAArwIAIAEAALACADACAACsAgAgAv8BAQAAAAGAAgEAAAABARcAALQCACAC_wEBAAAAAYACAQAAAAEBFwAAtgIAMAEXAAC2AgAwAv8BAQDCAwAhgAIBAMIDACECAAAArAIAIBcAALkCACAC_wEBAMIDACGAAgEAwgMAIQIAAACvAgAgFwAAuwIAIAIAAACvAgAgFwAAuwIAIAMAAACsAgAgHgAAtAIAIB8AALkCACABAAAArAIAIAEAAACvAgAgAwkAAN0EACAmAADfBAAgJwAA3gQAIAXqAQAAgwMAMOsBAADCAgAQ7AEAAIMDADD_AQEA9QIAIYACAQD1AgAhAwAAAK8CACABAADBAgAwIwAAwgIAIAMAAACvAgAgAQAAsAIAMAIAAKwCACAG6gEAAIADADDrAQAAyAIAEOwBAACAAwAw7QEBAAAAAfABAQCBAwAh_gFAAIIDACEBAAAAxQIAIAEAAADFAgAgBuoBAACAAwAw6wEAAMgCABDsAQAAgAMAMO0BAQCBAwAh8AEBAIEDACH-AUAAggMAIQADAAAAyAIAIAEAAMkCADACAADFAgAgAwAAAMgCACABAADJAgAwAgAAxQIAIAMAAADIAgAgAQAAyQIAMAIAAMUCACAD7QEBAAAAAfABAQAAAAH-AUAAAAABARcAAM0CACAD7QEBAAAAAfABAQAAAAH-AUAAAAABARcAAM8CADABFwAAzwIAMAPtAQEAwgMAIfABAQDCAwAh_gFAAMQDACECAAAAxQIAIBcAANICACAD7QEBAMIDACHwAQEAwgMAIf4BQADEAwAhAgAAAMgCACAXAADUAgAgAgAAAMgCACAXAADUAgAgAwAAAMUCACAeAADNAgAgHwAA0gIAIAEAAADFAgAgAQAAAMgCACADCQAA2gQAICYAANwEACAnAADbBAAgBuoBAAD_AgAw6wEAANsCABDsAQAA_wIAMO0BAQD1AgAh8AEBAPUCACH-AUAA9wIAIQMAAADIAgAgAQAA2gIAMCMAANsCACADAAAAyAIAIAEAAMkCADACAADFAgAgAQAAADcAIAEAAAA3ACADAAAABwAgAQAANgAwAgAANwAgAwAAAAcAIAEAADYAMAIAADcAIAMAAAAHACABAAA2ADACAAA3ACAKAwAA1gQAIAQAANcEACANAADZBAAgDwAA2AQAIO0BAQAAAAHuAQEAAAAB7wEBAAAAAfABAQAAAAHxAQIAAAAB8gFAAAAAAQEXAADjAgAgBu0BAQAAAAHuAQEAAAAB7wEBAAAAAfABAQAAAAHxAQIAAAAB8gFAAAAAAQEXAADlAgAwARcAAOUCADAKAwAAxQMAIAQAAMYDACANAADIAwAgDwAAxwMAIO0BAQDCAwAh7gEBAMIDACHvAQEAwgMAIfABAQDCAwAh8QECAMMDACHyAUAAxAMAIQIAAAA3ACAXAADoAgAgBu0BAQDCAwAh7gEBAMIDACHvAQEAwgMAIfABAQDCAwAh8QECAMMDACHyAUAAxAMAIQIAAAAHACAXAADqAgAgAgAAAAcAIBcAAOoCACADAAAANwAgHgAA4wIAIB8AAOgCACABAAAANwAgAQAAAAcAIAUJAAC9AwAgJAAAvgMAICUAAMEDACAmAADAAwAgJwAAvwMAIAnqAQAA9AIAMOsBAADxAgAQ7AEAAPQCADDtAQEA9QIAIe4BAQD1AgAh7wEBAPUCACHwAQEA9QIAIfEBAgD2AgAh8gFAAPcCACEDAAAABwAgAQAA8AIAMCMAAPECACADAAAABwAgAQAANgAwAgAANwAgCeoBAAD0AgAw6wEAAPECABDsAQAA9AIAMO0BAQD1AgAh7gEBAPUCACHvAQEA9QIAIfABAQD1AgAh8QECAPYCACHyAUAA9wIAIQ4JAAD5AgAgJgAA_gIAICcAAP4CACDzAQEAAAAB9AEBAAAABPUBAQAAAAT2AQEAAAAB9wEBAAAAAfgBAQAAAAH5AQEAAAAB-gEBAP0CACH7AQEAAAAB_AEBAAAAAf0BAQAAAAENCQAA-QIAICQAAPwCACAlAAD5AgAgJgAA-QIAICcAAPkCACDzAQIAAAAB9AECAAAABPUBAgAAAAT2AQIAAAAB9wECAAAAAfgBAgAAAAH5AQIAAAAB-gECAPsCACELCQAA-QIAICYAAPoCACAnAAD6AgAg8wFAAAAAAfQBQAAAAAT1AUAAAAAE9gFAAAAAAfcBQAAAAAH4AUAAAAAB-QFAAAAAAfoBQAD4AgAhCwkAAPkCACAmAAD6AgAgJwAA-gIAIPMBQAAAAAH0AUAAAAAE9QFAAAAABPYBQAAAAAH3AUAAAAAB-AFAAAAAAfkBQAAAAAH6AUAA-AIAIQjzAQIAAAAB9AECAAAABPUBAgAAAAT2AQIAAAAB9wECAAAAAfgBAgAAAAH5AQIAAAAB-gECAPkCACEI8wFAAAAAAfQBQAAAAAT1AUAAAAAE9gFAAAAAAfcBQAAAAAH4AUAAAAAB-QFAAAAAAfoBQAD6AgAhDQkAAPkCACAkAAD8AgAgJQAA-QIAICYAAPkCACAnAAD5AgAg8wECAAAAAfQBAgAAAAT1AQIAAAAE9gECAAAAAfcBAgAAAAH4AQIAAAAB-QECAAAAAfoBAgD7AgAhCPMBCAAAAAH0AQgAAAAE9QEIAAAABPYBCAAAAAH3AQgAAAAB-AEIAAAAAfkBCAAAAAH6AQgA_AIAIQ4JAAD5AgAgJgAA_gIAICcAAP4CACDzAQEAAAAB9AEBAAAABPUBAQAAAAT2AQEAAAAB9wEBAAAAAfgBAQAAAAH5AQEAAAAB-gEBAP0CACH7AQEAAAAB_AEBAAAAAf0BAQAAAAEL8wEBAAAAAfQBAQAAAAT1AQEAAAAE9gEBAAAAAfcBAQAAAAH4AQEAAAAB-QEBAAAAAfoBAQD-AgAh-wEBAAAAAfwBAQAAAAH9AQEAAAABBuoBAAD_AgAw6wEAANsCABDsAQAA_wIAMO0BAQD1AgAh8AEBAPUCACH-AUAA9wIAIQbqAQAAgAMAMOsBAADIAgAQ7AEAAIADADDtAQEAgQMAIfABAQCBAwAh_gFAAIIDACEL8wEBAAAAAfQBAQAAAAT1AQEAAAAE9gEBAAAAAfcBAQAAAAH4AQEAAAAB-QEBAAAAAfoBAQD-AgAh-wEBAAAAAfwBAQAAAAH9AQEAAAABCPMBQAAAAAH0AUAAAAAE9QFAAAAABPYBQAAAAAH3AUAAAAAB-AFAAAAAAfkBQAAAAAH6AUAA-gIAIQXqAQAAgwMAMOsBAADCAgAQ7AEAAIMDADD_AQEA9QIAIYACAQD1AgAhBeoBAACEAwAw6wEAAK8CABDsAQAAhAMAMP8BAQCBAwAhgAIBAIEDACEH6gEAAIUDADDrAQAAqQIAEOwBAACFAwAw7QECAPYCACGBAgIA9gIAIYICQACGAwAhgwJAAIYDACELCQAAiAMAICYAAIkDACAnAACJAwAg8wFAAAAAAfQBQAAAAAX1AUAAAAAF9gFAAAAAAfcBQAAAAAH4AUAAAAAB-QFAAAAAAfoBQACHAwAhCwkAAIgDACAmAACJAwAgJwAAiQMAIPMBQAAAAAH0AUAAAAAF9QFAAAAABfYBQAAAAAH3AUAAAAAB-AFAAAAAAfkBQAAAAAH6AUAAhwMAIQjzAQIAAAAB9AECAAAABfUBAgAAAAX2AQIAAAAB9wECAAAAAfgBAgAAAAH5AQIAAAAB-gECAIgDACEI8wFAAAAAAfQBQAAAAAX1AUAAAAAF9gFAAAAAAfcBQAAAAAH4AUAAAAAB-QFAAAAAAfoBQACJAwAhB-oBAACKAwAw6wEAAJYCABDsAQAAigMAMO0BAgCLAwAhgQICAIsDACGCAkAAjAMAIYMCQACMAwAhCPMBAgAAAAH0AQIAAAAE9QECAAAABPYBAgAAAAH3AQIAAAAB-AECAAAAAfkBAgAAAAH6AQIA-QIAIQjzAUAAAAAB9AFAAAAABfUBQAAAAAX2AUAAAAAB9wFAAAAAAfgBQAAAAAH5AUAAAAAB-gFAAIkDACEJ6gEAAI0DADDrAQAAkAIAEOwBAACNAwAw7QEBAPUCACHyAUAA9wIAIYQCAQD1AgAhhQIBAI4DACGGAgEAjgMAIYcCAQCOAwAhDgkAAIgDACAmAACQAwAgJwAAkAMAIPMBAQAAAAH0AQEAAAAF9QEBAAAABfYBAQAAAAH3AQEAAAAB-AEBAAAAAfkBAQAAAAH6AQEAjwMAIfsBAQAAAAH8AQEAAAAB_QEBAAAAAQ4JAACIAwAgJgAAkAMAICcAAJADACDzAQEAAAAB9AEBAAAABfUBAQAAAAX2AQEAAAAB9wEBAAAAAfgBAQAAAAH5AQEAAAAB-gEBAI8DACH7AQEAAAAB_AEBAAAAAf0BAQAAAAEL8wEBAAAAAfQBAQAAAAX1AQEAAAAF9gEBAAAAAfcBAQAAAAH4AQEAAAAB-QEBAAAAAfoBAQCQAwAh-wEBAAAAAfwBAQAAAAH9AQEAAAABDAUAAJQDACAGAACTAwAgDAAAkgMAIOoBAACRAwAw6wEAABwAEOwBAACRAwAw7QEBAIEDACHyAUAAggMAIYQCAQCBAwAhhQIBAK4DACGGAgEArgMAIYcCAQCuAwAhEQMAAKcDACAFAACUAwAgDQAAqAMAIA4AALoDACAQAAC8AwAg6gEAALsDADDrAQAAAwAQ7AEAALsDADDtAQEAgQMAIe4BAQCBAwAh7wEBAIEDACHxAQIAiwMAIfIBQACCAwAhhwIBAK4DACGZAgEArgMAIaICAAADACCjAgAAAwAgEgMAAKcDACAFAACUAwAgCgAAuAMAIAsAALkDACANAACoAwAgDgAAugMAIOoBAAC3AwAw6wEAAAoAEOwBAAC3AwAw7QEBAIEDACHuAQEAgQMAIe8BAQCBAwAh8QECAIsDACHyAUAAggMAIYcCAQCuAwAhmQIBAK4DACGiAgAACgAgowIAAAoAIA8DAACnAwAgBAAAowMAIA0AAKgDACAPAACkAwAg6gEAAKYDADDrAQAABwAQ7AEAAKYDADDtAQEAgQMAIe4BAQCBAwAh7wEBAIEDACHwAQEAgQMAIfEBAgCLAwAh8gFAAIIDACGiAgAABwAgowIAAAcAIArqAQAAlQMAMOsBAADyAQAQ7AEAAJUDADDtAQEA9QIAIYYCAQD1AgAhiAIIAJYDACGJAgIA9gIAIYoCAQD1AgAhiwIgAJcDACGMAkAA9wIAIQ0JAAD5AgAgJAAA_AIAICUAAPwCACAmAAD8AgAgJwAA_AIAIPMBCAAAAAH0AQgAAAAE9QEIAAAABPYBCAAAAAH3AQgAAAAB-AEIAAAAAfkBCAAAAAH6AQgAmgMAIQUJAAD5AgAgJgAAmQMAICcAAJkDACDzASAAAAAB-gEgAJgDACEFCQAA-QIAICYAAJkDACAnAACZAwAg8wEgAAAAAfoBIACYAwAhAvMBIAAAAAH6ASAAmQMAIQ0JAAD5AgAgJAAA_AIAICUAAPwCACAmAAD8AgAgJwAA_AIAIPMBCAAAAAH0AQgAAAAE9QEIAAAABPYBCAAAAAH3AQgAAAAB-AEIAAAAAfkBCAAAAAH6AQgAmgMAIQ3qAQAAmwMAMOsBAADcAQAQ7AEAAJsDADDtAQEA9QIAIYUCAQCOAwAhhgIBAI4DACGKAgEAjgMAIY0CAQD1AgAhjgIBAPUCACGPAgIA9gIAIZACAQD1AgAhkQIBAI4DACGSAkAA9wIAIQjqAQAAnAMAMOsBAADCAQAQ7AEAAJwDADDtAQEA9QIAIfEBAgD2AgAhkwIBAPUCACGUAgEA9QIAIZUCIACXAwAhCuoBAACdAwAw6wEAAKwBABDsAQAAnQMAMO0BAQD1AgAh8AEBAPUCACHxAQIA9gIAIYYCAQD1AgAhlgIBAPUCACGXAgEA9QIAIZgCAQD1AgAhCuoBAACeAwAw6wEAAJYBABDsAQAAngMAMO0BAQD1AgAh7gEBAPUCACHvAQEA9QIAIfEBAgD2AgAh8gFAAPcCACGHAgEAjgMAIZkCAQCOAwAhCOoBAACfAwAw6wEAAH4AEOwBAACfAwAw7QEBAPUCACHxAQIA9gIAIYUCAQD1AgAhmgIBAPUCACGbAgEA9QIAIQrqAQAAoAMAMOsBAABoABDsAQAAoAMAMO0BAQD1AgAh7gEBAPUCACHvAQEA9QIAIfEBAgD2AgAh8gFAAPcCACGHAgEAjgMAIZkCAQCOAwAhCOoBAAChAwAw6wEAAFAAEOwBAAChAwAw7QEBAPUCACHvAQEA9QIAIfEBAgD2AgAh8gFAAPcCACGcAgEA9QIAIQsEAACjAwAgDwAApAMAIBEAAKUDACDqAQAAogMAMOsBAAA9ABDsAQAAogMAMO0BAQCBAwAh7wEBAIEDACHxAQIAiwMAIfIBQACCAwAhnAIBAIEDACEDnQIAAAMAIJ4CAAADACCfAgAAAwAgA50CAAAKACCeAgAACgAgnwIAAAoAIAOdAgAABwAgngIAAAcAIJ8CAAAHACANAwAApwMAIAQAAKMDACANAACoAwAgDwAApAMAIOoBAACmAwAw6wEAAAcAEOwBAACmAwAw7QEBAIEDACHuAQEAgQMAIe8BAQCBAwAh8AEBAIEDACHxAQIAiwMAIfIBQACCAwAhDQQAAKMDACAPAACkAwAgEQAApQMAIOoBAACiAwAw6wEAAD0AEOwBAACiAwAw7QEBAIEDACHvAQEAgQMAIfEBAgCLAwAh8gFAAIIDACGcAgEAgQMAIaICAAA9ACCjAgAAPQAgDgUAAJQDACAGAACTAwAgDAAAkgMAIOoBAACRAwAw6wEAABwAEOwBAACRAwAw7QEBAIEDACHyAUAAggMAIYQCAQCBAwAhhQIBAK4DACGGAgEArgMAIYcCAQCuAwAhogIAABwAIKMCAAAcACAJDAAAqgMAIOoBAACpAwAw6wEAAC0AEOwBAACpAwAw7QEBAIEDACHxAQIAiwMAIYUCAQCBAwAhmgIBAIEDACGbAgEAgQMAIREDAACnAwAgBQAAlAMAIA0AAKgDACAOAAC6AwAgEAAAvAMAIOoBAAC7AwAw6wEAAAMAEOwBAAC7AwAw7QEBAIEDACHuAQEAgQMAIe8BAQCBAwAh8QECAIsDACHyAUAAggMAIYcCAQCuAwAhmQIBAK4DACGiAgAAAwAgowIAAAMAIAKFAgEAAAABjgIBAAAAAQKGAgEAAAABjgIBAAAAAQ8GAACTAwAgDAAAkgMAIOoBAACtAwAw6wEAACEAEOwBAACtAwAw7QEBAIEDACGFAgEArgMAIYYCAQCuAwAhigIBAK4DACGNAgEAgQMAIY4CAQCBAwAhjwICAIsDACGQAgEAgQMAIZECAQCuAwAhkgJAAIIDACEL8wEBAAAAAfQBAQAAAAX1AQEAAAAF9gEBAAAAAfcBAQAAAAH4AQEAAAAB-QEBAAAAAfoBAQCQAwAh-wEBAAAAAfwBAQAAAAH9AQEAAAABCwYAALIDACDqAQAArwMAMOsBAAAYABDsAQAArwMAMO0BAQCBAwAhhgIBAIEDACGIAggAsAMAIYkCAgCLAwAhigIBAIEDACGLAiAAsQMAIYwCQACCAwAhCPMBCAAAAAH0AQgAAAAE9QEIAAAABPYBCAAAAAH3AQgAAAAB-AEIAAAAAfkBCAAAAAH6AQgA_AIAIQLzASAAAAAB-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-AMAMB8AAP0DADCkAgAA-QMAMKUCAAD6AwAwpgIAAPwDADCnAgAA_AMAMKgCAAD8AwAwqQIAAPsDACCqAgAA_AMAMKsCAAD-AwAwrAIAAP8DADAHHgAA8QMAIB8AAPQDACCkAgAA8gMAIKUCAADzAwAgpgIAABwAIKcCAAAcACCoAgAA9QEAIAseAADjAwAwHwAA6AMAMKQCAADkAwAwpQIAAOUDADCmAgAA5wMAMKcCAADnAwAwqAIAAOcDADCpAgAA5gMAIKoCAADnAwAwqwIAAOkDADCsAgAA6gMAMAoMAADwAwAg7QEBAAAAAYUCAQAAAAGKAgEAAAABjQIBAAAAAY4CAQAAAAGPAgIAAAABkAIBAAAAAZECAQAAAAGSAkAAAAABAgAAACMAIB4AAO8DACADAAAAIwAgHgAA7wMAIB8AAO0DACABFwAAjwYAMBEGAACTAwAgDAAAkgMAIOoBAACtAwAw6wEAACEAEOwBAACtAwAw7QEBAAAAAYUCAQCuAwAhhgIBAK4DACGKAgEArgMAIY0CAQCBAwAhjgIBAIEDACGPAgIAiwMAIZACAQCBAwAhkQIBAK4DACGSAkAAggMAIaACAACrAwAgoQIAAKwDACACAAAAIwAgFwAA7QMAIAIAAADrAwAgFwAA7AMAIA3qAQAA6gMAMOsBAADrAwAQ7AEAAOoDADDtAQEAgQMAIYUCAQCuAwAhhgIBAK4DACGKAgEArgMAIY0CAQCBAwAhjgIBAIEDACGPAgIAiwMAIZACAQCBAwAhkQIBAK4DACGSAkAAggMAIQ3qAQAA6gMAMOsBAADrAwAQ7AEAAOoDADDtAQEAgQMAIYUCAQCuAwAhhgIBAK4DACGKAgEArgMAIY0CAQCBAwAhjgIBAIEDACGPAgIAiwMAIZACAQCBAwAhkQIBAK4DACGSAkAAggMAIQntAQEAwgMAIYUCAQDQAwAhigIBANADACGNAgEAwgMAIY4CAQDCAwAhjwICAMMDACGQAgEAwgMAIZECAQDQAwAhkgJAAMQDACEKDAAA7gMAIO0BAQDCAwAhhQIBANADACGKAgEA0AMAIY0CAQDCAwAhjgIBAMIDACGPAgIAwwMAIZACAQDCAwAhkQIBANADACGSAkAAxAMAIQceAACKBgAgHwAAjQYAIKQCAACLBgAgpQIAAIwGACCmAgAAAwAgpwIAAAMAIKgCAAAFACAKDAAA8AMAIO0BAQAAAAGFAgEAAAABigIBAAAAAY0CAQAAAAGOAgEAAAABjwICAAAAAZACAQAAAAGRAgEAAAABkgJAAAAAAQMeAACKBgAgpAIAAIsGACCoAgAABQAgBwUAAPcDACAMAADRAwAg7QEBAAAAAfIBQAAAAAGEAgEAAAABhQIBAAAAAYcCAQAAAAECAAAA9QEAIB4AAPEDACADAAAAHAAgHgAA8QMAIB8AAPUDACAJAAAAHAAgBQAA9gMAIAwAAM4DACAXAAD1AwAg7QEBAMIDACHyAUAAxAMAIYQCAQDCAwAhhQIBANADACGHAgEA0AMAIQcFAAD2AwAgDAAAzgMAIO0BAQDCAwAh8gFAAMQDACGEAgEAwgMAIYUCAQDQAwAhhwIBANADACEHHgAAhQYAIB8AAIgGACCkAgAAhgYAIKUCAACHBgAgpgIAAAcAIKcCAAAHACCoAgAANwAgAx4AAIUGACCkAgAAhgYAIKgCAAA3ACAG7QEBAAAAAYgCCAAAAAGJAgIAAAABigIBAAAAAYsCIAAAAAGMAkAAAAABAgAAABoAIB4AAIUEACADAAAAGgAgHgAAhQQAIB8AAIQEACABFwAAhAYAMAsGAACyAwAg6gEAAK8DADDrAQAAGAAQ7AEAAK8DADDtAQEAAAABhgIBAIEDACGIAggAsAMAIYkCAgCLAwAhigIBAIEDACGLAiAAsQMAIYwCQACCAwAhAgAAABoAIBcAAIQEACACAAAAgAQAIBcAAIEEACAK6gEAAP8DADDrAQAAgAQAEOwBAAD_AwAw7QEBAIEDACGGAgEAgQMAIYgCCACwAwAhiQICAIsDACGKAgEAgQMAIYsCIACxAwAhjAJAAIIDACEK6gEAAP8DADDrAQAAgAQAEOwBAAD_AwAw7QEBAIEDACGGAgEAgQMAIYgCCACwAwAhiQICAIsDACGKAgEAgQMAIYsCIACxAwAhjAJAAIIDACEG7QEBAMIDACGIAggAggQAIYkCAgDDAwAhigIBAMIDACGLAiAAgwQAIYwCQADEAwAhBaoCCAAAAAGtAggAAAABrgIIAAAAAa8CCAAAAAGwAggAAAABAaoCIAAAAAEG7QEBAMIDACGIAggAggQAIYkCAgDDAwAhigIBAMIDACGLAiAAgwQAIYwCQADEAwAhBu0BAQAAAAGIAggAAAABiQICAAAAAYoCAQAAAAGLAiAAAAABjAJAAAAAAQcIAACfBAAg7QEBAAAAAfABAQAAAAHxAQIAAAABlgIBAAAAAZcCAQAAAAGYAgEAAAABAgAAABEAIB4AAJ4EACADAAAAEQAgHgAAngQAIB8AAJAEACABFwAAgwYAMAwGAACyAwAgCAAAtgMAIOoBAAC1AwAw6wEAAA8AEOwBAAC1AwAw7QEBAAAAAfABAQCBAwAh8QECAIsDACGGAgEAgQMAIZYCAQCBAwAhlwIBAIEDACGYAgEAgQMAIQIAAAARACAXAACQBAAgAgAAAI4EACAXAACPBAAgCuoBAACNBAAw6wEAAI4EABDsAQAAjQQAMO0BAQCBAwAh8AEBAIEDACHxAQIAiwMAIYYCAQCBAwAhlgIBAIEDACGXAgEAgQMAIZgCAQCBAwAhCuoBAACNBAAw6wEAAI4EABDsAQAAjQQAMO0BAQCBAwAh8AEBAIEDACHxAQIAiwMAIYYCAQCBAwAhlgIBAIEDACGXAgEAgQMAIZgCAQCBAwAhBu0BAQDCAwAh8AEBAMIDACHxAQIAwwMAIZYCAQDCAwAhlwIBAMIDACGYAgEAwgMAIQcIAACRBAAg7QEBAMIDACHwAQEAwgMAIfEBAgDDAwAhlgIBAMIDACGXAgEAwgMAIZgCAQDCAwAhCx4AAJIEADAfAACXBAAwpAIAAJMEADClAgAAlAQAMKYCAACWBAAwpwIAAJYEADCoAgAAlgQAMKkCAACVBAAgqgIAAJYEADCrAgAAmAQAMKwCAACZBAAwBO0BAQAAAAHxAQIAAAABlAIBAAAAAZUCIAAAAAECAAAAFQAgHgAAnQQAIAMAAAAVACAeAACdBAAgHwAAnAQAIAEXAACCBgAwCQcAALQDACDqAQAAswMAMOsBAAATABDsAQAAswMAMO0BAQAAAAHxAQIAiwMAIZMCAQCBAwAhlAIBAIEDACGVAiAAsQMAIQIAAAAVACAXAACcBAAgAgAAAJoEACAXAACbBAAgCOoBAACZBAAw6wEAAJoEABDsAQAAmQQAMO0BAQCBAwAh8QECAIsDACGTAgEAgQMAIZQCAQCBAwAhlQIgALEDACEI6gEAAJkEADDrAQAAmgQAEOwBAACZBAAw7QEBAIEDACHxAQIAiwMAIZMCAQCBAwAhlAIBAIEDACGVAiAAsQMAIQTtAQEAwgMAIfEBAgDDAwAhlAIBAMIDACGVAiAAgwQAIQTtAQEAwgMAIfEBAgDDAwAhlAIBAMIDACGVAiAAgwQAIQTtAQEAAAAB8QECAAAAAZQCAQAAAAGVAiAAAAABBwgAAJ8EACDtAQEAAAAB8AEBAAAAAfEBAgAAAAGWAgEAAAABlwIBAAAAAZgCAQAAAAEEHgAAkgQAMKQCAACTBAAwqAIAAJYEADCpAgAAlQQAIAsDAAChBAAgCgAAogQAIAsAAKMEACANAACkBAAgDgAApQQAIO0BAQAAAAHuAQEAAAAB7wEBAAAAAfEBAgAAAAHyAUAAAAABmQIBAAAAAQMeAACABgAgpAIAAIEGACCoAgAAAQAgBB4AAIYEADCkAgAAhwQAMKgCAACKBAAwqQIAAIkEACAEHgAA-AMAMKQCAAD5AwAwqAIAAPwDADCpAgAA-wMAIAMeAADxAwAgpAIAAPIDACCoAgAA9QEAIAQeAADjAwAwpAIAAOQDADCoAgAA5wMAMKkCAADmAwAgCgMAANIEACANAADUBAAgDgAA1QQAIBAAANMEACDtAQEAAAAB7gEBAAAAAe8BAQAAAAHxAQIAAAAB8gFAAAAAAZkCAQAAAAECAAAABQAgHgAA0QQAIAMAAAAFACAeAADRBAAgHwAAsAQAIAEXAAD_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_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-JQB_JgCAAScAgQESAgETPAEUPwEVQAEWQQEYQwEZRRAaRhEbSAEcShAdSxIgTAEhTQEiThAoURMpUhkqUwIrVAIsVQItVgIuVwIvWQIwWxAxXBoyXwIzYRA0Yhs1ZAI2ZQI3ZhA4aRw5aiI6aw07bA08bQ09bg0-bw0_cQ1AcxBBdCNCdg1DeBBEeSRFeg1Gew1HfBBIfyVJgAErSoEBBEuCAQRMgwEETYQBBE6FAQRPhwEEUIkBEFGKASxSjQEEU48BEFSQAS1VkgEEVpMBBFeUARBYlwEuWZgBNFqZAQVbmgEFXJsBBV2cAQVenQEFX58BBWChARBhogE1YqQBBWOmARBkpwE2ZagBBWapAQVnqgEQaK0BN2muAT1qrwEGa7ABBmyxAQZtsgEGbrMBBm-1AQZwtwEQcbgBPnK6AQZzvAEQdL0BP3W-AQZ2vwEGd8ABEHjDAUB5xAFGesUBCnvGAQp8xwEKfcgBCn7JAQp_ywEKgAHNARCBAc4BR4IB0gEKgwHUARCEAdUBSIUB2AEKhgHZAQqHAdoBEIgB3QFJiQHeAU-KAd8BCIsB4AEIjAHhAQiNAeIBCI4B4wEIjwHlAQiQAecBEJEB6AFQkgHqAQiTAewBEJQB7QFRlQHuAQiWAe8BCJcB8AEQmAHzAVKZAfQBWJoB9gEJmwH3AQmcAfkBCZ0B-gEJngH7AQmfAf0BCaAB_wEQoQGAAlmiAYUCCaMBhwIQpAGIAlqlAYwCCaYBjQIJpwGOAhCoAZECW6kBkgJfqgGUAmCrAZUCYKwBmAJgrQGZAmCuAZoCYK8BnAJgsAGeAhCxAZ8CYbIBoQJgswGjAhC0AaQCYrUBpQJgtgGmAmC3AacCELgBqgJjuQGrAmm6Aa0CarsBrgJqvAGxAmq9AbICar4BswJqvwG1AmrAAbcCEMEBuAJrwgG6AmrDAbwCEMQBvQJsxQG-AmrGAb8CascBwAIQyAHDAm3JAcQCccoBxgJyywHHAnLMAcoCcs0BywJyzgHMAnLPAc4CctAB0AIQ0QHRAnPSAdMCctMB1QIQ1AHWAnTVAdcCctYB2AJy1wHZAhDYAdwCddkB3QJ52gHeAgPbAd8CA9wB4AID3QHhAgPeAeICA98B5AID4AHmAhDhAecCeuIB6QID4wHrAhDkAewCe-UB7QID5gHuAgPnAe8CEOgB8gJ86QHzAoIB" } async function decodeBase64AsWasm(wasmBase64: string): Promise { @@ -298,6 +298,16 @@ export interface PrismaClient< */ get setting(): Prisma.SettingDelegate; + /** + * `prisma.studyActivity`: Exposes CRUD operations for the **StudyActivity** model. + * Example usage: + * ```ts + * // Fetch zero or more StudyActivities + * const studyActivities = await prisma.studyActivity.findMany() + * ``` + */ + get studyActivity(): Prisma.StudyActivityDelegate; + /** * `prisma.materialGroup`: Exposes CRUD operations for the **MaterialGroup** model. * Example usage: diff --git a/src/generated/prisma/internal/prismaNamespace.ts b/src/generated/prisma/internal/prismaNamespace.ts index e12ee22..7f2be5f 100644 --- a/src/generated/prisma/internal/prismaNamespace.ts +++ b/src/generated/prisma/internal/prismaNamespace.ts @@ -395,6 +395,7 @@ export const ModelName = { ShareLink: 'ShareLink', AuthSecurity: 'AuthSecurity', Setting: 'Setting', + StudyActivity: 'StudyActivity', MaterialGroup: 'MaterialGroup' } as const @@ -411,7 +412,7 @@ export type TypeMap + fields: Prisma.StudyActivityFieldRefs + operations: { + findUnique: { + args: Prisma.StudyActivityFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.StudyActivityFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.StudyActivityFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.StudyActivityFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.StudyActivityFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.StudyActivityCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.StudyActivityCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.StudyActivityCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.StudyActivityDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.StudyActivityUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.StudyActivityDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.StudyActivityUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.StudyActivityUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.StudyActivityUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.StudyActivityAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.StudyActivityGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.StudyActivityCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } MaterialGroup: { payload: Prisma.$MaterialGroupPayload fields: Prisma.MaterialGroupFieldRefs @@ -1470,6 +1545,15 @@ export const SettingScalarFieldEnum = { export type SettingScalarFieldEnum = (typeof SettingScalarFieldEnum)[keyof typeof SettingScalarFieldEnum] +export const StudyActivityScalarFieldEnum = { + id: 'id', + type: 'type', + occurredAt: 'occurredAt' +} as const + +export type StudyActivityScalarFieldEnum = (typeof StudyActivityScalarFieldEnum)[keyof typeof StudyActivityScalarFieldEnum] + + export const MaterialGroupScalarFieldEnum = { id: 'id', classId: 'classId', @@ -1659,6 +1743,7 @@ export type GlobalOmitConfig = { shareLink?: Prisma.ShareLinkOmit authSecurity?: Prisma.AuthSecurityOmit setting?: Prisma.SettingOmit + studyActivity?: Prisma.StudyActivityOmit materialGroup?: Prisma.MaterialGroupOmit } diff --git a/src/generated/prisma/internal/prismaNamespaceBrowser.ts b/src/generated/prisma/internal/prismaNamespaceBrowser.ts index 90ca7f0..7b45804 100644 --- a/src/generated/prisma/internal/prismaNamespaceBrowser.ts +++ b/src/generated/prisma/internal/prismaNamespaceBrowser.ts @@ -62,6 +62,7 @@ export const ModelName = { ShareLink: 'ShareLink', AuthSecurity: 'AuthSecurity', Setting: 'Setting', + StudyActivity: 'StudyActivity', MaterialGroup: 'MaterialGroup' } as const @@ -209,6 +210,15 @@ export const SettingScalarFieldEnum = { export type SettingScalarFieldEnum = (typeof SettingScalarFieldEnum)[keyof typeof SettingScalarFieldEnum] +export const StudyActivityScalarFieldEnum = { + id: 'id', + type: 'type', + occurredAt: 'occurredAt' +} as const + +export type StudyActivityScalarFieldEnum = (typeof StudyActivityScalarFieldEnum)[keyof typeof StudyActivityScalarFieldEnum] + + export const MaterialGroupScalarFieldEnum = { id: 'id', classId: 'classId', diff --git a/src/generated/prisma/models.ts b/src/generated/prisma/models.ts index 145eb9e..b7bcb19 100644 --- a/src/generated/prisma/models.ts +++ b/src/generated/prisma/models.ts @@ -19,5 +19,6 @@ export type * from './models/QuizAttempt' export type * from './models/ShareLink' export type * from './models/AuthSecurity' export type * from './models/Setting' +export type * from './models/StudyActivity' export type * from './models/MaterialGroup' export type * from './commonInputTypes' \ No newline at end of file diff --git a/src/generated/prisma/models/StudyActivity.ts b/src/generated/prisma/models/StudyActivity.ts new file mode 100644 index 0000000..316fa78 --- /dev/null +++ b/src/generated/prisma/models/StudyActivity.ts @@ -0,0 +1,1091 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `StudyActivity` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model StudyActivity + * + */ +export type StudyActivityModel = runtime.Types.Result.DefaultSelection + +export type AggregateStudyActivity = { + _count: StudyActivityCountAggregateOutputType | null + _min: StudyActivityMinAggregateOutputType | null + _max: StudyActivityMaxAggregateOutputType | null +} + +export type StudyActivityMinAggregateOutputType = { + id: string | null + type: string | null + occurredAt: Date | null +} + +export type StudyActivityMaxAggregateOutputType = { + id: string | null + type: string | null + occurredAt: Date | null +} + +export type StudyActivityCountAggregateOutputType = { + id: number + type: number + occurredAt: number + _all: number +} + + +export type StudyActivityMinAggregateInputType = { + id?: true + type?: true + occurredAt?: true +} + +export type StudyActivityMaxAggregateInputType = { + id?: true + type?: true + occurredAt?: true +} + +export type StudyActivityCountAggregateInputType = { + id?: true + type?: true + occurredAt?: true + _all?: true +} + +export type StudyActivityAggregateArgs = { + /** + * Filter which StudyActivity to aggregate. + */ + where?: Prisma.StudyActivityWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of StudyActivities to fetch. + */ + orderBy?: Prisma.StudyActivityOrderByWithRelationInput | Prisma.StudyActivityOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.StudyActivityWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` StudyActivities from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` StudyActivities. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned StudyActivities + **/ + _count?: true | StudyActivityCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: StudyActivityMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: StudyActivityMaxAggregateInputType +} + +export type GetStudyActivityAggregateType = { + [P in keyof T & keyof AggregateStudyActivity]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type StudyActivityGroupByArgs = { + where?: Prisma.StudyActivityWhereInput + orderBy?: Prisma.StudyActivityOrderByWithAggregationInput | Prisma.StudyActivityOrderByWithAggregationInput[] + by: Prisma.StudyActivityScalarFieldEnum[] | Prisma.StudyActivityScalarFieldEnum + having?: Prisma.StudyActivityScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: StudyActivityCountAggregateInputType | true + _min?: StudyActivityMinAggregateInputType + _max?: StudyActivityMaxAggregateInputType +} + +export type StudyActivityGroupByOutputType = { + id: string + type: string + occurredAt: Date + _count: StudyActivityCountAggregateOutputType | null + _min: StudyActivityMinAggregateOutputType | null + _max: StudyActivityMaxAggregateOutputType | null +} + +export type GetStudyActivityGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof StudyActivityGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type StudyActivityWhereInput = { + AND?: Prisma.StudyActivityWhereInput | Prisma.StudyActivityWhereInput[] + OR?: Prisma.StudyActivityWhereInput[] + NOT?: Prisma.StudyActivityWhereInput | Prisma.StudyActivityWhereInput[] + id?: Prisma.StringFilter<"StudyActivity"> | string + type?: Prisma.StringFilter<"StudyActivity"> | string + occurredAt?: Prisma.DateTimeFilter<"StudyActivity"> | Date | string +} + +export type StudyActivityOrderByWithRelationInput = { + id?: Prisma.SortOrder + type?: Prisma.SortOrder + occurredAt?: Prisma.SortOrder +} + +export type StudyActivityWhereUniqueInput = Prisma.AtLeast<{ + id?: string + AND?: Prisma.StudyActivityWhereInput | Prisma.StudyActivityWhereInput[] + OR?: Prisma.StudyActivityWhereInput[] + NOT?: Prisma.StudyActivityWhereInput | Prisma.StudyActivityWhereInput[] + type?: Prisma.StringFilter<"StudyActivity"> | string + occurredAt?: Prisma.DateTimeFilter<"StudyActivity"> | Date | string +}, "id"> + +export type StudyActivityOrderByWithAggregationInput = { + id?: Prisma.SortOrder + type?: Prisma.SortOrder + occurredAt?: Prisma.SortOrder + _count?: Prisma.StudyActivityCountOrderByAggregateInput + _max?: Prisma.StudyActivityMaxOrderByAggregateInput + _min?: Prisma.StudyActivityMinOrderByAggregateInput +} + +export type StudyActivityScalarWhereWithAggregatesInput = { + AND?: Prisma.StudyActivityScalarWhereWithAggregatesInput | Prisma.StudyActivityScalarWhereWithAggregatesInput[] + OR?: Prisma.StudyActivityScalarWhereWithAggregatesInput[] + NOT?: Prisma.StudyActivityScalarWhereWithAggregatesInput | Prisma.StudyActivityScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"StudyActivity"> | string + type?: Prisma.StringWithAggregatesFilter<"StudyActivity"> | string + occurredAt?: Prisma.DateTimeWithAggregatesFilter<"StudyActivity"> | Date | string +} + +export type StudyActivityCreateInput = { + id?: string + type: string + occurredAt?: Date | string +} + +export type StudyActivityUncheckedCreateInput = { + id?: string + type: string + occurredAt?: Date | string +} + +export type StudyActivityUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + type?: Prisma.StringFieldUpdateOperationsInput | string + occurredAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type StudyActivityUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + type?: Prisma.StringFieldUpdateOperationsInput | string + occurredAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type StudyActivityCreateManyInput = { + id?: string + type: string + occurredAt?: Date | string +} + +export type StudyActivityUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + type?: Prisma.StringFieldUpdateOperationsInput | string + occurredAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type StudyActivityUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + type?: Prisma.StringFieldUpdateOperationsInput | string + occurredAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type StudyActivityCountOrderByAggregateInput = { + id?: Prisma.SortOrder + type?: Prisma.SortOrder + occurredAt?: Prisma.SortOrder +} + +export type StudyActivityMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + type?: Prisma.SortOrder + occurredAt?: Prisma.SortOrder +} + +export type StudyActivityMinOrderByAggregateInput = { + id?: Prisma.SortOrder + type?: Prisma.SortOrder + occurredAt?: Prisma.SortOrder +} + + + +export type StudyActivitySelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + type?: boolean + occurredAt?: boolean +}, ExtArgs["result"]["studyActivity"]> + +export type StudyActivitySelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + type?: boolean + occurredAt?: boolean +}, ExtArgs["result"]["studyActivity"]> + +export type StudyActivitySelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + type?: boolean + occurredAt?: boolean +}, ExtArgs["result"]["studyActivity"]> + +export type StudyActivitySelectScalar = { + id?: boolean + type?: boolean + occurredAt?: boolean +} + +export type StudyActivityOmit = runtime.Types.Extensions.GetOmit<"id" | "type" | "occurredAt", ExtArgs["result"]["studyActivity"]> + +export type $StudyActivityPayload = { + name: "StudyActivity" + objects: {} + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + type: string + occurredAt: Date + }, ExtArgs["result"]["studyActivity"]> + composites: {} +} + +export type StudyActivityGetPayload = runtime.Types.Result.GetResult + +export type StudyActivityCountArgs = + Omit & { + select?: StudyActivityCountAggregateInputType | true + } + +export interface StudyActivityDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['StudyActivity'], meta: { name: 'StudyActivity' } } + /** + * Find zero or one StudyActivity that matches the filter. + * @param {StudyActivityFindUniqueArgs} args - Arguments to find a StudyActivity + * @example + * // Get one StudyActivity + * const studyActivity = await prisma.studyActivity.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__StudyActivityClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one StudyActivity that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {StudyActivityFindUniqueOrThrowArgs} args - Arguments to find a StudyActivity + * @example + * // Get one StudyActivity + * const studyActivity = await prisma.studyActivity.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__StudyActivityClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first StudyActivity that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {StudyActivityFindFirstArgs} args - Arguments to find a StudyActivity + * @example + * // Get one StudyActivity + * const studyActivity = await prisma.studyActivity.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__StudyActivityClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first StudyActivity that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {StudyActivityFindFirstOrThrowArgs} args - Arguments to find a StudyActivity + * @example + * // Get one StudyActivity + * const studyActivity = await prisma.studyActivity.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__StudyActivityClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more StudyActivities that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {StudyActivityFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all StudyActivities + * const studyActivities = await prisma.studyActivity.findMany() + * + * // Get first 10 StudyActivities + * const studyActivities = await prisma.studyActivity.findMany({ take: 10 }) + * + * // Only select the `id` + * const studyActivityWithIdOnly = await prisma.studyActivity.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a StudyActivity. + * @param {StudyActivityCreateArgs} args - Arguments to create a StudyActivity. + * @example + * // Create one StudyActivity + * const StudyActivity = await prisma.studyActivity.create({ + * data: { + * // ... data to create a StudyActivity + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__StudyActivityClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many StudyActivities. + * @param {StudyActivityCreateManyArgs} args - Arguments to create many StudyActivities. + * @example + * // Create many StudyActivities + * const studyActivity = await prisma.studyActivity.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many StudyActivities and returns the data saved in the database. + * @param {StudyActivityCreateManyAndReturnArgs} args - Arguments to create many StudyActivities. + * @example + * // Create many StudyActivities + * const studyActivity = await prisma.studyActivity.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many StudyActivities and only return the `id` + * const studyActivityWithIdOnly = await prisma.studyActivity.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a StudyActivity. + * @param {StudyActivityDeleteArgs} args - Arguments to delete one StudyActivity. + * @example + * // Delete one StudyActivity + * const StudyActivity = await prisma.studyActivity.delete({ + * where: { + * // ... filter to delete one StudyActivity + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__StudyActivityClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one StudyActivity. + * @param {StudyActivityUpdateArgs} args - Arguments to update one StudyActivity. + * @example + * // Update one StudyActivity + * const studyActivity = await prisma.studyActivity.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__StudyActivityClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more StudyActivities. + * @param {StudyActivityDeleteManyArgs} args - Arguments to filter StudyActivities to delete. + * @example + * // Delete a few StudyActivities + * const { count } = await prisma.studyActivity.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more StudyActivities. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {StudyActivityUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many StudyActivities + * const studyActivity = await prisma.studyActivity.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more StudyActivities and returns the data updated in the database. + * @param {StudyActivityUpdateManyAndReturnArgs} args - Arguments to update many StudyActivities. + * @example + * // Update many StudyActivities + * const studyActivity = await prisma.studyActivity.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more StudyActivities and only return the `id` + * const studyActivityWithIdOnly = await prisma.studyActivity.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one StudyActivity. + * @param {StudyActivityUpsertArgs} args - Arguments to update or create a StudyActivity. + * @example + * // Update or create a StudyActivity + * const studyActivity = await prisma.studyActivity.upsert({ + * create: { + * // ... data to create a StudyActivity + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the StudyActivity we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__StudyActivityClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of StudyActivities. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {StudyActivityCountArgs} args - Arguments to filter StudyActivities to count. + * @example + * // Count the number of StudyActivities + * const count = await prisma.studyActivity.count({ + * where: { + * // ... the filter for the StudyActivities we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a StudyActivity. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {StudyActivityAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by StudyActivity. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {StudyActivityGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends StudyActivityGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: StudyActivityGroupByArgs['orderBy'] } + : { orderBy?: StudyActivityGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetStudyActivityGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the StudyActivity model + */ +readonly fields: StudyActivityFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for StudyActivity. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__StudyActivityClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the StudyActivity model + */ +export interface StudyActivityFieldRefs { + readonly id: Prisma.FieldRef<"StudyActivity", 'String'> + readonly type: Prisma.FieldRef<"StudyActivity", 'String'> + readonly occurredAt: Prisma.FieldRef<"StudyActivity", 'DateTime'> +} + + +// Custom InputTypes +/** + * StudyActivity findUnique + */ +export type StudyActivityFindUniqueArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelect | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * Filter, which StudyActivity to fetch. + */ + where: Prisma.StudyActivityWhereUniqueInput +} + +/** + * StudyActivity findUniqueOrThrow + */ +export type StudyActivityFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelect | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * Filter, which StudyActivity to fetch. + */ + where: Prisma.StudyActivityWhereUniqueInput +} + +/** + * StudyActivity findFirst + */ +export type StudyActivityFindFirstArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelect | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * Filter, which StudyActivity to fetch. + */ + where?: Prisma.StudyActivityWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of StudyActivities to fetch. + */ + orderBy?: Prisma.StudyActivityOrderByWithRelationInput | Prisma.StudyActivityOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for StudyActivities. + */ + cursor?: Prisma.StudyActivityWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` StudyActivities from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` StudyActivities. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of StudyActivities. + */ + distinct?: Prisma.StudyActivityScalarFieldEnum | Prisma.StudyActivityScalarFieldEnum[] +} + +/** + * StudyActivity findFirstOrThrow + */ +export type StudyActivityFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelect | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * Filter, which StudyActivity to fetch. + */ + where?: Prisma.StudyActivityWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of StudyActivities to fetch. + */ + orderBy?: Prisma.StudyActivityOrderByWithRelationInput | Prisma.StudyActivityOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for StudyActivities. + */ + cursor?: Prisma.StudyActivityWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` StudyActivities from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` StudyActivities. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of StudyActivities. + */ + distinct?: Prisma.StudyActivityScalarFieldEnum | Prisma.StudyActivityScalarFieldEnum[] +} + +/** + * StudyActivity findMany + */ +export type StudyActivityFindManyArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelect | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * Filter, which StudyActivities to fetch. + */ + where?: Prisma.StudyActivityWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of StudyActivities to fetch. + */ + orderBy?: Prisma.StudyActivityOrderByWithRelationInput | Prisma.StudyActivityOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing StudyActivities. + */ + cursor?: Prisma.StudyActivityWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` StudyActivities from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` StudyActivities. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of StudyActivities. + */ + distinct?: Prisma.StudyActivityScalarFieldEnum | Prisma.StudyActivityScalarFieldEnum[] +} + +/** + * StudyActivity create + */ +export type StudyActivityCreateArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelect | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * The data needed to create a StudyActivity. + */ + data: Prisma.XOR +} + +/** + * StudyActivity createMany + */ +export type StudyActivityCreateManyArgs = { + /** + * The data used to create many StudyActivities. + */ + data: Prisma.StudyActivityCreateManyInput | Prisma.StudyActivityCreateManyInput[] +} + +/** + * StudyActivity createManyAndReturn + */ +export type StudyActivityCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelectCreateManyAndReturn | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * The data used to create many StudyActivities. + */ + data: Prisma.StudyActivityCreateManyInput | Prisma.StudyActivityCreateManyInput[] +} + +/** + * StudyActivity update + */ +export type StudyActivityUpdateArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelect | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * The data needed to update a StudyActivity. + */ + data: Prisma.XOR + /** + * Choose, which StudyActivity to update. + */ + where: Prisma.StudyActivityWhereUniqueInput +} + +/** + * StudyActivity updateMany + */ +export type StudyActivityUpdateManyArgs = { + /** + * The data used to update StudyActivities. + */ + data: Prisma.XOR + /** + * Filter which StudyActivities to update + */ + where?: Prisma.StudyActivityWhereInput + /** + * Limit how many StudyActivities to update. + */ + limit?: number +} + +/** + * StudyActivity updateManyAndReturn + */ +export type StudyActivityUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelectUpdateManyAndReturn | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * The data used to update StudyActivities. + */ + data: Prisma.XOR + /** + * Filter which StudyActivities to update + */ + where?: Prisma.StudyActivityWhereInput + /** + * Limit how many StudyActivities to update. + */ + limit?: number +} + +/** + * StudyActivity upsert + */ +export type StudyActivityUpsertArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelect | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * The filter to search for the StudyActivity to update in case it exists. + */ + where: Prisma.StudyActivityWhereUniqueInput + /** + * In case the StudyActivity found by the `where` argument doesn't exist, create a new StudyActivity with this data. + */ + create: Prisma.XOR + /** + * In case the StudyActivity was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * StudyActivity delete + */ +export type StudyActivityDeleteArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelect | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null + /** + * Filter which StudyActivity to delete. + */ + where: Prisma.StudyActivityWhereUniqueInput +} + +/** + * StudyActivity deleteMany + */ +export type StudyActivityDeleteManyArgs = { + /** + * Filter which StudyActivities to delete + */ + where?: Prisma.StudyActivityWhereInput + /** + * Limit how many StudyActivities to delete. + */ + limit?: number +} + +/** + * StudyActivity without action + */ +export type StudyActivityDefaultArgs = { + /** + * Select specific fields to fetch from the StudyActivity + */ + select?: Prisma.StudyActivitySelect | null + /** + * Omit specific fields from the StudyActivity + */ + omit?: Prisma.StudyActivityOmit | null +} diff --git a/src/lib/activityClient.ts b/src/lib/activityClient.ts new file mode 100644 index 0000000..6eaaafd --- /dev/null +++ b/src/lib/activityClient.ts @@ -0,0 +1,9 @@ +import type { StudyActivityType } from "@/lib/validation/activitySchemas"; + +export function recordStudyActivity(type: StudyActivityType) { + return fetch("/api/activity", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type }), + }); +} diff --git a/src/lib/validation/activitySchemas.ts b/src/lib/validation/activitySchemas.ts new file mode 100644 index 0000000..a71c764 --- /dev/null +++ b/src/lib/validation/activitySchemas.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const studyActivitySchema = z.object({ + type: z.enum(["FLASHCARD", "QUIZ_QUESTION"]), +}); + +export type StudyActivityType = z.infer["type"]; diff --git a/src/services/activityService.ts b/src/services/activityService.ts new file mode 100644 index 0000000..dea1574 --- /dev/null +++ b/src/services/activityService.ts @@ -0,0 +1,104 @@ +import { prisma } from "@/lib/db"; +import type { StudyActivityType } from "@/lib/validation/activitySchemas"; + +const ARIZONA_OFFSET_MS = 7 * 60 * 60 * 1000; +const WEEKS_TO_DISPLAY = 53; +const DAYS_TO_DISPLAY = WEEKS_TO_DISPLAY * 7; +const STREAK_THRESHOLD = 20; + +export interface DailyActivity { + date: string; + flashcards: number; + questions: number; + total: number; + level: 0 | 1 | 2 | 3 | 4; +} + +export interface ActivitySummary { + days: DailyActivity[]; + today: string; + activeDays: number; + flashcards: number; + questions: number; + currentStreak: number; +} + +function toArizonaDateKey(date: Date): string { + return new Date(date.getTime() - ARIZONA_OFFSET_MS).toISOString().slice(0, 10); +} + +function dateKeyToUtcDate(dateKey: string): Date { + return new Date(`${dateKey}T00:00:00.000Z`); +} + +function addDays(dateKey: string, days: number): string { + const date = dateKeyToUtcDate(dateKey); + date.setUTCDate(date.getUTCDate() + days); + return date.toISOString().slice(0, 10); +} + +function getLevel(total: number): 0 | 1 | 2 | 3 | 4 { + if (total === 0) return 0; + if (total < 20) return 1; + if (total < 50) return 2; + if (total < 100) return 3; + return 4; +} + +function emptyDay(date: string): DailyActivity { + return { date, flashcards: 0, questions: 0, total: 0, level: 0 }; +} + +export async function recordActivity(type: StudyActivityType) { + return prisma.studyActivity.create({ data: { type } }); +} + +export async function getActivitySummary(now = new Date()): Promise { + const today = toArizonaDateKey(now); + const todayDate = dateKeyToUtcDate(today); + const dayOfWeek = todayDate.getUTCDay(); + const startDate = addDays(today, -(dayOfWeek + (WEEKS_TO_DISPLAY - 1) * 7)); + const endDate = addDays(startDate, DAYS_TO_DISPLAY - 1); + + const activities = await prisma.studyActivity.findMany({ + select: { type: true, occurredAt: true }, + orderBy: { occurredAt: "asc" }, + }); + const totals = new Map(); + + for (const activity of activities) { + const date = toArizonaDateKey(activity.occurredAt); + const day = totals.get(date) ?? emptyDay(date); + if (activity.type === "FLASHCARD") day.flashcards += 1; + if (activity.type === "QUIZ_QUESTION") day.questions += 1; + day.total += 1; + day.level = getLevel(day.total); + totals.set(date, day); + } + + const days = Array.from({ length: DAYS_TO_DISPLAY }, (_, index) => { + const date = addDays(startDate, index); + return totals.get(date) ?? emptyDay(date); + }); + const visibleDays = days.filter((day) => day.date <= today && day.date <= endDate); + const flashcards = visibleDays.reduce((sum, day) => sum + day.flashcards, 0); + const questions = visibleDays.reduce((sum, day) => sum + day.questions, 0); + + let streakDate = (totals.get(today)?.total ?? 0) >= STREAK_THRESHOLD + ? today + : addDays(today, -1); + let currentStreak = 0; + while ((totals.get(streakDate)?.total ?? 0) >= STREAK_THRESHOLD) { + currentStreak += 1; + streakDate = addDays(streakDate, -1); + } + + return { + days, + today, + activeDays: visibleDays.filter((day) => day.total > 0).length, + flashcards, + questions, + currentStreak, + }; +} From e3265f03e2f6cf402195c42223c45b492820b343 Mon Sep 17 00:00:00 2001 From: Elijah Date: Sun, 12 Jul 2026 12:01:31 -0700 Subject: [PATCH 07/13] Remove activity summary stats from banner --- src/components/activity/ActivityBanner.tsx | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/components/activity/ActivityBanner.tsx b/src/components/activity/ActivityBanner.tsx index 0bcaf0a..eea45cf 100644 --- a/src/components/activity/ActivityBanner.tsx +++ b/src/components/activity/ActivityBanner.tsx @@ -13,9 +13,6 @@ interface DailyActivity { interface ActivitySummary { days: DailyActivity[]; today: string; - activeDays: number; - flashcards: number; - questions: number; currentStreak: number; } @@ -65,11 +62,6 @@ export function ActivityBanner({ onNewClass }: { onNewClass: () => void }) {
-
- - - -
@@ -182,10 +174,6 @@ function StreakDisplay({ streak }: { streak: number }) { ); } -function SummaryStat({ value, label }: { value: number; label: string }) { - return
{value}
{label}
; -} - function getMonthLabel(week: DailyActivity[], previousWeek?: DailyActivity[]) { const firstOfMonth = week.find((day) => Number(day.date.slice(8, 10)) <= 7); if (!firstOfMonth) return ""; From 6d1d9183553e06a216418011fe238f522e887f28 Mon Sep 17 00:00:00 2001 From: Elijah Date: Mon, 13 Jul 2026 06:32:54 -0700 Subject: [PATCH 08/13] Improve responsive layout for study and quiz cards --- .../[classSlug]/flashcards/page.tsx | 44 +++++++------ .../(protected)/[classSlug]/quizzes/page.tsx | 63 ++++++++++++++----- src/components/quizzes/QuizViewer.tsx | 12 ++-- 3 files changed, 78 insertions(+), 41 deletions(-) diff --git a/src/app/(protected)/[classSlug]/flashcards/page.tsx b/src/app/(protected)/[classSlug]/flashcards/page.tsx index 21f2fdb..5bd7ab7 100644 --- a/src/app/(protected)/[classSlug]/flashcards/page.tsx +++ b/src/app/(protected)/[classSlug]/flashcards/page.tsx @@ -99,9 +99,10 @@ function SortableDeckCard({ deck, onEdit, onDelete, classSlug }: any) { )}
-
- {deck.progress?.length ? ( - <> +
+
+ {deck.progress?.length ? ( + <> Continue @@ -119,23 +120,26 @@ function SortableDeckCard({ deck, onEdit, onDelete, classSlug }: any) { > Restart - - ) : ( - - Study - - )} - - - + + ) : ( + + Study + + )} +
+
+ + + +
diff --git a/src/app/(protected)/[classSlug]/quizzes/page.tsx b/src/app/(protected)/[classSlug]/quizzes/page.tsx index d007514..7a09211 100644 --- a/src/app/(protected)/[classSlug]/quizzes/page.tsx +++ b/src/app/(protected)/[classSlug]/quizzes/page.tsx @@ -35,6 +35,12 @@ interface QuizItem { groupId: string | null; sortOrder: number; _count: { questions: number; attempts: number }; + progress: Array<{ + mode: string; + currentIndex: number; + orderJson: string; + answersJson: string | null; + }>; } interface MaterialGroup { @@ -79,21 +85,48 @@ function SortableQuizCard({ quiz, onEdit, onDelete, classSlug }: any) { )}
-
- - Start Quiz - - - - +
+
+ {quiz.progress?.length ? ( + <> + + Continue + + + + ) : ( + + Start Quiz + + )} +
+
+ + + +
diff --git a/src/components/quizzes/QuizViewer.tsx b/src/components/quizzes/QuizViewer.tsx index ac2bd97..d269ec4 100644 --- a/src/components/quizzes/QuizViewer.tsx +++ b/src/components/quizzes/QuizViewer.tsx @@ -350,11 +350,11 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu
{/* Tags */} -
- +
+ {currentQ.category} - + {currentQ.type === "MULTIPLE_CHOICE" ? "Multiple Choice" : "Select All That Apply"}
@@ -382,7 +382,7 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu } else if (!isSelected && opt.isCorrect) { stateClass = "border-success/50 bg-success/5 text-text-heading"; rightIcon = ( -
+
@@ -400,7 +400,7 @@ export function QuizViewer({ quiz, retakeIds, isShared = false, onFinished }: Qu
toggleOption(opt.id)} - className={`flex min-h-14 items-center gap-4 rounded-2xl border px-4 py-4 transition-all duration-200 md:px-5 ${stateClass}`} + className={`grid min-h-14 grid-cols-[auto_minmax(0,1fr)] items-center gap-x-4 gap-y-2 rounded-2xl border px-4 py-4 transition-all duration-200 md:flex md:gap-4 md:px-5 ${stateClass}`} >
{rightIcon && ( -
+
{rightIcon}
)} From d6f3502cb1013849a9dbaa6e17439fb6a69032dd Mon Sep 17 00:00:00 2001 From: Elijah Date: Mon, 13 Jul 2026 06:55:17 -0700 Subject: [PATCH 09/13] Tighten typing and input validation across study pages --- .../[classSlug]/flashcards/page.tsx | 14 ++-- .../(protected)/[classSlug]/quizzes/page.tsx | 14 ++-- src/app/api/decks/reorder/route.ts | 11 ++-- src/app/api/material-groups/[id]/route.ts | 3 +- src/app/api/material-groups/route.ts | 3 +- src/app/api/quizzes/reorder/route.ts | 11 ++-- .../[type]/[token]/SharedGroupViewer.tsx | 7 +- .../[type]/[token]/SharedViewer.tsx | 15 +++-- .../[classSlug]/[type]/[token]/page.tsx | 17 +++-- src/components/import/CreateTab.tsx | 4 +- src/components/quizzes/AttemptHistory.tsx | 7 +- src/components/quizzes/CategoryBreakdown.tsx | 14 ++-- src/components/quizzes/QuizResults.tsx | 27 +++++--- src/components/quizzes/QuizViewer.tsx | 3 +- src/lib/validation/reorderSchemas.ts | 11 ++++ src/types/study.ts | 65 +++++++++++++++++++ 16 files changed, 170 insertions(+), 56 deletions(-) create mode 100644 src/lib/validation/reorderSchemas.ts create mode 100644 src/types/study.ts diff --git a/src/app/(protected)/[classSlug]/flashcards/page.tsx b/src/app/(protected)/[classSlug]/flashcards/page.tsx index 5bd7ab7..8a455f1 100644 --- a/src/app/(protected)/[classSlug]/flashcards/page.tsx +++ b/src/app/(protected)/[classSlug]/flashcards/page.tsx @@ -27,6 +27,7 @@ import { useSortable, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; +import type { ReorderItem } from "@/types/study"; interface DeckItem { id: string; @@ -65,7 +66,14 @@ function getProgressLabel(deck: DeckItem) { return `${current}/${total}, ${modeLabel} · ${correctCount} ✓`; } -function SortableDeckCard({ deck, onEdit, onDelete, classSlug }: any) { +interface SortableDeckCardProps { + deck: DeckItem; + onEdit: (deck: DeckItem) => void; + onDelete: (deck: DeckItem) => void; + classSlug: string; +} + +function SortableDeckCard({ deck, onEdit, onDelete, classSlug }: SortableDeckCardProps) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: deck.id }); const style = { transform: CSS.Transform.toString(transform), @@ -314,8 +322,6 @@ export default function FlashcardsPage() { if (!activeDeck) return; let targetGroupId: string | null = null; - let targetIndex = 0; - const overContainerId = over.data.current?.sortable?.containerId; if (overContainerId) { targetGroupId = overContainerId === "uncategorized" ? null : overContainerId; @@ -344,7 +350,7 @@ export default function FlashcardsPage() { } const affectedGroups = new Set([activeDeck.groupId, targetGroupId]); - const updates: any[] = []; + const updates: ReorderItem[] = []; affectedGroups.forEach(gId => { const gItems = newItems.filter(d => d.groupId === gId); diff --git a/src/app/(protected)/[classSlug]/quizzes/page.tsx b/src/app/(protected)/[classSlug]/quizzes/page.tsx index 7a09211..97be050 100644 --- a/src/app/(protected)/[classSlug]/quizzes/page.tsx +++ b/src/app/(protected)/[classSlug]/quizzes/page.tsx @@ -27,6 +27,7 @@ import { useSortable, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; +import type { ReorderItem } from "@/types/study"; interface QuizItem { id: string; @@ -51,7 +52,14 @@ interface MaterialGroup { const cache: Record = {}; -function SortableQuizCard({ quiz, onEdit, onDelete, classSlug }: any) { +interface SortableQuizCardProps { + quiz: QuizItem; + onEdit: (quiz: QuizItem) => void; + onDelete: (quiz: QuizItem) => void; + classSlug: string; +} + +function SortableQuizCard({ quiz, onEdit, onDelete, classSlug }: SortableQuizCardProps) { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: quiz.id }); const style = { transform: CSS.Transform.toString(transform), @@ -312,8 +320,6 @@ export default function QuizzesPage() { if (!activeQuiz) return; let targetGroupId: string | null = null; - let targetIndex = 0; - // Check if over a group container directly const overContainerId = over.data.current?.sortable?.containerId; if (overContainerId) { @@ -345,7 +351,7 @@ export default function QuizzesPage() { // Re-calculate sortOrder for the affected groups to persist const affectedGroups = new Set([activeQuiz.groupId, targetGroupId]); - const updates: any[] = []; + const updates: ReorderItem[] = []; affectedGroups.forEach(gId => { const gItems = newItems.filter(q => q.groupId === gId); diff --git a/src/app/api/decks/reorder/route.ts b/src/app/api/decks/reorder/route.ts index 97313be..c7bcc43 100644 --- a/src/app/api/decks/reorder/route.ts +++ b/src/app/api/decks/reorder/route.ts @@ -1,19 +1,20 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; +import { reorderRequestSchema } from "@/lib/validation/reorderSchemas"; export async function PATCH(request: NextRequest) { try { - const body = await request.json(); - const { items } = body; - - if (!Array.isArray(items)) { + const parsed = reorderRequestSchema.safeParse(await request.json()); + if (!parsed.success) { return NextResponse.json({ error: "Invalid input" }, { status: 400 }); } + const { items } = parsed.data; + // items should be an array of { id, sortOrder, groupId } // Using a transaction to perform all updates await prisma.$transaction( - items.map((item: any) => + items.map((item) => prisma.deck.update({ where: { id: item.id }, data: { diff --git a/src/app/api/material-groups/[id]/route.ts b/src/app/api/material-groups/[id]/route.ts index f2e0316..bbd03cf 100644 --- a/src/app/api/material-groups/[id]/route.ts +++ b/src/app/api/material-groups/[id]/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; +import type { Prisma } from "@/generated/prisma/client"; export async function PATCH( request: NextRequest, @@ -10,7 +11,7 @@ export async function PATCH( const body = await request.json(); const { name, sortOrder } = body; - const updateData: any = {}; + const updateData: Prisma.MaterialGroupUpdateInput = {}; if (name !== undefined) updateData.name = name; if (sortOrder !== undefined) updateData.sortOrder = sortOrder; diff --git a/src/app/api/material-groups/route.ts b/src/app/api/material-groups/route.ts index 974b8ee..6184603 100644 --- a/src/app/api/material-groups/route.ts +++ b/src/app/api/material-groups/route.ts @@ -1,5 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; +import type { Prisma } from "@/generated/prisma/client"; export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url); @@ -14,7 +15,7 @@ export async function GET(request: NextRequest) { return NextResponse.json({ error: "Invalid type" }, { status: 400 }); } - const whereClause: any = { classId }; + const whereClause: Prisma.MaterialGroupWhereInput = { classId }; if (type) { whereClause.type = type; } diff --git a/src/app/api/quizzes/reorder/route.ts b/src/app/api/quizzes/reorder/route.ts index cc6d499..7ad7087 100644 --- a/src/app/api/quizzes/reorder/route.ts +++ b/src/app/api/quizzes/reorder/route.ts @@ -1,19 +1,20 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib/db"; +import { reorderRequestSchema } from "@/lib/validation/reorderSchemas"; export async function PATCH(request: NextRequest) { try { - const body = await request.json(); - const { items } = body; - - if (!Array.isArray(items)) { + const parsed = reorderRequestSchema.safeParse(await request.json()); + if (!parsed.success) { return NextResponse.json({ error: "Invalid input" }, { status: 400 }); } + const { items } = parsed.data; + // items should be an array of { id, sortOrder, groupId } // Using a transaction to perform all updates await prisma.$transaction( - items.map((item: any) => + items.map((item) => prisma.quizSet.update({ where: { id: item.id }, data: { diff --git a/src/app/shared/[classSlug]/[type]/[token]/SharedGroupViewer.tsx b/src/app/shared/[classSlug]/[type]/[token]/SharedGroupViewer.tsx index a49d137..5381446 100644 --- a/src/app/shared/[classSlug]/[type]/[token]/SharedGroupViewer.tsx +++ b/src/app/shared/[classSlug]/[type]/[token]/SharedGroupViewer.tsx @@ -4,8 +4,9 @@ import { useState, useEffect } from "react"; import Link from "next/link"; import { usePathname, useRouter } from "next/navigation"; +import type { SharedGroupData } from "@/types/study"; -export function SharedGroupViewer({ data }: { data: any }) { +export function SharedGroupViewer({ data }: { data: SharedGroupData }) { const pathname = usePathname(); const router = useRouter(); @@ -18,7 +19,7 @@ export function SharedGroupViewer({ data }: { data: any }) { if (typeof window === 'undefined') return; const newProgress: Record = {}; - items.forEach((item: any) => { + items.forEach((item) => { const key = data.type === "DECK" ? `flashcard_progress_${item.id}` : `quiz_progress_${item.id}`; const saved = localStorage.getItem(key); if (saved) { @@ -71,7 +72,7 @@ export function SharedGroupViewer({ data }: { data: any }) {
- {items.map((item: any) => ( + {items.map((item) => (
q.category.toUpperCase()))).join(" · ") + ? Array.from(new Set(data.questions.map((q) => q.category.toUpperCase()))).join(" · ") : ""; return ( @@ -137,18 +138,20 @@ export function SharedViewer({ type, data, groupMode = false }: SharedViewerProp view === "study" ? ( ) : ( - + ) ) : ( d.id === itemId); + targetData = link.group.decks.find(d => d.id === itemId) ?? null; targetType = "flashcards"; } else { - targetData = link.group.quizSets.find(q => q.id === itemId); + targetData = link.group.quizSets.find(q => q.id === itemId) ?? null; targetType = "quizzes"; } if (!targetData) notFound(); @@ -69,6 +72,8 @@ export default async function SharedPage(props: SharedPageProps & { searchParams targetData = type === "flashcards" ? link.deck : link.quizSet; } + if (!targetData) notFound(); + return (
{/* Read-only Header */} @@ -105,11 +110,11 @@ export default async function SharedPage(props: SharedPageProps & { searchParams
{type === "groups" && !itemId ? ( - + ) : ( )} diff --git a/src/components/import/CreateTab.tsx b/src/components/import/CreateTab.tsx index 37fd693..6867575 100644 --- a/src/components/import/CreateTab.tsx +++ b/src/components/import/CreateTab.tsx @@ -74,8 +74,8 @@ export function CreateTab({ classId, onCreated }: CreateTabProps) { } onCreated(); - } catch (err: any) { - setError(err.message); + } catch (err: unknown) { + setError(err instanceof Error ? err.message : "Failed to create deck"); } finally { setSaving(false); } diff --git a/src/components/quizzes/AttemptHistory.tsx b/src/components/quizzes/AttemptHistory.tsx index a0b7892..6ee69d3 100644 --- a/src/components/quizzes/AttemptHistory.tsx +++ b/src/components/quizzes/AttemptHistory.tsx @@ -2,15 +2,16 @@ import { useState } from "react"; import { QuizResults } from "./QuizResults"; +import type { QuizAttempt, QuizSummary } from "@/types/study"; interface AttemptHistoryProps { - quiz: any; - attempts: any[]; + quiz: QuizSummary; + attempts: QuizAttempt[]; onRetake: (missedIds: string[]) => void; } export function AttemptHistory({ quiz, attempts, onRetake }: AttemptHistoryProps) { - const [selectedAttempt, setSelectedAttempt] = useState(null); + const [selectedAttempt, setSelectedAttempt] = useState(null); if (selectedAttempt) { return ( diff --git a/src/components/quizzes/CategoryBreakdown.tsx b/src/components/quizzes/CategoryBreakdown.tsx index 6f16b22..e8956a9 100644 --- a/src/components/quizzes/CategoryBreakdown.tsx +++ b/src/components/quizzes/CategoryBreakdown.tsx @@ -2,20 +2,20 @@ import { Fragment } from "react"; import { scoreQuestion } from "@/lib/scoring"; +import type { QuizSummary } from "@/types/study"; interface CategoryBreakdownProps { - quiz: any; - attempt: any; + quiz: QuizSummary; answeredIds: string[]; answers: Record; } -export function CategoryBreakdown({ quiz, attempt, answeredIds, answers }: CategoryBreakdownProps) { +export function CategoryBreakdown({ quiz, answeredIds, answers }: CategoryBreakdownProps) { // Aggregate stats per category const stats: Record = {}; answeredIds.forEach((qId) => { - const q = quiz.questions.find((x: any) => x.id === qId); + const q = quiz.questions.find((x) => x.id === qId); if (!q) return; const cat = q.category.trim().toLowerCase(); @@ -23,7 +23,11 @@ export function CategoryBreakdown({ quiz, attempt, answeredIds, answers }: Categ stats[cat] = { earned: 0, possible: 0 }; } - const formattedQ = { id: q.id, type: q.type, options: q.options }; + const formattedQ = { + id: q.id, + type: q.type as "MULTIPLE_CHOICE" | "SATA", + options: q.options, + }; const points = scoreQuestion(formattedQ, answers[q.id] || []); stats[cat].earned += points; diff --git a/src/components/quizzes/QuizResults.tsx b/src/components/quizzes/QuizResults.tsx index a66e7e8..95f0baa 100644 --- a/src/components/quizzes/QuizResults.tsx +++ b/src/components/quizzes/QuizResults.tsx @@ -4,14 +4,21 @@ import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; import { CategoryBreakdown } from "./CategoryBreakdown"; import { scoreQuestion } from "@/lib/scoring"; +import type { QuizAttempt, QuizQuestion, QuizSummary } from "@/types/study"; interface QuizResultsProps { - quiz: any; // QuizData - attempt: any; // QuizAttempt + quiz: QuizSummary; + attempt: QuizAttempt; onRetake: (missedIds: string[]) => void; onClose?: () => void; } +interface ReviewItem { + question: QuizQuestion; + score: number; + selections: string[]; +} + export function QuizResults({ quiz, attempt, onRetake, onClose }: QuizResultsProps) { let answers: Record = {}; try { @@ -19,16 +26,16 @@ export function QuizResults({ quiz, attempt, onRetake, onClose }: QuizResultsPro } catch {} // Determine non-full-credit questions for review list - const reviewItems: any[] = []; + const reviewItems: ReviewItem[] = []; const missedIds: string[] = []; const answeredIds = Object.keys(answers); - const questionsToReview = quiz.questions.filter((q: any) => answeredIds.includes(q.id)); + const questionsToReview = quiz.questions.filter((q) => answeredIds.includes(q.id)); - questionsToReview.forEach((q: any) => { + questionsToReview.forEach((q) => { const formattedQ = { id: q.id, - type: q.type, + type: q.type as "MULTIPLE_CHOICE" | "SATA", options: q.options, }; const score = scoreQuestion(formattedQ, answers[q.id] || []); @@ -92,7 +99,7 @@ export function QuizResults({ quiz, attempt, onRetake, onClose }: QuizResultsPro +
+

{deck.name}

+

{deck._count.cards} cards

+
+
+ ); +} + +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 ( +
+ +
+

{deck.name}

+

{deck._count.cards} cards

+
+ +
+ ); +} + +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 ( +
+ {children} +
+ ); +} + +export function SpacedRepetitionSets() { + const { classSlug } = useParams<{ classSlug: string }>(); + const [classId, setClassId] = useState(""); + const [sets, setSets] = useState([]); + const [decks, setDecks] = useState([]); + const [loading, setLoading] = useState(true); + const [creating, setCreating] = useState(false); + const [newName, setNewName] = useState(""); + const [editing, setEditing] = useState(null); + const [message, setMessage] = useState(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
Loading repetition sets…
; + + return ( +
+
+

Adaptive review

Spaced repetition

+ +
+ + {message &&
{message}
} + + {creating && ( +
+ 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" /> + + +
+ )} + + +
+ {sets.length === 0 &&

No repetition sets yet

Create one, then drag flashcard decks into it.

} + {sets.map((set) => ( +
+
+
+

{set.name}

+ {set.description &&

{set.description}

} +
+ {set.stats.totalCards} cards + {set.stats.dueCards} due + {set.stats.learningAheadCards > 0 && {set.stats.learningAheadCards} learning} + {set.stats.newCardsAvailable} new today + Limit {set.newCardsPerDay}/day +
+
+
+ {set.stats.totalCards === 0 ? : set.stats.availableCards > 0 ? ( + {set.stats.reviewedCards === 0 ? "Study" : "Continue"} · {set.stats.availableCards} + ) : Caught up} + + +
+
+ `set:${set.id}:deck:${deck.id}`)} strategy={verticalListSortingStrategy}> + + {set.decks.length === 0 ?

Drop flashcard decks here

: set.decks.map((deck) => removeDeck(set, deck)} />)} +
+
+
+ ))} + +
+

Source library

+

Flashcard decks

+

Drag a deck into any repetition set. The original deck stays here and can be reused.

+ {decks.length ?
{decks.map((deck) => )}
:
Create flashcard decks before building a repetition set.
} +
+
+
+ + {editing && ( +
+
+

Edit repetition set

+ + 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" /> + +