From 5a8f50f67b0cb04900678b8e836f6128f19db593 Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Mon, 30 Mar 2026 08:24:55 +0100 Subject: [PATCH] feat: certification rules, content sanitization, handle draft course updates and reset content, better redis logging --- AGENTS.md | 50 + PRD-TRACKER.md | 2 +- apps/api/package.json | 5 +- apps/api/src/middlewares/rate-limiter.ts | 5 +- apps/api/src/routes/course/course.ts | 32 + .../src/routes/organization/organization.ts | 42 +- .../services/course-import/course-import.ts | 94 +- apps/api/src/services/course/clone.ts | 3 +- apps/api/src/services/course/completion.ts | 275 +++++ apps/api/src/services/course/course.ts | 67 +- apps/api/src/services/course/invite.ts | 13 +- apps/api/src/services/course/people.ts | 4 +- apps/api/src/services/exercise/exercise.ts | 70 +- apps/api/src/services/exercise/utils.ts | 1 + apps/api/src/services/lesson-language.ts | 7 +- apps/api/src/services/lesson/lesson.ts | 5 +- apps/api/src/services/newsfeed/newsfeed.ts | 18 +- apps/api/src/services/organization.ts | 76 +- .../api/src/services/organization/audience.ts | 357 ++++--- .../api/src/services/submission/submission.ts | 152 +-- apps/api/src/types/org.ts | 16 + apps/api/src/utils/course-completion.ts | 89 ++ apps/api/src/utils/errors.ts | 1 + apps/api/src/utils/redis/redis.ts | 27 +- apps/api/src/utils/s3.ts | 6 +- apps/api/src/utils/sanitize-html.ts | 39 + apps/dashboard/src/app.css | 25 + .../audience-delete-confirmation.svelte | 42 + .../components/audience-member-row.svelte | 36 +- .../components/audience-table-toolbar.svelte | 39 +- .../audience/components/audience-table.svelte | 11 +- .../lib/features/audience/components/index.ts | 1 + .../features/audience/pages/audience.svelte | 214 +++- .../lib/features/course/api/course.svelte.ts | 36 +- .../features/course/api/exercise.svelte.ts | 10 +- .../lib/features/course/api/lesson.svelte.ts | 13 +- .../ceritficate/certificate-design.svelte | 134 +++ .../ceritficate/certificate-edit.svelte | 38 + .../ceritficate/certificate-settings.svelte | 186 ++++ .../course-completion-modal.svelte | 57 + .../components/ceritficate/design.svelte | 196 ---- .../student-certificate/index.svelte | 2 +- .../unlocked-certificate.svelte | 99 +- .../content/content-create-modal.svelte | 2 +- .../content/exercise-create-stepper.svelte | 2 +- .../components/exercise/edit-mode.svelte | 46 +- .../exercise/exercise-settings-tab.svelte | 56 + .../course/components/exercise/store.ts | 13 +- .../components/exercise/view-mode.svelte | 21 +- .../lesson/content-navigation-actions.svelte | 15 +- .../features/course/components/list.svelte | 82 +- .../people/existing-students-section.svelte | 184 ++-- .../components/people/invitation-modal.svelte | 6 +- .../components/question-container.svelte | 5 + .../course-content-sidebar-navigation.svelte | 24 +- .../sidebar/course-content-tree.svelte | 3 +- .../sidebar/course-sidebar-navigation.svelte | 4 +- .../features/course/pages/certificates.svelte | 10 +- .../lib/features/course/pages/courses.svelte | 31 +- .../lib/features/course/pages/exercise.svelte | 84 +- .../lib/features/course/pages/settings.svelte | 7 +- .../src/lib/features/course/store.ts | 26 +- .../course/store/course-completion-modal.ts | 16 + .../course/utils/certificate-utils.ts | 10 + .../lib/features/course/utils/constants.ts | 13 + .../lib/features/course/utils/functions.ts | 11 +- .../src/lib/features/course/utils/types.ts | 12 + .../lib/features/lms/pages/mylearning.svelte | 2 +- .../src/lib/features/org/api/org.svelte.ts | 82 +- .../org/utils/audience-query-utils.ts | 58 + .../src/lib/features/org/utils/types.ts | 16 +- .../features/settings/pages/domains.svelte | 2 +- .../src/lib/features/ui/confetti/store.ts | 7 + .../components/editor/editor.svelte | 22 +- .../src/lib/features/ui/question/constants.ts | 3 +- .../src/lib/utils/functions/validator.ts | 11 - .../src/lib/utils/services/api/base.svelte.ts | 4 + .../src/lib/utils/services/api/index.ts | 43 +- .../src/lib/utils/translations/da.json | 35 +- .../src/lib/utils/translations/de.json | 35 +- .../src/lib/utils/translations/en.json | 7 +- .../src/lib/utils/translations/es.json | 37 +- .../src/lib/utils/translations/fr.json | 35 +- .../src/lib/utils/translations/hi.json | 35 +- .../src/lib/utils/translations/pl.json | 35 +- .../src/lib/utils/translations/pt.json | 35 +- .../src/lib/utils/translations/ru.json | 35 +- .../src/lib/utils/translations/vi.json | 37 +- apps/dashboard/src/routes/+error.svelte | 3 +- .../src/routes/courses/[id]/+layout.svelte | 2 + .../courses/[id]/certificates/+page.svelte | 150 ++- .../exercises/[exerciseId]/+page.server.ts | 6 +- .../[id]/exercises/[exerciseId]/+page.svelte | 5 +- .../routes/courses/[id]/settings/+page.svelte | 5 +- .../org/[slug]/audience/+page.server.ts | 15 +- .../routes/org/[slug]/audience/+page.svelte | 4 +- .../routes/org/[slug]/courses/+page.server.ts | 5 - .../queries/course/certification-exercise.ts | 80 ++ packages/db/src/queries/course/course.ts | 147 ++- packages/db/src/queries/course/people.ts | 36 + .../src/queries/organization/organization.ts | 168 ++- .../db/src/queries/submission/submission.ts | 50 +- packages/db/src/schema.ts | 16 +- packages/db/src/scripts/seed.ts | 22 +- packages/db/src/utils/seed/course.ts | 15 +- packages/db/src/utils/seed/organization.ts | 15 +- .../db/src/utils/seed/organizationPlan.ts | 28 + .../db/src/utils/seed/organizationmember.ts | 27 +- packages/db/users.json | 102 ++ packages/email/src/emails/index.ts | 3 + .../src/emails/student-course-completion.ts | 33 + .../src/emails/student-course-welcome.ts | 11 +- packages/email/src/utils/constants.ts | 1 + .../question-types/src/question-scoring.ts | 349 ++++++ packages/ui/package.json | 1 + .../editor/ui/components/CodeBlock.svelte | 2 +- .../shared/option-correctness-badges.svelte | 14 +- .../multi-select-list.svelte | 71 +- .../ui/src/custom/multi-select-list/types.ts | 2 +- packages/ui/src/custom/search/search.svelte | 21 +- packages/ui/src/output.css | 2 +- packages/ui/src/tools/sanitize.ts | 87 +- packages/ui/tsconfig.json | 4 +- packages/utils/src/functions/index.ts | 1 + packages/utils/src/functions/sanitize.ts | 50 + .../utils/src/validation/course/course.ts | 15 +- .../utils/src/validation/exercise/exercise.ts | 1 + .../src/validation/organization/audience.ts | 15 + pnpm-lock.yaml | 353 ++++++- prd/classes/README.md | 993 ------------------ prd/programs/README.md | 993 ++++++++++++++++++ 131 files changed, 5643 insertions(+), 2134 deletions(-) create mode 100644 apps/api/src/services/course/completion.ts create mode 100644 apps/api/src/utils/course-completion.ts create mode 100644 apps/api/src/utils/sanitize-html.ts create mode 100644 apps/dashboard/src/lib/features/audience/components/audience-delete-confirmation.svelte create mode 100644 apps/dashboard/src/lib/features/course/components/ceritficate/certificate-design.svelte create mode 100644 apps/dashboard/src/lib/features/course/components/ceritficate/certificate-edit.svelte create mode 100644 apps/dashboard/src/lib/features/course/components/ceritficate/certificate-settings.svelte create mode 100644 apps/dashboard/src/lib/features/course/components/ceritficate/course-completion-modal.svelte delete mode 100644 apps/dashboard/src/lib/features/course/components/ceritficate/design.svelte create mode 100644 apps/dashboard/src/lib/features/course/components/exercise/exercise-settings-tab.svelte create mode 100644 apps/dashboard/src/lib/features/course/store/course-completion-modal.ts create mode 100644 apps/dashboard/src/lib/features/course/utils/certificate-utils.ts create mode 100644 apps/dashboard/src/lib/features/org/utils/audience-query-utils.ts create mode 100644 packages/db/src/queries/course/certification-exercise.ts create mode 100644 packages/db/src/utils/seed/organizationPlan.ts create mode 100644 packages/email/src/emails/student-course-completion.ts create mode 100644 packages/question-types/src/question-scoring.ts create mode 100644 packages/utils/src/functions/sanitize.ts delete mode 100644 prd/classes/README.md create mode 100644 prd/programs/README.md diff --git a/AGENTS.md b/AGENTS.md index a2bd02d5..7207416e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,8 @@ This document collects implementation rules and workflow conventions for code ch ## Naming Convention - Use kebab-case for files (e.g. `user-profile.svelte`, `org.svelte.ts`). +- **Variables must read clearly at the call site.** Avoid opaque single-letter or ultra-short names (`d`, `x`, `tmp`) unless they match a universal convention (e.g. `i` in a trivial index loop). Prefer names that state intent (`deadlineDate`, `parsedUrl`, `nextSection`). +- **After an early `return` (or other guard exit), leave a blank line** before the next statement so the “happy path” block is visually separated from guards. ## Creating a New Route @@ -177,6 +179,54 @@ You can bind form controls directly to a writable store’s properties. Svelte c ``` +### Form Structure + +For large dashboard forms, group fields by section instead of rendering one long flat list of controls. + +- Use a top-level `Field.Group` as the form container. +- Split each logical section into its own `Field.Set`. +- Use `Field.Legend` for the section title. +- Separate sibling sections with `Field.Separator`. +- Inside each section, wrap related controls in an inner `Field.Group`. +- Put every control inside `Field.Field`, and use `orientation="horizontal"` for toggle or checkbox rows. +- Use `Field.Description` for helper copy that applies to a whole section. + +Pattern: + +```svelte + + + {$t('settings.section.heading')} + + + + {$t('settings.section.toggle')} + + + + + {$t('settings.section.title')} + + + + + {$t('settings.section.description')} +