Completed studies: finishing a study keeps access after leaving; certificates always shown; no reminders for finished studies; v2.11.0
Publish Container / build (push) Successful in 15m31s

- Completion is stored on the user (completedStudySlugs, sticky) when every lesson is done; backfilled at startup
- Lessons, notes, progress, quiz, community and certificate stay open for completed studies after leaving the enrollment
- Dashboard/My Account list completed studies with Review Lessons and certificate access
- Study page shows a completed banner
- Lesson-release reminders, weekly digest and re-engagement emails skip finished studies
- Fix: streak fields (current/longest/last studied) and firstCompletionAt were dropped on every restart when users were loaded
- Tests for completion/access/re-engagement

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-09 11:42:58 -04:00
1 parent 7301982bb1
commit bb83f3e8c5
13 files changed
+221 -52

No files matched your search

+6 -2
View File
@@ -37,6 +37,7 @@ import {
storeEmailOtp,
verifyEmailOtp,
getStudyUserFromRequest,
getAccessibleStudySlugs,
} from '../study-helpers.js'
import {
sendEmailOtp,
@@ -69,7 +70,9 @@ export function register(app) {
subscribeNewsletter: user?.subscribeNewsletter !== false,
studyRemindersEnabled: user?.studyRemindersEnabled === true,
avatarUrl: user ? getStudyAvatarUrl(user) : '',
enrolledStudySlugs: Array.isArray(user?.enrolledStudySlugs) ? user.enrolledStudySlugs : [],
// Studies the student can open: enrolled ones plus any they have finished
enrolledStudySlugs: user ? getAccessibleStudySlugs(user) : [],
completedStudySlugs: Array.isArray(user?.completedStudySlugs) ? user.completedStudySlugs : [],
emailVerified: user?.emailVerified === true,
totpEnabled: Boolean(user && (user.twoFaMethod === 'app' || user.twoFaMethod === 'email') && (user.twoFaMethod === 'email' || (user.totpSecret && user.totpVerified))),
twoFaMethod: user?.twoFaMethod ?? null,
@@ -211,7 +214,8 @@ export function register(app) {
subscribeNewsletter: user.subscribeNewsletter !== false,
studyRemindersEnabled: user.studyRemindersEnabled === true,
avatarUrl: getStudyAvatarUrl(user.username),
enrolledStudySlugs: user.enrolledStudySlugs ?? [],
enrolledStudySlugs: getAccessibleStudySlugs(user),
completedStudySlugs: Array.isArray(user.completedStudySlugs) ? user.completedStudySlugs : [],
})
})