From 50d6ee31116281102e6d0020cebf06ba7dda30d0 Mon Sep 17 00:00:00 2001 From: Adeyemi Adetunji Date: Thu, 25 Dec 2025 05:09:27 +0100 Subject: [PATCH] feat: mock exercise template api (#613) * feat: move mock exercise template to api * feat: add template migration file * chore: - remove exercise templates form api - rework api to pull exercise templates from db - add exercise template seed to seed file - declutter seed file * chore: - refactor template fetch to be dependent on the selected tag - add tag to schema, remote db and template's seed file - clear remote db's template table and replace data and their respective tags * chore: add number of questions and center loading screen on the frontend * chore: pr fixes * chore: type fixes * chore: - move seed functionality to /seed folder - add typing for individual seed functions - pr fixes * chore: pr fixes v2 * update output css * refactor: update seeding script and schema definitions - Renamed `seedTypes` to `seedQuestionTypes` for clarity in the seeding process. - Enhanced the seeding script to include a command-line interface for better usability, allowing specific seed functions to be executed based on flags. - Removed redundant unique constraints in the schema definitions for `courseNewsfeedComment` and `exerciseTemplate` to streamline database structure. * feat: add email verification error handling page and update session management - Introduced a new page for handling email verification errors, providing user-friendly messages based on error types. - Updated session management logic to improve handling of public routes and session states. - Enhanced security policies in the Svelte configuration to include additional sources for styles and scripts. - Removed the old email verification error page to streamline the codebase. * move the exercise template to cloudflare cdn --------- Co-authored-by: Rotimi Best --- ARCHITECHTURE.md | 59 +- apps/api/src/app.ts | 2 + apps/api/src/routes/exercise/index.ts | 1 + apps/api/src/routes/exercise/template.ts | 38 + apps/api/src/services/exercise/template.ts | 54 + apps/api/src/utils/template.ts | 11 + .../Lesson/Exercises/NewExerciseModal.svelte | 94 +- .../components/Lesson/Exercises/index.svelte | 4 +- .../features/exercise-template/api/index.ts | 1 + .../exercise-template/api/template.svelte.ts | 39 + .../exercise-template/utils/constants.ts | 12 + .../features/exercise-template/utils/types.ts | 16 + .../src/lib/mocks/css/001_css_syntax.ts | 118 --- .../src/lib/mocks/css/002_css_selectors.ts | 118 --- .../src/lib/mocks/css/003_css_howTo.ts | 118 --- .../src/lib/mocks/css/004_css_comments.ts | 120 --- .../src/lib/mocks/css/005_css_colors.ts | 126 --- .../src/lib/mocks/css/006_css_backgrounds.ts | 129 --- .../src/lib/mocks/css/007_css_borders.ts | 126 --- .../src/lib/mocks/css/008_css_margin.ts | 126 --- .../src/lib/mocks/css/009_css_padding.ts | 138 --- .../src/lib/mocks/css/010_css_dimentions.ts | 139 --- .../src/lib/mocks/css/011_css_boxModel.ts | 137 --- .../src/lib/mocks/css/012_css_outline.ts | 132 --- .../src/lib/mocks/css/013_css_text.ts | 137 --- .../src/lib/mocks/css/014_css_fonts.ts | 139 --- .../src/lib/mocks/css/015_css_icons.ts | 139 --- .../src/lib/mocks/css/016_css_links.ts | 139 --- .../src/lib/mocks/css/017_css_lists.ts | 139 --- .../src/lib/mocks/css/018_css_tables.ts | 139 --- .../src/lib/mocks/css/019_css_display.ts | 129 --- .../src/lib/mocks/css/020_css_maxWidth.ts | 119 --- apps/dashboard/src/lib/mocks/css/index.ts | 45 - .../src/lib/mocks/git/001_git_newFiles.ts | 125 --- .../src/lib/mocks/git/002_git_staging.ts | 125 --- .../src/lib/mocks/git/003_git_commit.ts | 125 --- .../src/lib/mocks/git/004_git_amend.ts | 128 --- .../src/lib/mocks/git/005_git_branch.ts | 125 --- .../src/lib/mocks/git/006_git_branchMerge.ts | 125 --- .../src/lib/mocks/git/007_git_pull.ts | 128 --- .../src/lib/mocks/git/008_git_push.ts | 125 --- .../src/lib/mocks/git/009_git_githubBranch.ts | 125 --- .../src/lib/mocks/git/010_git_branchPull.ts | 128 --- .../src/lib/mocks/git/011_git_branchPush.ts | 212 ---- .../src/lib/mocks/git/012_git_githubFlow.ts | 198 ---- .../src/lib/mocks/git/013_git_githubPages.ts | 198 ---- .../src/lib/mocks/git/014_git_githubFork.ts | 198 ---- .../src/lib/mocks/git/015_git_clone.ts | 199 ---- .../src/lib/mocks/git/016_git_pullRequest.ts | 202 ---- .../src/lib/mocks/git/017_git_ignore.ts | 198 ---- .../dashboard/src/lib/mocks/git/018_gitSsh.ts | 185 ---- .../src/lib/mocks/git/019_git_revert.ts | 185 ---- .../src/lib/mocks/git/020_git_reset.ts | 159 --- apps/dashboard/src/lib/mocks/git/index.ts | 45 - .../src/lib/mocks/html/001_html_elements.ts | 122 --- .../src/lib/mocks/html/002_html_attributes.ts | 119 --- .../src/lib/mocks/html/003_html_headings.ts | 117 -- .../src/lib/mocks/html/004_html_paragraph.ts | 119 --- .../src/lib/mocks/html/005_html_styles.ts | 120 --- .../src/lib/mocks/html/006_html_formatting.ts | 224 ---- .../src/lib/mocks/html/007_html_quotations.ts | 224 ---- .../src/lib/mocks/html/008_html_comments.ts | 220 ---- .../src/lib/mocks/html/009_html_colors.ts | 224 ---- .../src/lib/mocks/html/010_html_css.ts | 129 --- .../src/lib/mocks/html/011_html_links.ts | 224 ---- .../src/lib/mocks/html/012_html_images.ts | 134 --- .../src/lib/mocks/html/013_html_favicon.ts | 137 --- .../src/lib/mocks/html/014_html_pageTitle.ts | 135 --- .../src/lib/mocks/html/015_html_tables.ts | 137 --- .../src/lib/mocks/html/016_html_lists.ts | 130 --- .../src/lib/mocks/html/017_html_blocks.ts | 122 --- .../src/lib/mocks/html/018_html_classes.ts | 146 --- .../src/lib/mocks/html/019_html_id.ts | 129 --- .../src/lib/mocks/html/020_html_iframes.ts | 181 ---- apps/dashboard/src/lib/mocks/html/index.ts | 45 - apps/dashboard/src/lib/mocks/index.ts | 91 -- .../src/lib/mocks/js/001_js_statements.ts | 228 ---- .../src/lib/mocks/js/002_js_syntax.ts | 224 ---- .../src/lib/mocks/js/003_js_comments.ts | 220 ---- .../src/lib/mocks/js/004_js_variables.ts | 119 --- apps/dashboard/src/lib/mocks/js/005_js_let.ts | 216 ---- .../src/lib/mocks/js/006_js_const.ts | 220 ---- .../src/lib/mocks/js/007_js_operators.ts | 212 ---- .../src/lib/mocks/js/008_js_arithmetic.ts | 212 ---- .../src/lib/mocks/js/009_js_assignment.ts | 224 ---- .../src/lib/mocks/js/010_js_dataTypes.ts | 224 ---- .../src/lib/mocks/js/011_js_functions.ts | 228 ---- .../src/lib/mocks/js/012_js_objects.ts | 224 ---- .../src/lib/mocks/js/013_js_events.ts | 224 ---- .../src/lib/mocks/js/014_js_strings.ts | 224 ---- .../src/lib/mocks/js/015_js_stringMethods.ts | 227 ---- .../src/lib/mocks/js/016_stringSearch.ts | 226 ---- .../lib/mocks/js/017_js_stringTemplates.ts | 227 ---- .../src/lib/mocks/js/018_js_numbers.ts | 224 ---- .../src/lib/mocks/js/019_js_bigInt.ts | 232 ---- .../src/lib/mocks/js/020_js_numberMethods.ts | 220 ---- apps/dashboard/src/lib/mocks/js/index.ts | 45 - .../src/lib/mocks/node/001_node_module.ts | 128 --- .../src/lib/mocks/node/002_node_httpModule.ts | 123 --- .../src/lib/mocks/node/003_node_fileSystem.ts | 124 --- .../src/lib/mocks/node/004_node_urlModule.ts | 202 ---- .../src/lib/mocks/node/005_node_npm.ts | 211 ---- .../src/lib/mocks/node/006_node_events.ts | 211 ---- .../lib/mocks/node/007_node_uploadFiles.ts | 211 ---- .../src/lib/mocks/node/008_node_email.ts | 211 ---- .../lib/mocks/node/009_node_sqlDatabase.ts | 211 ---- .../lib/mocks/node/010_node_sqlCreateTable.ts | 211 ---- .../lib/mocks/node/011_node_sqlInsertInto.ts | 211 ---- .../lib/mocks/node/012_node_sqlSelectForm.ts | 211 ---- .../src/lib/mocks/node/013_node_sqlWhere.ts | 211 ---- .../src/lib/mocks/node/014_node_sqlOrderBy.ts | 211 ---- .../src/lib/mocks/node/015_node_sqlDelete.ts | 198 ---- .../lib/mocks/node/016_node_sqlDropTable.ts | 194 ---- .../src/lib/mocks/node/017_node_sqlUpdate.ts | 181 ---- .../src/lib/mocks/node/018_node_sqlLimit.ts | 181 ---- .../src/lib/mocks/node/019_node_sqlJoin.ts | 201 ---- .../mocks/node/020_node_mongodbDatabase.ts | 201 ---- apps/dashboard/src/lib/mocks/node/index.ts | 45 - .../src/lib/mocks/php/001_php_syntax.ts | 218 ---- .../src/lib/mocks/php/002_php_comments.ts | 214 ---- .../src/lib/mocks/php/003_php_variables.ts | 210 ---- .../src/lib/mocks/php/004_php_echo.ts | 186 ---- .../src/lib/mocks/php/005_php_dataTypes.ts | 226 ---- .../src/lib/mocks/php/006_php_string.ts | 264 ----- .../src/lib/mocks/php/007_php_numbers.ts | 252 ----- .../src/lib/mocks/php/008_php_math.ts | 252 ----- .../src/lib/mocks/php/009_php_constants.ts | 252 ----- .../src/lib/mocks/php/010_php_operators.ts | 264 ----- .../src/lib/mocks/php/011_php_conditionals.ts | 265 ----- .../src/lib/mocks/php/012_php_switch.ts | 266 ----- .../src/lib/mocks/php/013_php_loops.ts | 265 ----- .../src/lib/mocks/php/014_php_functions.ts | 264 ----- .../src/lib/mocks/php/015_php_arrays.ts | 264 ----- .../src/lib/mocks/php/016_php_superGlobal.ts | 264 ----- .../src/lib/mocks/php/017_php_regex.ts | 264 ----- .../src/lib/mocks/php/018_php_forms.ts | 264 ----- .../lib/mocks/php/019_php_formValidation.ts | 184 ---- .../src/lib/mocks/php/020_php_formRequired.ts | 184 ---- apps/dashboard/src/lib/mocks/php/index.ts | 45 - .../src/lib/mocks/python/001_python_syntax.ts | 127 --- .../lib/mocks/python/002_python_comments.ts | 126 --- .../lib/mocks/python/003_python_variables.ts | 128 --- .../lib/mocks/python/004_python_dataTypes.ts | 128 --- .../lib/mocks/python/005_python_numbers.ts | 128 --- .../lib/mocks/python/006_python_casting.ts | 126 --- .../lib/mocks/python/007_python_strings.ts | 128 --- .../lib/mocks/python/008_python_booleans.ts | 124 --- .../lib/mocks/python/009_python_operators.ts | 122 --- .../src/lib/mocks/python/010_python_lists.ts | 126 --- .../src/lib/mocks/python/011_python_tuples.ts | 122 --- .../src/lib/mocks/python/012_python_sets.ts | 122 --- .../mocks/python/013_python_dictionaries.ts | 122 --- .../mocks/python/014_python_conditionals.ts | 122 --- .../lib/mocks/python/015_python_whileLoops.ts | 122 --- .../lib/mocks/python/016_python_forLoops.ts | 122 --- .../lib/mocks/python/017_python_functions.ts | 122 --- .../src/lib/mocks/python/018_python_lambda.ts | 121 --- .../src/lib/mocks/python/019_python_arrays.ts | 125 --- .../lib/mocks/python/020_python_classes.ts | 131 --- apps/dashboard/src/lib/mocks/python/index.ts | 45 - .../src/lib/mocks/react/001_react_es6.ts | 119 --- .../react/002_react_es6ArrowFunctions.ts | 119 --- .../lib/mocks/react/003_react_es6Variables.ts | 120 --- .../mocks/react/004_react_es6ArrayMethods.ts | 122 --- .../mocks/react/005_react_es6Destructuring.ts | 118 --- .../react/006_react_es6SpreadOperator.ts | 115 -- .../lib/mocks/react/007_react_es6Modules.ts | 119 --- .../react/008_react_es6TenaryOperator.ts | 122 --- .../src/lib/mocks/react/009_react_jsx.ts | 121 --- .../lib/mocks/react/010_react_components.ts | 115 -- .../mocks/react/011_react_classComponents.ts | 127 --- .../src/lib/mocks/react/012_react_props.ts | 118 --- .../src/lib/mocks/react/013_react_events.ts | 114 -- .../lib/mocks/react/014_react_conditionals.ts | 173 --- .../src/lib/mocks/react/015_react_lists.ts | 118 --- .../src/lib/mocks/react/016_react_forms.ts | 118 --- .../src/lib/mocks/react/017_react_router.ts | 118 --- .../src/lib/mocks/react/018_react_memo.ts | 122 --- .../lib/mocks/react/019_react_cssStyling.ts | 123 --- .../lib/mocks/react/020_react_sassStyling.ts | 123 --- .../react/021_react_importingAndExporting.ts | 213 ---- .../react/022_react_extractingStateLogic.ts | 212 ---- .../lib/mocks/react/023_react_sharingState.ts | 172 --- .../mocks/react/024_react_updatingObjects.ts | 172 --- .../mocks/react/025_react_updatingArrays.ts | 174 --- .../src/lib/mocks/react/026_react_useState.ts | 140 --- .../lib/mocks/react/027_react_useContext.ts | 134 --- .../lib/mocks/react/028_react_useEffect.ts | 137 --- .../lib/mocks/react/029_react_useReducer.ts | 143 --- .../src/lib/mocks/react/030_react_useRef.ts | 134 --- .../lib/mocks/react/031_react_useCallBack.ts | 163 --- .../lib/mocks/react/032_react_customHooks.ts | 151 --- .../src/lib/mocks/react/033_react_useMemo.ts | 163 --- apps/dashboard/src/lib/mocks/react/index.ts | 71 -- .../mocks/typescript/001_ts_simpleTypes.ts | 228 ---- .../mocks/typescript/002_ts_specialTypes.ts | 305 ------ .../src/lib/mocks/typescript/003_ts_arrays.ts | 305 ------ .../src/lib/mocks/typescript/004_ts_tuples.ts | 265 ----- .../mocks/typescript/005_ts_objectTypes.ts | 267 ----- .../src/lib/mocks/typescript/006_ts_enums.ts | 264 ----- .../lib/mocks/typescript/007_ts_interfaces.ts | 264 ----- .../lib/mocks/typescript/008_ts_unionTypes.ts | 264 ----- .../lib/mocks/typescript/009_ts_functions.ts | 247 ----- .../lib/mocks/typescript/010_ts_casting.ts | 132 --- .../lib/mocks/typescript/011_ts_classes.ts | 133 --- .../mocks/typescript/012_ts_basicGenerics.ts | 145 --- .../mocks/typescript/013_ts_utilityTypes.ts | 138 --- .../src/lib/mocks/typescript/014_ts_keyOf.ts | 132 --- .../src/lib/mocks/typescript/015_ts_null.ts | 154 --- .../typescript/016_ts_definitelyTyped.ts | 155 --- .../src/lib/mocks/typescript/index.ts | 37 - apps/dashboard/src/lib/mocks/utils.ts | 49 - .../src/lib/mocks/vue/001_vue_directives.ts | 172 --- .../src/lib/mocks/vue/002_vue_vBind.ts | 133 --- .../src/lib/mocks/vue/003_vue_vIf.ts | 133 --- .../src/lib/mocks/vue/004_vue_vShow.ts | 133 --- .../src/lib/mocks/vue/005_vue_vFor.ts | 133 --- .../src/lib/mocks/vue/006_vue-vEvents.ts | 120 --- .../src/lib/mocks/vue/007_vue_vOn.ts | 120 --- .../src/lib/mocks/vue/008_vue_methods.ts | 120 --- .../lib/mocks/vue/009_vue_eventModifiers.ts | 120 --- .../src/lib/mocks/vue/010_vue_forms.ts | 172 --- .../src/lib/mocks/vue/011_vue_vModel.ts | 202 ---- .../src/lib/mocks/vue/012_vue_cssBinding.ts | 198 ---- .../mocks/vue/013_vue_computedProperties.ts | 172 --- .../src/lib/mocks/vue/014_vue_watchers.ts | 185 ---- .../src/lib/mocks/vue/015_vue_templates.ts | 185 ---- .../src/lib/mocks/vue/016_vue_components.ts | 185 ---- .../src/lib/mocks/vue/017_vue_props.ts | 185 ---- .../src/lib/mocks/vue/018_vue_vForComp.ts | 185 ---- .../src/lib/mocks/vue/019_vue_emit.ts | 185 ---- .../mocks/vue/020_vue_fallThroughAttribute.ts | 185 ---- apps/dashboard/src/lib/mocks/vue/index.ts | 45 - .../src/lib/utils/translations/en.json | 5 +- apps/dashboard/src/lib/utils/types/index.ts | 1 + .../(auth)/verify-email-error/+page.svelte | 96 ++ apps/dashboard/src/routes/+layout.svelte | 6 +- .../src/routes/courses/[id]/+layout.svelte | 4 +- .../routes/verify-email-error/+page.svelte | 147 --- apps/dashboard/svelte.config.js | 14 +- packages/db/src/queries/template/index.ts | 1 + packages/db/src/queries/template/template.ts | 18 + packages/db/src/schema.ts | 45 +- packages/db/src/scripts/seed.ts | 999 ++++-------------- packages/db/src/types.ts | 5 +- packages/db/src/utils/seed/account.ts | 69 ++ packages/db/src/utils/seed/course.ts | 114 ++ packages/db/src/utils/seed/exercise.ts | 63 ++ .../db/src/utils/seed/exerciseTemplate.ts | 39 + packages/db/src/utils/seed/group.ts | 44 + packages/db/src/utils/seed/groupmember.ts | 53 + packages/db/src/utils/seed/lesson.ts | 98 ++ packages/db/src/utils/seed/option.ts | 136 +++ packages/db/src/utils/seed/organization.ts | 29 + .../db/src/utils/seed/organizationmember.ts | 35 + packages/db/src/utils/seed/profile.ts | 26 + packages/db/src/utils/seed/question.ts | 67 ++ packages/db/src/utils/seed/questionType.ts | 34 + packages/db/src/utils/seed/role.ts | 31 + packages/db/src/utils/seed/submission.ts | 25 + packages/db/src/utils/seed/users.ts | 27 + packages/utils/src/validation/mocks/index.ts | 1 + .../utils/src/validation/mocks/template.ts | 12 + 263 files changed, 1568 insertions(+), 37195 deletions(-) create mode 100644 apps/api/src/routes/exercise/index.ts create mode 100644 apps/api/src/routes/exercise/template.ts create mode 100644 apps/api/src/services/exercise/template.ts create mode 100644 apps/api/src/utils/template.ts create mode 100644 apps/dashboard/src/lib/features/exercise-template/api/index.ts create mode 100644 apps/dashboard/src/lib/features/exercise-template/api/template.svelte.ts create mode 100644 apps/dashboard/src/lib/features/exercise-template/utils/constants.ts create mode 100644 apps/dashboard/src/lib/features/exercise-template/utils/types.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/001_css_syntax.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/002_css_selectors.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/003_css_howTo.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/004_css_comments.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/005_css_colors.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/006_css_backgrounds.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/007_css_borders.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/008_css_margin.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/009_css_padding.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/010_css_dimentions.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/011_css_boxModel.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/012_css_outline.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/013_css_text.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/014_css_fonts.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/015_css_icons.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/016_css_links.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/017_css_lists.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/018_css_tables.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/019_css_display.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/020_css_maxWidth.ts delete mode 100644 apps/dashboard/src/lib/mocks/css/index.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/001_git_newFiles.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/002_git_staging.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/003_git_commit.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/004_git_amend.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/005_git_branch.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/006_git_branchMerge.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/007_git_pull.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/008_git_push.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/009_git_githubBranch.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/010_git_branchPull.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/011_git_branchPush.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/012_git_githubFlow.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/013_git_githubPages.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/014_git_githubFork.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/015_git_clone.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/016_git_pullRequest.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/017_git_ignore.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/018_gitSsh.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/019_git_revert.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/020_git_reset.ts delete mode 100644 apps/dashboard/src/lib/mocks/git/index.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/001_html_elements.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/002_html_attributes.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/003_html_headings.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/004_html_paragraph.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/005_html_styles.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/006_html_formatting.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/007_html_quotations.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/008_html_comments.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/009_html_colors.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/010_html_css.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/011_html_links.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/012_html_images.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/013_html_favicon.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/014_html_pageTitle.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/015_html_tables.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/016_html_lists.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/017_html_blocks.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/018_html_classes.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/019_html_id.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/020_html_iframes.ts delete mode 100644 apps/dashboard/src/lib/mocks/html/index.ts delete mode 100644 apps/dashboard/src/lib/mocks/index.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/001_js_statements.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/002_js_syntax.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/003_js_comments.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/004_js_variables.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/005_js_let.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/006_js_const.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/007_js_operators.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/008_js_arithmetic.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/009_js_assignment.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/010_js_dataTypes.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/011_js_functions.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/012_js_objects.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/013_js_events.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/014_js_strings.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/015_js_stringMethods.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/016_stringSearch.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/017_js_stringTemplates.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/018_js_numbers.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/019_js_bigInt.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/020_js_numberMethods.ts delete mode 100644 apps/dashboard/src/lib/mocks/js/index.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/001_node_module.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/002_node_httpModule.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/003_node_fileSystem.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/004_node_urlModule.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/005_node_npm.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/006_node_events.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/007_node_uploadFiles.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/008_node_email.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/009_node_sqlDatabase.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/010_node_sqlCreateTable.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/011_node_sqlInsertInto.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/012_node_sqlSelectForm.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/013_node_sqlWhere.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/014_node_sqlOrderBy.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/015_node_sqlDelete.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/016_node_sqlDropTable.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/017_node_sqlUpdate.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/018_node_sqlLimit.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/019_node_sqlJoin.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/020_node_mongodbDatabase.ts delete mode 100644 apps/dashboard/src/lib/mocks/node/index.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/001_php_syntax.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/002_php_comments.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/003_php_variables.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/004_php_echo.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/005_php_dataTypes.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/006_php_string.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/007_php_numbers.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/008_php_math.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/009_php_constants.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/010_php_operators.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/011_php_conditionals.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/012_php_switch.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/013_php_loops.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/014_php_functions.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/015_php_arrays.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/016_php_superGlobal.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/017_php_regex.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/018_php_forms.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/019_php_formValidation.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/020_php_formRequired.ts delete mode 100644 apps/dashboard/src/lib/mocks/php/index.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/001_python_syntax.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/002_python_comments.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/003_python_variables.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/004_python_dataTypes.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/005_python_numbers.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/006_python_casting.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/007_python_strings.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/008_python_booleans.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/009_python_operators.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/010_python_lists.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/011_python_tuples.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/012_python_sets.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/013_python_dictionaries.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/014_python_conditionals.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/015_python_whileLoops.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/016_python_forLoops.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/017_python_functions.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/018_python_lambda.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/019_python_arrays.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/020_python_classes.ts delete mode 100644 apps/dashboard/src/lib/mocks/python/index.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/001_react_es6.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/002_react_es6ArrowFunctions.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/003_react_es6Variables.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/004_react_es6ArrayMethods.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/005_react_es6Destructuring.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/006_react_es6SpreadOperator.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/007_react_es6Modules.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/008_react_es6TenaryOperator.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/009_react_jsx.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/010_react_components.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/011_react_classComponents.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/012_react_props.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/013_react_events.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/014_react_conditionals.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/015_react_lists.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/016_react_forms.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/017_react_router.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/018_react_memo.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/019_react_cssStyling.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/020_react_sassStyling.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/021_react_importingAndExporting.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/022_react_extractingStateLogic.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/023_react_sharingState.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/024_react_updatingObjects.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/025_react_updatingArrays.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/026_react_useState.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/027_react_useContext.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/028_react_useEffect.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/029_react_useReducer.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/030_react_useRef.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/031_react_useCallBack.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/032_react_customHooks.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/033_react_useMemo.ts delete mode 100644 apps/dashboard/src/lib/mocks/react/index.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/001_ts_simpleTypes.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/002_ts_specialTypes.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/003_ts_arrays.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/004_ts_tuples.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/005_ts_objectTypes.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/006_ts_enums.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/007_ts_interfaces.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/008_ts_unionTypes.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/009_ts_functions.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/010_ts_casting.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/011_ts_classes.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/012_ts_basicGenerics.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/013_ts_utilityTypes.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/014_ts_keyOf.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/015_ts_null.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/016_ts_definitelyTyped.ts delete mode 100644 apps/dashboard/src/lib/mocks/typescript/index.ts delete mode 100644 apps/dashboard/src/lib/mocks/utils.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/001_vue_directives.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/002_vue_vBind.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/003_vue_vIf.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/004_vue_vShow.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/005_vue_vFor.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/006_vue-vEvents.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/007_vue_vOn.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/008_vue_methods.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/009_vue_eventModifiers.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/010_vue_forms.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/011_vue_vModel.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/012_vue_cssBinding.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/013_vue_computedProperties.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/014_vue_watchers.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/015_vue_templates.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/016_vue_components.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/017_vue_props.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/018_vue_vForComp.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/019_vue_emit.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/020_vue_fallThroughAttribute.ts delete mode 100644 apps/dashboard/src/lib/mocks/vue/index.ts create mode 100644 apps/dashboard/src/routes/(auth)/verify-email-error/+page.svelte delete mode 100644 apps/dashboard/src/routes/verify-email-error/+page.svelte create mode 100644 packages/db/src/queries/template/index.ts create mode 100644 packages/db/src/queries/template/template.ts create mode 100644 packages/db/src/utils/seed/account.ts create mode 100644 packages/db/src/utils/seed/course.ts create mode 100644 packages/db/src/utils/seed/exercise.ts create mode 100644 packages/db/src/utils/seed/exerciseTemplate.ts create mode 100644 packages/db/src/utils/seed/group.ts create mode 100644 packages/db/src/utils/seed/groupmember.ts create mode 100644 packages/db/src/utils/seed/lesson.ts create mode 100644 packages/db/src/utils/seed/option.ts create mode 100644 packages/db/src/utils/seed/organization.ts create mode 100644 packages/db/src/utils/seed/organizationmember.ts create mode 100644 packages/db/src/utils/seed/profile.ts create mode 100644 packages/db/src/utils/seed/question.ts create mode 100644 packages/db/src/utils/seed/questionType.ts create mode 100644 packages/db/src/utils/seed/role.ts create mode 100644 packages/db/src/utils/seed/submission.ts create mode 100644 packages/db/src/utils/seed/users.ts create mode 100644 packages/utils/src/validation/mocks/index.ts create mode 100644 packages/utils/src/validation/mocks/template.ts diff --git a/ARCHITECHTURE.md b/ARCHITECHTURE.md index 2ab75688..55b9b49f 100644 --- a/ARCHITECHTURE.md +++ b/ARCHITECHTURE.md @@ -821,6 +821,7 @@ import { authOrApiKeyMiddleware } from '@api/middlewares/auth-or-api-key'; ``` **API Key Authentication:** + - Add `API_KEY` to `apps/api/src/config/env.ts` environment schema - Use `Authorization: Bearer ` header format (standard across popular APIs) - Use `apiKeyMiddleware` for API-key-only endpoints @@ -1100,7 +1101,7 @@ export const app = new Hono().route('/account', accountRouter); // Route availab ### Step 5: Run the api build command (at the root directory) ``` - sudo pnpm --filter @cio/api build + pnpm --filter @cio/api build ``` --- @@ -1219,12 +1220,7 @@ export async function createOrg(profileId: string, data: { name: string; siteNam // Business Logic: Check sitename availability const exists = await checkSiteNameExists(data.siteName); if (exists) { - throw new AppError( - `Site name '${data.siteName}' already exists`, - ErrorCodes.SITENAME_EXISTS, - 409, - 'siteName' - ); + throw new AppError(`Site name '${data.siteName}' already exists`, ErrorCodes.SITENAME_EXISTS, 409, 'siteName'); } // Business Logic: Create org and member in a transaction @@ -1256,12 +1252,7 @@ export async function createOrg(profileId: string, data: { name: string; siteNam } catch (error) { // Handle database constraint violations if (error.code === '23505') { - throw new AppError( - `Site name '${data.siteName}' already exists`, - ErrorCodes.SITENAME_EXISTS, - 409, - 'siteName' - ); + throw new AppError(`Site name '${data.siteName}' already exists`, ErrorCodes.SITENAME_EXISTS, 409, 'siteName'); } throw new AppError('Failed to create organization', ErrorCodes.ORG_CREATE_FAILED, 500); } @@ -1282,8 +1273,11 @@ import { authMiddleware } from '@api/middlewares/auth'; import { handleError } from '@api/utils/errors'; import { zValidator } from '@hono/zod-validator'; -export const organizationRouter = new Hono() - .post('/', authMiddleware, zValidator('json', ZCreateOrganization), async (c) => { +export const organizationRouter = new Hono().post( + '/', + authMiddleware, + zValidator('json', ZCreateOrganization), + async (c) => { try { const user = c.get('user'); const data = c.req.valid('json'); @@ -1294,7 +1288,8 @@ export const organizationRouter = new Hono() } catch (error) { return handleError(c, error, 'Failed to create organization'); } - }); + } +); ``` **Note**: Use `201 Created` status code for successful POST requests that create resources. @@ -1310,8 +1305,7 @@ export * from './organization'; // apps/api/src/app.ts import { organizationRouter } from '@api/routes/organization'; -export const app = new Hono() - .route('/organization', organizationRouter); // Route available at POST /organization +export const app = new Hono().route('/organization', organizationRouter); // Route available at POST /organization ``` --- @@ -1373,6 +1367,7 @@ export const orgApi = new OrgApi(); ``` **Key Points:** + - Use `this.execute()` from `BaseApiWithErrors` for automatic loading/error state - Field-specific errors are automatically mapped to `this.errors[field]` - Update stores in `onSuccess` callback @@ -1385,11 +1380,13 @@ export const orgApi = new OrgApi(); Sometimes a service function exists in a different context (e.g., `createOrganizationWithOwner` in onboarding service). **When to Reuse:** + - ✅ The function does exactly what you need - ✅ It's in the same domain (organization operations) - ✅ You can call it from your new route without modification **When to Create New:** + - ❌ The existing function has domain-specific logic you don't want (e.g., onboarding-specific) - ❌ You need different parameters or return values - ❌ The function is in a different domain and mixing concerns would be inappropriate @@ -1455,18 +1452,17 @@ When you have existing components using direct Supabase calls, follow this migra #### Step 1: Identify the Operation Analyze the existing Supabase call: + - **What database operation?** (CREATE, UPDATE, DELETE, SELECT) - **What tables are involved?** - **Are there related operations?** (e.g., create org + create member should be atomic) - **What validation is currently done?** (client-side only or also server-side?) **Example Analysis:** + ```typescript // Current code -const { data: org, error } = await supabase - .from('organization') - .insert({ name: orgName, siteName: siteName }) - .select(); +const { data: org, error } = await supabase.from('organization').insert({ name: orgName, siteName: siteName }).select(); const { data, error } = await supabase .from('organizationmember') @@ -1482,21 +1478,25 @@ const { data, error } = await supabase #### Step 2: Check for Existing Services Search for existing service functions that might handle this: + - Check `apps/api/src/services/` for similar operations - Look in related domains (e.g., onboarding might have org creation) - Check if the operation exists but in a different context **Decision:** + - **Reuse existing service** if it does what you need - **Create new service** if you need different logic or parameters #### Step 3: Create/Update API Route If the route doesn't exist: + - Follow the "Creating a New Route" workflow above - Use appropriate HTTP method: `POST` (create), `PUT` (update), `DELETE` (delete) If the route exists: + - Add new endpoint to existing router - Follow route naming conventions (see [`apps/api/ROUTE_NAMING_BEST_PRACTICES.md`](apps/api/ROUTE_NAMING_BEST_PRACTICES.md)) @@ -1542,6 +1542,7 @@ class OrgApi extends BaseApiWithErrors { ``` **Key Points:** + - Always use `this.execute()` from `BaseApiWithErrors` - Client-side validation before API call - Update stores in `onSuccess` callback @@ -1552,6 +1553,7 @@ class OrgApi extends BaseApiWithErrors { Replace Supabase calls with API class method: **Before (Direct Supabase):** + ```typescript + + + Email Verification Error - ClassroomIO + + + + +{#if type === 'legacy_method_blocked'} +
+
+ +
+

+ {$t('email_verification.security_notice.title')} +

+

+ {$t('email_verification.security_notice.message')} +

+
+
+
+{/if} +
+ + +
+
diff --git a/apps/dashboard/src/routes/+layout.svelte b/apps/dashboard/src/routes/+layout.svelte index 6b850cb6..bc129534 100644 --- a/apps/dashboard/src/routes/+layout.svelte +++ b/apps/dashboard/src/routes/+layout.svelte @@ -71,15 +71,17 @@ // The goal of this effect is to make sure that we do a redirect to /login if the session data is expired // This should be happening on the hooks.server.ts but we've made the home page public so we can show a loading spinner while the app is initializing OR an error message if the backend is down. $effect(() => { - if ($session.isPending || $session.isRefetching) { + if ($session.isPending || $session.isRefetching || !!$session.data) { console.log('session is pending or refetching'); return; } console.log('path', path); console.log('isPublicRoute', isPublicRoute(path)); + console.log('data', $session.data) + // No need to require login for public routes - if (isPublicRoute(path)) return; + if (isPublicRoute(path) && path !== '/') return; if (!$session.data && path !== '/login') { console.log('session data is not available, go to login'); diff --git a/apps/dashboard/src/routes/courses/[id]/+layout.svelte b/apps/dashboard/src/routes/courses/[id]/+layout.svelte index 6318e1da..d53d3be8 100644 --- a/apps/dashboard/src/routes/courses/[id]/+layout.svelte +++ b/apps/dashboard/src/routes/courses/[id]/+layout.svelte @@ -15,11 +15,11 @@ - + {@render children?.()} diff --git a/apps/dashboard/src/routes/verify-email-error/+page.svelte b/apps/dashboard/src/routes/verify-email-error/+page.svelte deleted file mode 100644 index 26a1e674..00000000 --- a/apps/dashboard/src/routes/verify-email-error/+page.svelte +++ /dev/null @@ -1,147 +0,0 @@ - - - - Email Verification Error - ClassroomIO - - - -
-
-
- - - - - -

- {errorTitle} -

- -

- {errorMessage} -

- - {#if errorType === 'legacy_method_blocked'} -
-
- - - -
-

- {$t('email_verification.security_notice.title')} -

-

- {$t('email_verification.security_notice.message')} -

-
-
-
- {/if} -
- -
- - goto('/')} - variant={VARIANTS.OUTLINED} - label={$t('email_verification.actions.back_to_dashboard')} - /> -
- - -
-

{$t('email_verification.security_footer')}

-
-
-
diff --git a/apps/dashboard/svelte.config.js b/apps/dashboard/svelte.config.js index d32ec6de..d3be7264 100644 --- a/apps/dashboard/svelte.config.js +++ b/apps/dashboard/svelte.config.js @@ -38,21 +38,24 @@ const config = { 'https://*.i.posthog.com', 'https://*.senja.io', 'https://www.youtube.com', - 'https://youtube.com' + 'https://youtube.com', + 'unsafe-hashes' ], 'style-src': [ 'self', 'unsafe-inline', 'https://cdn.plyr.io', 'https://unpkg.com/katex@0.12.0/dist/katex.min.css', - 'https://assets.cdn.clsrio.com/eqneditor_1.css' + 'https://assets.cdn.clsrio.com/eqneditor_1.css', + 'https://fonts.googleapis.com' ], 'style-src-elem': [ 'self', 'unsafe-inline', 'https://cdn.plyr.io', 'https://unpkg.com/katex@0.12.0/dist/katex.min.css', - 'https://assets.cdn.clsrio.com/eqneditor_1.css' + 'https://assets.cdn.clsrio.com/eqneditor_1.css', + 'https://fonts.googleapis.com' ], 'font-src': ['self', 'https://fonts.gstatic.com', 'https://cdn.plyr.io'], 'img-src': ['self', 'data:', 'https:'], @@ -67,6 +70,7 @@ const config = { 'https://*.i.posthog.com', 'https://umami.hz.oncws.com', 'https://*.r2.cloudflarestorage.com', + 'http://localhost:3002', 'http://localhost:54321', 'ws://localhost:54321', 'wss://*.classroomio.com', @@ -89,7 +93,8 @@ const config = { 'https://*.i.posthog.com', 'https://*.senja.io', 'https://www.youtube.com', - 'https://youtube.com' + 'https://youtube.com', + 'unsafe-hashes' ], 'style-src': [ 'self', @@ -119,6 +124,7 @@ const config = { 'https://*.i.posthog.com', 'https://umami.hz.oncws.com', 'https://*.r2.cloudflarestorage.com', + 'http://localhost:3002', 'http://localhost:54321', 'ws://localhost:54321', 'wss://*.classroomio.com', diff --git a/packages/db/src/queries/template/index.ts b/packages/db/src/queries/template/index.ts new file mode 100644 index 00000000..1b4a22a4 --- /dev/null +++ b/packages/db/src/queries/template/index.ts @@ -0,0 +1 @@ +export * from './template'; diff --git a/packages/db/src/queries/template/template.ts b/packages/db/src/queries/template/template.ts new file mode 100644 index 00000000..e7cdf186 --- /dev/null +++ b/packages/db/src/queries/template/template.ts @@ -0,0 +1,18 @@ +import * as schema from '@db/schema'; + +import { eq } from 'drizzle-orm'; +import { db } from '@db/drizzle'; + +export function getAllTemplates() { + return db.select().from(schema.exerciseTemplate); +} + +export async function getTemplateById(id: number) { + const result = await db.select().from(schema.exerciseTemplate).where(eq(schema.exerciseTemplate.id, id)).limit(1); + return result[0]; +} + +export async function getTemplateByTag(tag: string) { + const result = await db.select().from(schema.exerciseTemplate).where(eq(schema.exerciseTemplate.tag, tag)); + return result; +} diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 7852d8b0..ecb6b84f 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -898,8 +898,7 @@ export const courseNewsfeedComment = pgTable( columns: [table.courseNewsfeedId], foreignColumns: [courseNewsfeed.id], name: 'course_newsfeed_comment_course_newsfeed_id_fkey' - }), - unique('course_newsfeed_comment_id_key').on(table.id) + }) ] ); @@ -1312,3 +1311,45 @@ export const lessonVersions = pgView('lesson_versions', { }).as( sql`SELECT llh.old_content, llh.new_content, llh."timestamp", ll.locale, ll.lesson_id FROM lesson_language_history llh JOIN lesson_language ll ON ll.id = llh.lesson_language_id` ); + +export const exerciseTemplate = pgTable('exercise_template', { + id: bigint({ mode: 'number' }).primaryKey().generatedByDefaultAsIdentity({ + name: 'exercise_template_id_seq', + startWith: 1, + increment: 1, + minValue: 1, + cache: 1 + }), + createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' }).defaultNow().notNull(), + title: text(), + description: text(), + tag: text(), + questionnaire: jsonb().default({}).$type<{ + questions: [ + { + title: string; + name: string; + points: number; + order: number; + question_type: { + id: number; + label: string; + }; + options: [ + { + label: string; + is_correct: boolean; + }, + { + label: string; + is_correct: boolean; + }, + { + label: string; + is_correct: boolean; + } + ]; + } + ]; + }>() +}); diff --git a/packages/db/src/scripts/seed.ts b/packages/db/src/scripts/seed.ts index c5596239..579b6a57 100644 --- a/packages/db/src/scripts/seed.ts +++ b/packages/db/src/scripts/seed.ts @@ -1,805 +1,232 @@ import 'dotenv/config'; -import { - account, - course, - db, - exercise, - group, - groupmember, - lesson, - option, - organization, - organizationmember, - profile, - question, - questionType, - role, - submissionstatus, - user -} from '../drizzle'; -import { eq, or } from 'drizzle-orm'; - +import { seedAccount } from '@db/utils/seed/account'; +import { seedCourses } from '@db/utils/seed/course'; +import { seedExercise } from '@db/utils/seed/exercise'; +import { seedExerciseTemplates } from '@db/utils/seed/exerciseTemplate'; +import { seedGroup } from '@db/utils/seed/group'; +import { seedGroupmembers } from '@db/utils/seed/groupmember'; +import { seedLessons } from '@db/utils/seed/lesson'; +import { seedOrganization } from '@db/utils/seed/organization'; +import { seedOrganizationMember } from '@db/utils/seed/organizationmember'; +import { seedProfile } from '@db/utils/seed/profile'; +import { seedQuestionTypes } from '@db/utils/seed/questionType'; +import { seedQuestions } from '@db/utils/seed/question'; +import { seedRoles } from '@db/utils/seed/role'; +import { seedSubmissions } from '@db/utils/seed/submission'; +import { seedUsers } from '@db/utils/seed/users'; import usersData from '../../users.json'; +// Constants for IDs used across multiple seed functions +const TEST_ORG_ID = '1a1dcddd-1abc-4f72-b644-0bd18191a289'; +const ADMIN_USER_ID = '7ac00503-8519-43c8-a5ea-b79aeca900b1'; +const MVC_GROUP_ID = 'c6b022fd-fff3-4f09-8960-c9cb06819761'; +const REACT_GROUP_ID = '04a250f1-bcb9-4e0d-a3d4-a01096e7a105'; +const PANDAS_GROUP_ID = '0789ced2-b8f3-472c-97ff-bdde1e80dddf'; +const MVC_COURSE_ID = '98e6e798-f0bd-4f9d-a6f5-ce0816a4f97e'; +const REACT_COURSE_ID = '16e3bc8d-5d1b-4708-988e-93abae288ccf'; +const PANDAS_COURSE_ID = 'f0a85d18-aff4-412f-b8e6-3b34ef098dce'; +const STUDENT_USER_ID = '0c256e75-aa40-4f62-8d30-0217ca1c60d9'; + +// Parse command line arguments +function parseFlags(): Set { + const args = process.argv.slice(2); + const flags = new Set(); + + for (const arg of args) { + if (arg.startsWith('--')) { + flags.add(arg.slice(2)); + } else if (arg.startsWith('-')) { + flags.add(arg.slice(1)); + } + } + + return flags; +} + +// Show help message +function showHelp() { + console.log(` +🌱 Database Seeding CLI + +Usage: pnpm seed [flags] + +Flags: + --all, -a Run all seed functions (default if no flags provided) + --roles Seed roles + --submissions Seed submission statuses + --question-types Seed question types + --users Seed users + --profiles Seed profiles + --accounts Seed accounts + --organizations Seed organizations + --organization-members Seed organization members + --groups Seed groups + --group-members Seed group members + --courses Seed courses + --lessons Seed lessons + --exercises Seed exercises + --questions Seed questions + --templates Seed exercise templates + --help, -h Show this help message + +Examples: + pnpm seed --users --profiles + pnpm seed --all + pnpm seed --roles --submissions --question-types +`); +} + +// Seed function map +const seedFunctions = { + roles: async () => { + console.log('📝 Seeding roles...'); + await seedRoles(); + }, + submissions: async () => { + console.log('📝 Seeding submission statuses...'); + await seedSubmissions(); + }, + 'question-types': async () => { + console.log('📝 Seeding question types...'); + await seedQuestionTypes(); + }, + users: async () => { + console.log('📝 Seeding users...'); + // @ts-expect-error missing properties + await seedUsers({ usersData }); + }, + profiles: async () => { + console.log('📝 Seeding profiles...'); + // @ts-expect-error missing properties + await seedProfile({ usersData }); + }, + accounts: async () => { + console.log('📝 Seeding accounts...'); + // @ts-expect-error missing properties + await seedAccount({ usersData }); + }, + organizations: async () => { + console.log('📝 Seeding organizations...'); + await seedOrganization({ testOrgId: TEST_ORG_ID }); + }, + 'organization-members': async () => { + console.log('📝 Seeding organization members...'); + await seedOrganizationMember({ testOrgId: TEST_ORG_ID, adminUserId: ADMIN_USER_ID }); + }, + groups: async () => { + console.log('📝 Seeding groups...'); + await seedGroup({ + mvcGroupId: MVC_GROUP_ID, + reactGroupId: REACT_GROUP_ID, + pandasGroupId: PANDAS_GROUP_ID, + testOrgId: TEST_ORG_ID + }); + }, + 'group-members': async () => { + console.log('📝 Seeding group members...'); + await seedGroupmembers({ + mvcGroupId: MVC_GROUP_ID, + reactGroupId: REACT_GROUP_ID, + pandasGroupId: PANDAS_GROUP_ID, + adminUserId: ADMIN_USER_ID, + studentUserId: STUDENT_USER_ID + }); + }, + courses: async () => { + console.log('📝 Seeding courses...'); + await seedCourses({ mvcGroupId: MVC_GROUP_ID, reactGroupId: REACT_GROUP_ID, pandasGroupId: PANDAS_GROUP_ID }); + }, + lessons: async () => { + console.log('📝 Seeding lessons...'); + await seedLessons({ + mvcCourseId: MVC_COURSE_ID, + adminUserId: ADMIN_USER_ID, + reactCourseId: REACT_COURSE_ID, + pandasCourseId: PANDAS_COURSE_ID + }); + }, + exercises: async () => { + console.log('📝 Seeding exercises...'); + await seedExercise(); + }, + questions: async () => { + console.log('📝 Seeding questions...'); + // sequence fix for id error + // await db.execute( + // sql`SELECT setval(pg_get_serial_sequence('question', 'id'), COALESCE(MAX(id), 1), true) FROM question;` + // ); + await seedQuestions(); + }, + templates: async () => { + console.log('📝 Seeding exercise templates...'); + await seedExerciseTemplates(); + } +}; + async function seed() { try { + const flags = parseFlags(); + + // Show help if requested + if (flags.has('help') || flags.has('h')) { + showHelp(); + process.exit(0); + } + console.log('🌱 Starting seed...'); - // Seed roles - console.log('📝 Seeding roles...'); - const existingRoles = await db.select().from(role); - const existingRoleIds = existingRoles.map((r) => r.id); + // If --all flag or no flags provided, run all seeds + const runAll = flags.size === 0 || flags.has('all') || flags.has('a'); - const rolesToInsert = [ - { - id: 1, - type: 'ADMIN', - description: 'The main controller' - }, - { - id: 2, - type: 'TUTOR', - description: 'Can make changes to content, courses, but cant control passwords and cant add other tutors' - }, - { - id: 3, - type: 'STUDENT', - description: 'A student role, can interact with application but cant make changes' - } - ].filter((r) => !existingRoleIds.includes(r.id)); - - if (rolesToInsert.length > 0) { - await db.insert(role).values(rolesToInsert); - console.log(` ✓ Inserted ${rolesToInsert.length} role(s)`); + if (runAll) { + // Run all seeds in order + await seedFunctions.roles(); + await seedFunctions.submissions(); + await seedFunctions['question-types'](); + await seedFunctions.users(); + await seedFunctions.profiles(); + await seedFunctions.accounts(); + await seedFunctions.organizations(); + await seedFunctions['organization-members'](); + await seedFunctions.groups(); + await seedFunctions['group-members'](); + await seedFunctions.courses(); + await seedFunctions.lessons(); + await seedFunctions.exercises(); + await seedFunctions.questions(); + await seedFunctions.templates(); } else { - console.log(' ✓ Roles already exist, skipping'); - } + // Run only specified seed functions + // Order matters for dependencies, so we maintain the original order + const orderedSeeds = [ + 'roles', + 'submissions', + 'question-types', + 'users', + 'profiles', + 'accounts', + 'organizations', + 'organization-members', + 'groups', + 'group-members', + 'courses', + 'lessons', + 'exercises', + 'questions', + 'templates' + ]; - // Seed submission statuses - console.log('📝 Seeding submission statuses...'); - const existingStatuses = await db.select().from(submissionstatus); - const statusLabels = existingStatuses.map((s) => s.label); - - const statusesToInsert = [ - { - label: 'Submitted' - }, - { - label: 'In Progress' - }, - { - label: 'Graded' - } - ].filter((s) => !statusLabels.includes(s.label)); - - if (statusesToInsert.length > 0) { - await db.insert(submissionstatus).values(statusesToInsert); - console.log(` ✓ Inserted ${statusesToInsert.length} submission status(es)`); - } else { - console.log(' ✓ Submission statuses already exist, skipping'); - } - - // Seed question types - console.log('📝 Seeding question types...'); - const existingQuestionTypes = await db.select().from(questionType); - const questionTypeNames = existingQuestionTypes.map((q) => q.typename); - - const questionTypesToInsert = [ - { - label: 'Single answer', - typename: 'RADIO', - createdAt: '2021-08-07 18:49:46.246529+00', - updatedAt: '2021-08-15 00:57:08.12069+00' - }, - { - label: 'Multiple answers', - typename: 'CHECKBOX', - createdAt: '2021-08-07 18:49:46.246529+00', - updatedAt: '2021-08-15 00:57:27.935478+00' - }, - { - label: 'Paragraph', - typename: 'TEXTAREA', - createdAt: '2021-08-07 18:49:46.246529+00', - updatedAt: '2021-08-15 00:57:38.634665+00' - } - ].filter((q) => !questionTypeNames.includes(q.typename)); - - if (questionTypesToInsert.length > 0) { - await db.insert(questionType).values(questionTypesToInsert); - console.log(` ✓ Inserted ${questionTypesToInsert.length} question type(s)`); - } else { - console.log(' ✓ Question types already exist, skipping'); - } - - // Seed users (must be before profiles due to foreign key constraint) - console.log('📝 Seeding users...'); - const existingUsers = await db.select().from(user); - const existingUserIds = existingUsers.map((u) => u.id); - - const usersToInsert = usersData - .map((userData: any) => ({ - id: userData.id, - name: userData.profile_fullname, - email: userData.email, - emailVerified: !!userData.email_confirmed_at, - image: null, - role: null, - banned: false, - isAnonymous: false - })) - .filter((u: any) => !existingUserIds.includes(u.id)); - - if (usersToInsert.length > 0) { - await db.insert(user).values(usersToInsert); - console.log(` ✓ Inserted ${usersToInsert.length} user(s)`); - } else { - console.log(' ✓ Users already exist, skipping'); - } - - // Seed profiles - console.log('📝 Seeding profiles...'); - const existingProfiles = await db.select().from(profile); - const existingProfileIds = existingProfiles.map((p) => p.id); - - const profilesToInsert = usersData - .map((user: any) => ({ - id: user.id, - fullname: user.profile_fullname, - username: `${user.email.split('@')[0]}${Date.now()}`, - email: user.email, - avatarUrl: 'https://pgrest.classroomio.com/storage/v1/object/public/avatars/avatar.png', - canAddCourse: true, - isEmailVerified: true - })) - .filter((p: any) => !existingProfileIds.includes(p.id)); - - if (profilesToInsert.length > 0) { - await db.insert(profile).values(profilesToInsert); - console.log(` ✓ Inserted ${profilesToInsert.length} profile(s)`); - } else { - console.log(' ✓ Profiles already exist, skipping'); - } - - // Seed accounts (must be after users due to foreign key constraint) - console.log('📝 Seeding accounts...'); - const existingAccounts = await db.select().from(account); - const existingAccountKeys = existingAccounts.map((a) => `${a.userId}-${a.providerId}`); - - const accountsToInsert: any[] = []; - for (const userData of usersData) { - // Process identities to create accounts for all users in usersData - // (whether they were just inserted or already existed) - - // Process identities to create accounts - if (userData.identities && Array.isArray(userData.identities)) { - for (const identity of userData.identities) { - const accountKey = `${userData.id}-${identity.provider === 'email' ? 'credential' : identity.provider}`; - - if (existingAccountKeys.includes(accountKey)) { - continue; - } - - if (identity.provider === 'email') { - // Create credential account for email provider - accountsToInsert.push({ - userId: userData.id, - providerId: 'credential', - accountId: userData.id, - password: userData.encrypted_password - }); - } else { - // For social providers, use the sub from identity_data - accountsToInsert.push({ - userId: userData.id, - providerId: identity.provider, - accountId: identity.identity_data?.sub || userData.id - }); + for (const seedName of orderedSeeds) { + if (flags.has(seedName)) { + const seedFn = seedFunctions[seedName as keyof typeof seedFunctions]; + if (seedFn) { + await seedFn(); } } - } else { - // Fallback: if no identities array, create credential account from email - const accountKey = `${userData.id}-credential`; - if (!existingAccountKeys.includes(accountKey)) { - accountsToInsert.push({ - userId: userData.id, - providerId: 'credential', - accountId: userData.id, - password: userData.encrypted_password - }); - } } } - if (accountsToInsert.length > 0) { - await db.insert(account).values(accountsToInsert); - console.log(` ✓ Inserted ${accountsToInsert.length} account(s)`); - } else { - console.log(' ✓ Accounts already exist, skipping'); - } - - // Seed organizations - console.log('📝 Seeding organizations...'); - const existingOrgs = await db.select().from(organization); - const existingOrgIds = existingOrgs.map((o) => o.id); - - const testOrgId = '1a1dcddd-1abc-4f72-b644-0bd18191a289'; - const organizationsToInsert = [ - { - id: testOrgId, - name: 'Udemy Test', - siteName: 'udemy-test', - settings: {}, - landingpage: {}, - theme: '', - customization: { - apps: { poll: true, comments: true }, - course: { grading: true, newsfeed: true }, - dashboard: { exercise: true, community: true, bannerText: '', bannerImage: '' } - } - } - ].filter((o) => !existingOrgIds.includes(o.id)); - - if (organizationsToInsert.length > 0) { - await db.insert(organization).values(organizationsToInsert); - console.log(` ✓ Inserted ${organizationsToInsert.length} organization(s)`); - } else { - console.log(' ✓ Organizations already exist, skipping'); - } - - // Seed organization members - console.log('📝 Seeding organization members...'); - const existingOrgMembers = await db.select().from(organizationmember); - const existingOrgMemberKeys = existingOrgMembers.map((om) => `${om.organizationId}-${om.profileId}`); - - const adminUserId = '7ac00503-8519-43c8-a5ea-b79aeca900b1'; - const studentUserId = '0c256e75-aa40-4f62-8d30-0217ca1c60d9'; - - const orgMembersToInsert = [ - { - organizationId: testOrgId, - roleId: 1, // ADMIN - profileId: adminUserId, - verified: false - }, - { - organizationId: testOrgId, - roleId: 3, // STUDENT - profileId: studentUserId, - verified: false - } - ].filter((om) => !existingOrgMemberKeys.includes(`${om.organizationId}-${om.profileId}`)); - - if (orgMembersToInsert.length > 0) { - await db.insert(organizationmember).values(orgMembersToInsert); - console.log(` ✓ Inserted ${orgMembersToInsert.length} organization member(s)`); - } else { - console.log(' ✓ Organization members already exist, skipping'); - } - - // Seed groups - console.log('📝 Seeding groups...'); - const existingGroups = await db.select().from(group); - const existingGroupIds = existingGroups.map((g) => g.id); - - const mvcGroupId = 'c6b022fd-fff3-4f09-8960-c9cb06819761'; - const reactGroupId = '04a250f1-bcb9-4e0d-a3d4-a01096e7a105'; - const pandasGroupId = '0789ced2-b8f3-472c-97ff-bdde1e80dddf'; - - const groupsToInsert = [ - { - id: mvcGroupId, - name: 'Getting started with MVC', - description: - "Embark on a comprehensive journey into the world of Model-View-Controller (MVC) architecture with our course, 'Getting Started with MVC.' Designed for beginners and aspiring developers, this course provides a solid foundation for understanding the principles and practices behind MVC, a widely adopted design pattern in software development.", - organizationId: testOrgId - }, - { - id: reactGroupId, - name: 'Modern Web Development with React', - description: - "By the end of this course, you'll be equipped to build interactive and responsive web applications, making you a proficient React developer ready for the demands of today's web development landscape.", - organizationId: testOrgId - }, - { - id: pandasGroupId, - name: 'Data Science with Python and Pandas', - description: - 'Unlock the power of data with our "Data Science with Python and Pandas" course. Dive into Python programming fundamentals and then journey into the world of Pandas for efficient data manipulation and analysis.', - organizationId: testOrgId - } - ].filter((g) => !existingGroupIds.includes(g.id)); - - if (groupsToInsert.length > 0) { - await db.insert(group).values(groupsToInsert); - console.log(` ✓ Inserted ${groupsToInsert.length} group(s)`); - } else { - console.log(' ✓ Groups already exist, skipping'); - } - - // Seed group members - console.log('📝 Seeding group members...'); - const existingGroupMembers = await db.select().from(groupmember); - const existingGroupMemberKeys = existingGroupMembers.map((gm) => `${gm.groupId}-${gm.profileId || gm.email}`); - - const groupMembersToInsert = [ - { - groupId: mvcGroupId, - roleId: 2, // TUTOR - profileId: adminUserId, - email: 'admin@test.com' - }, - { - groupId: reactGroupId, - roleId: 2, // TUTOR - profileId: adminUserId, - email: 'admin@test.com' - }, - { - groupId: pandasGroupId, - roleId: 2, // TUTOR - profileId: adminUserId, - email: 'admin@test.com' - }, - { - groupId: pandasGroupId, - roleId: 3, // STUDENT - profileId: studentUserId - } - ].filter((gm) => !existingGroupMemberKeys.includes(`${gm.groupId}-${gm.profileId || gm.email}`)); - - if (groupMembersToInsert.length > 0) { - await db.insert(groupmember).values(groupMembersToInsert); - console.log(` ✓ Inserted ${groupMembersToInsert.length} group member(s)`); - } else { - console.log(' ✓ Group members already exist, skipping'); - } - - // Seed courses - console.log('📝 Seeding courses...'); - const existingCourses = await db.select().from(course); - const existingCourseIds = existingCourses.map((c) => c.id); - - const mvcCourseId = '98e6e798-f0bd-4f9d-a6f5-ce0816a4f97e'; - const reactCourseId = '16e3bc8d-5d1b-4708-988e-93abae288ccf'; - const pandasCourseId = 'f0a85d18-aff4-412f-b8e6-3b34ef098dce'; - - const coursesToInsert = [ - { - id: mvcCourseId, - title: 'Getting started with MVC', - description: - "Embark on a comprehensive journey into the world of Model-View-Controller (MVC) architecture with our course, 'Getting Started with MVC.' Designed for beginners and aspiring developers, this course provides a solid foundation for understanding the principles and practices behind MVC, a widely adopted design pattern in software development.", - overview: - '

"Getting Started with MVC. is designed for beginners and aspiring developers, this course provides a solid foundation for understanding the principles and practices behind MVC, a widely adopted design pattern in software development.

', - groupId: mvcGroupId, - isTemplate: true, - logo: '', - slug: 'getting-started-with-mvc', - metadata: { - goals: '', - grading: false, - description: '', - requirements: '', - lessonDownload: false, - allowNewStudent: true, - lessonTabsOrder: [ - { id: 1, name: 'Note' }, - { id: 2, name: 'Slide' }, - { id: 3, name: 'Video' } - ] - }, - cost: 0, - currency: 'NGN', - isPublished: true, - isCertificateDownloadable: false, - status: 'ACTIVE' - }, - { - id: reactCourseId, - title: 'Modern Web Development with React', - description: - "By the end of this course, you'll be equipped to build interactive and responsive web applications, making you a proficient React developer ready for the demands of today's web development landscape.", - overview: 'Welcome to this amazing course 🚀 ', - groupId: reactGroupId, - isTemplate: true, - logo: 'https://images.unsplash.com/photo-1565843708714-52ecf69ab81f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w1MTE1NTV8MHwxfHNlYXJjaHwxOHx8cmVhY3QlMjBkZXZ8ZW58MHx8fHwxNzA3Nzk5NDMyfDA&ixlib=rb-4.0.3&q=80&w=1080', - slug: 'modern-web-development', - metadata: { - goals: '', - grading: true, - description: '', - requirements: '', - lessonDownload: false, - allowNewStudent: true, - lessonTabsOrder: [ - { id: 1, name: 'Note' }, - { id: 2, name: 'Slide' }, - { id: 3, name: 'Video' } - ] - }, - cost: 0, - currency: 'NGN', - isPublished: true, - isCertificateDownloadable: false, - status: 'ACTIVE' - }, - { - id: pandasCourseId, - title: 'Data Science with Python and Pandas', - description: - 'Unlock the power of data with our "Data Science with Python and Pandas" course. Dive into Python programming fundamentals and then journey into the world of Pandas for efficient data manipulation and analysis. Learn essential data cleaning and preprocessing techniques before venturing into statistical analysis using Pandas. Cap off your exploration with data visualization using Matplotlib and Seaborn.', - overview: 'Welcome to this amazing course 🚀 ', - groupId: pandasGroupId, - isTemplate: true, - logo: 'https://images.unsplash.com/photo-1518186285589-2f7649de83e0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w1MTE1NTV8MHwxfHNlYXJjaHwxOHx8ZGF0YSUyMHNjaWVuY2V8ZW58MHx8fHwxNzA3Nzk5MzMwfDA&ixlib=rb-4.0.3&q=80&w=1080', - slug: 'data-science-with-python-and-pandas-1702919269375', - metadata: { - goals: '', - grading: true, - description: '', - requirements: '', - lessonDownload: true, - allowNewStudent: true, - lessonTabsOrder: [ - { id: 1, name: 'Note' }, - { id: 2, name: 'Slide' }, - { id: 3, name: 'Video' } - ] - }, - cost: 0, - currency: 'NGN', - isPublished: true, - isCertificateDownloadable: false, - status: 'ACTIVE' - } - ].filter((c) => !existingCourseIds.includes(c.id)); - - if (coursesToInsert.length > 0) { - await db.insert(course).values(coursesToInsert); - console.log(` ✓ Inserted ${coursesToInsert.length} course(s)`); - } else { - console.log(' ✓ Courses already exist, skipping'); - } - - // Seed lessons - console.log('📝 Seeding lessons...'); - const existingLessons = await db.select().from(lesson); - const existingLessonIds = existingLessons.map((l) => l.id); - - const lessonsToInsert = [ - // MVC Course lessons - { - id: '5c75f4f1-c222-44a9-a8c6-81773ea33872', - courseId: mvcCourseId, - title: 'Lesson 1: Introduction to MVC Architecture', - teacherId: adminUserId, - videos: [{ link: 'https://youtu.be/pXLWqkA87e4?si=rUHaBMnuFgAMjm2T', type: 'youtube', metadata: {} }], - isUnlocked: false - }, - { - id: 'a99e65b7-1394-4751-ad8d-a5fb670ccb9e', - courseId: mvcCourseId, - title: 'Anatomy of MVC Components', - teacherId: adminUserId, - videos: [{ link: 'https://youtu.be/4Qfk8MhtZJU?si=VZ7cF-pjvm_RmFMp', type: 'youtube', metadata: {} }], - isUnlocked: false - }, - { - id: '266b3daa-1eb2-401e-9510-1819952b44b7', - courseId: mvcCourseId, - title: 'Building Your First MVC Application', - teacherId: adminUserId, - videos: [{ link: 'https://www.youtube.com/watch?v=EMwu8F0dCXE', type: 'youtube', metadata: {} }], - isUnlocked: false - }, - // React Course lessons - { - id: '6f2d8142-0903-425c-8534-f5105b624752', - courseId: reactCourseId, - title: 'Introduction to React: Understanding the Basics', - teacherId: adminUserId, - videos: [{ link: 'https://www.youtube.com/watch?v=H-PkPKF2Tfk', type: 'youtube', metadata: {} }], - isUnlocked: false - }, - { - id: '0a39ab2f-9451-4a90-902c-3030bf965637', - courseId: reactCourseId, - title: 'Components and Props: Building Reusable UI Elements', - teacherId: adminUserId, - videos: [{ link: 'https://www.youtube.com/watch?v=H-PkPKF2Tfk', type: 'youtube', metadata: {} }], - isUnlocked: false - }, - { - id: '80b79665-733b-41bf-9853-34fd8ab50496', - courseId: reactCourseId, - title: 'State and Lifecycle: Managing Data in React Applications', - teacherId: adminUserId, - videos: [{ link: 'https://www.youtube.com/watch?v=DveeFlWzWzc', type: 'youtube', metadata: {} }], - isUnlocked: false - }, - // Pandas Course lessons - { - id: '5e5c8221-4c11-4c40-8664-11743bb79579', - courseId: pandasCourseId, - title: 'Python Essentials: An Introduction to Data Science', - teacherId: adminUserId, - videos: [{ link: 'https://www.youtube.com/watch?v=T5pRlIbr6gg&vl=en', type: 'youtube', metadata: {} }], - isUnlocked: true - }, - { - id: '829da386-8ccd-4c81-b2fb-b9891102c83c', - courseId: pandasCourseId, - title: 'Delving into Data Analysis with Pandas', - teacherId: adminUserId, - videos: [{ link: 'https://www.youtube.com/watch?v=T5pRlIbr6gg&vl=en', type: 'youtube', metadata: {} }], - isUnlocked: true - }, - { - id: '05f03084-3ff1-49e3-aa2a-7a13840cc4b1', - courseId: pandasCourseId, - title: 'Data Cleaning and Preprocessing Techniques', - teacherId: adminUserId, - videos: [{ link: 'https://www.youtube.com/watch?v=LI7s_lyooO8', type: 'youtube', metadata: {} }], - isUnlocked: true - } - ].filter((l) => !existingLessonIds.includes(l.id)); - - if (lessonsToInsert.length > 0) { - await db.insert(lesson).values(lessonsToInsert); - console.log(` ✓ Inserted ${lessonsToInsert.length} lesson(s)`); - } else { - console.log(' ✓ Lessons already exist, skipping'); - } - - // Seed exercises - console.log('📝 Seeding exercises...'); - const existingExercises = await db.select().from(exercise); - const existingExerciseIds = existingExercises.map((e) => e.id); - - const exercisesToInsert = [ - { - id: 'e2ea9fb8-6448-4f6c-a1d5-02c2b12cf862', - title: 'MVC essentials quiz', - description: "

This exercise tests your knowledge of what we've covered in the first lesson.

", - lessonId: '5c75f4f1-c222-44a9-a8c6-81773ea33872' - }, - { - id: '8b6084b0-a936-411d-9408-a1289aeed068', - title: 'MVC Components quiz', - lessonId: 'a99e65b7-1394-4751-ad8d-a5fb670ccb9e' - }, - { - id: '43f43374-7df0-4e02-b839-56403f71473a', - title: 'NodeJS MVC Quiz', - lessonId: '266b3daa-1eb2-401e-9510-1819952b44b7' - }, - { - id: 'e78bfd24-8ac3-43e9-a117-a2f9d00f74b1', - title: 'Introduction To React: Quiz', - lessonId: '6f2d8142-0903-425c-8534-f5105b624752' - }, - { - id: '43ead5d7-af88-47cf-8f86-99124f5eb0cd', - title: 'Components And Props Quiz', - lessonId: '0a39ab2f-9451-4a90-902c-3030bf965637' - }, - { - id: 'b0770deb-a8a0-4efe-9d28-8bf1298c04b2', - title: 'React State And Lifecycle Quiz', - lessonId: '80b79665-733b-41bf-9853-34fd8ab50496' - }, - { - id: '6f1063ed-3791-43fe-81e9-ad3b007834fa', - title: 'Introduction to Python - Quiz', - lessonId: '5e5c8221-4c11-4c40-8664-11743bb79579', - dueBy: '2023-12-21T18:21:00.000Z' - }, - { - id: 'bd6e81c7-3d28-4037-acf0-a3028c583771', - title: 'Delving Into Data Analysis With Pandas - Quiz', - lessonId: '829da386-8ccd-4c81-b2fb-b9891102c83c' - }, - { - id: 'd8cd1cf7-1951-46b3-ad1c-41e415185bc1', - title: 'Data Cleaning And Preprocessing Techniques - Quiz', - lessonId: '05f03084-3ff1-49e3-aa2a-7a13840cc4b1' - } - ].filter((e) => !existingExerciseIds.includes(e.id)); - - if (exercisesToInsert.length > 0) { - await db.insert(exercise).values(exercisesToInsert); - console.log(` ✓ Inserted ${exercisesToInsert.length} exercise(s)`); - } else { - console.log(' ✓ Exercises already exist, skipping'); - } - - // Seed questions - console.log('📝 Seeding questions...'); - - const existingQuestions = await db - .select() - .from(question) - .where( - or( - eq(question.exerciseId, 'e2ea9fb8-6448-4f6c-a1d5-02c2b12cf862'), - eq(question.exerciseId, 'e78bfd24-8ac3-43e9-a117-a2f9d00f74b1'), - eq(question.exerciseId, 'bd6e81c7-3d28-4037-acf0-a3028c583771'), - eq(question.exerciseId, 'd8cd1cf7-1951-46b3-ad1c-41e415185bc1') - ) - ); - const existingQuestionExerciseIds = existingQuestions.map((q) => q.exerciseId); - - const questionsToInsert = [ - { - questionTypeId: 2, // CHECKBOX - title: 'What does MVC stand for', - exerciseId: 'e2ea9fb8-6448-4f6c-a1d5-02c2b12cf862', - points: 0, - order: 0 - }, - { - questionTypeId: 2, // CHECKBOX - title: - "What is the purpose of the virtual DOM in React, and how does it contribute to the framework's performance optimization?", - exerciseId: 'e78bfd24-8ac3-43e9-a117-a2f9d00f74b1', - points: 10, - order: 0 - }, - { - questionTypeId: 2, // CHECKBOX - title: 'When working with Pandas, what function is used to drop missing values from a DataFrame?', - exerciseId: 'bd6e81c7-3d28-4037-acf0-a3028c583771', - points: 20, - order: 0 - }, - { - questionTypeId: 2, // CHECKBOX - title: 'What is the primary purpose of data cleaning in the context of data preprocessing?', - exerciseId: 'd8cd1cf7-1951-46b3-ad1c-41e415185bc1', - points: 0, - order: 0 - } - ].filter((q) => !existingQuestionExerciseIds.includes(q.exerciseId)); - - let insertedQuestions: any[] = []; - if (questionsToInsert.length > 0) { - const result = await db.insert(question).values(questionsToInsert).returning(); - insertedQuestions = result; - console.log(` ✓ Inserted ${questionsToInsert.length} question(s)`); - } else { - console.log(' ✓ Questions already exist, skipping'); - insertedQuestions = existingQuestions; - } - - // Seed options for questions - console.log('📝 Seeding question options...'); - const questionIds = insertedQuestions.map((q) => q.id); - - // Check if options already exist for these questions - let existingOptions: any[] = []; - if (questionIds.length > 0) { - const conditions = questionIds.map((id) => eq(option.questionId, id)); - existingOptions = await db - .select() - .from(option) - .where(or(...conditions)); - } - const existingOptionQuestionIds = new Set(existingOptions.map((o) => o.questionId)); - - // Get question IDs from inserted or existing questions - const mvcQuestion = insertedQuestions.find((q) => q.exerciseId === 'e2ea9fb8-6448-4f6c-a1d5-02c2b12cf862'); - const reactQuestion = insertedQuestions.find((q) => q.exerciseId === 'e78bfd24-8ac3-43e9-a117-a2f9d00f74b1'); - const pandasQuestion1 = insertedQuestions.find((q) => q.exerciseId === 'bd6e81c7-3d28-4037-acf0-a3028c583771'); - const pandasQuestion2 = insertedQuestions.find((q) => q.exerciseId === 'd8cd1cf7-1951-46b3-ad1c-41e415185bc1'); - - const optionsToInsert = [ - // MVC question options - ...(mvcQuestion - ? [ - { - label: 'Modulus, View, Calculus', - isCorrect: false, - questionId: mvcQuestion.id - }, - { - label: 'Mode, Vue, Controller', - isCorrect: false, - questionId: mvcQuestion.id - }, - { - label: 'Model, View, Controller', - isCorrect: true, - questionId: mvcQuestion.id - } - ] - : []), - // React question options - ...(reactQuestion - ? [ - { - label: 'It stores React components in a centralized virtual memory.', - isCorrect: false, - questionId: reactQuestion.id - }, - { - label: 'It facilitates direct communication between the browser and the server.', - isCorrect: false, - questionId: reactQuestion.id - }, - { - label: - 'It is an in-memory representation of the actual DOM, allowing React to efficiently update and render UI changes.', - isCorrect: true, - questionId: reactQuestion.id - }, - { - label: 'It handles encryption and decryption for secure data transmission.', - isCorrect: false, - questionId: reactQuestion.id - } - ] - : []), - // Pandas question 1 options - ...(pandasQuestion1 - ? [ - { - label: 'drop_null()', - isCorrect: false, - questionId: pandasQuestion1.id - }, - { - label: 'remove_missing()', - isCorrect: false, - questionId: pandasQuestion1.id - }, - { - label: 'dropna()', - isCorrect: true, - questionId: pandasQuestion1.id - }, - { - label: 'clean_data()', - isCorrect: false, - questionId: pandasQuestion1.id - } - ] - : []), - // Pandas question 2 options - ...(pandasQuestion2 - ? [ - { - label: 'To increase the size of the dataset', - isCorrect: false, - questionId: pandasQuestion2.id - }, - { - label: 'To introduce noise and variability', - isCorrect: false, - questionId: pandasQuestion2.id - }, - { - label: 'To remove errors and inconsistencies', - isCorrect: true, - questionId: pandasQuestion2.id - }, - { - label: 'To speed up the data processing time', - isCorrect: false, - questionId: pandasQuestion2.id - } - ] - : []) - ].filter((o) => !existingOptionQuestionIds.has(o.questionId)); - - if (optionsToInsert.length > 0) { - await db.insert(option).values(optionsToInsert); - console.log(` ✓ Inserted ${optionsToInsert.length} option(s)`); - } else { - console.log(' ✓ Options already exist, skipping'); - } - console.log('✅ Seed completed successfully!'); process.exit(0); } catch (error) { diff --git a/packages/db/src/types.ts b/packages/db/src/types.ts index 0bea90e1..ce87b93d 100644 --- a/packages/db/src/types.ts +++ b/packages/db/src/types.ts @@ -130,6 +130,9 @@ export type TNewWaitinglist = typeof schema.waitinglist.$inferInsert; export type TOrganization = typeof schema.organization.$inferSelect; export type TNewOrganization = typeof schema.organization.$inferInsert; +export type TExerciseTemplate = typeof schema.exerciseTemplate.$inferSelect; +export type TNewExerciseTemplate = typeof schema.exerciseTemplate.$inferInsert; + export type TCourseType = (typeof schema.courseType.enumValues)[number]; export type TCourseVersion = (typeof schema.courseVersion.enumValues)[number]; @@ -137,5 +140,3 @@ export type TCourseVersion = (typeof schema.courseVersion.enumValues)[number]; export type TLocale = (typeof schema.locale.enumValues)[number]; export type TPlan = (typeof schema.plan.enumValues)[number]; - -export type TDashStats = typeof schema.dashOrgStats.$inferSelect; diff --git a/packages/db/src/utils/seed/account.ts b/packages/db/src/utils/seed/account.ts new file mode 100644 index 00000000..ffbe1e74 --- /dev/null +++ b/packages/db/src/utils/seed/account.ts @@ -0,0 +1,69 @@ +import { account, db } from '@db/drizzle'; +import { TAccount } from '@db/types'; + +interface TUserSeedData extends TAccount { + encrypted_password?: string; + identities?: Array<{ + provider: string; + identity_data?: { + sub?: string; + }; + }>; +} + +export async function seedAccount({ usersData }: { usersData: TUserSeedData[] }) { + const existingAccounts = await db.select().from(account); + const existingAccountKeys = existingAccounts.map((a) => `${a.userId}-${a.providerId}`); + + const accountsToInsert = []; + for (const userData of usersData) { + // Process identities to create accounts for all users in usersData + // (whether they were just inserted or already existed) + + // Process identities to create accounts + if (userData.identities && Array.isArray(userData.identities)) { + for (const identity of userData.identities) { + const accountKey = `${userData.id}-${identity.provider === 'email' ? 'credential' : identity.provider}`; + + if (existingAccountKeys.includes(accountKey)) { + continue; + } + + if (identity.provider === 'email') { + // Create credential account for email provider + accountsToInsert.push({ + userId: userData.id, + providerId: 'credential', + accountId: userData.id, + password: userData.encrypted_password + }); + } else { + // For social providers, use the sub from identity_data + accountsToInsert.push({ + userId: userData.id, + providerId: identity.provider, + accountId: identity.identity_data?.sub || userData.id + }); + } + } + } else { + // Fallback: if no identities array, create credential account from email + const accountKey = `${userData.id}-credential`; + if (!existingAccountKeys.includes(accountKey)) { + accountsToInsert.push({ + userId: userData.id, + providerId: 'credential', + accountId: userData.id, + password: userData.encrypted_password + }); + } + } + } + + if (accountsToInsert.length > 0) { + await db.insert(account).values(accountsToInsert); + console.log(` ✓ Inserted ${accountsToInsert.length} account(s)`); + } else { + console.log(' ✓ Accounts already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/course.ts b/packages/db/src/utils/seed/course.ts new file mode 100644 index 00000000..c833f6ad --- /dev/null +++ b/packages/db/src/utils/seed/course.ts @@ -0,0 +1,114 @@ +import { course, db } from '@db/drizzle'; + +interface SeedCourse { + mvcGroupId: string; + reactGroupId: string; + pandasGroupId: string; +} + +export async function seedCourses({ mvcGroupId, reactGroupId, pandasGroupId }: SeedCourse) { + const existingCourses = await db.select().from(course); + const existingCourseIds = existingCourses.map((c) => c.id); + + const mvcCourseId = '98e6e798-f0bd-4f9d-a6f5-ce0816a4f97e'; + const reactCourseId = '16e3bc8d-5d1b-4708-988e-93abae288ccf'; + const pandasCourseId = 'f0a85d18-aff4-412f-b8e6-3b34ef098dce'; + + const coursesToInsert = [ + { + id: mvcCourseId, + title: 'Getting started with MVC', + description: + "Embark on a comprehensive journey into the world of Model-View-Controller (MVC) architecture with our course, 'Getting Started with MVC.' Designed for beginners and aspiring developers, this course provides a solid foundation for understanding the principles and practices behind MVC, a widely adopted design pattern in software development.", + overview: + '

"Getting Started with MVC. is designed for beginners and aspiring developers, this course provides a solid foundation for understanding the principles and practices behind MVC, a widely adopted design pattern in software development.

', + groupId: mvcGroupId, + isTemplate: true, + logo: '', + slug: 'getting-started-with-mvc', + metadata: { + goals: '', + grading: false, + description: '', + requirements: '', + lessonDownload: false, + allowNewStudent: true, + lessonTabsOrder: [ + { id: 1, name: 'Note' }, + { id: 2, name: 'Slide' }, + { id: 3, name: 'Video' } + ] + }, + cost: 0, + currency: 'NGN', + isPublished: true, + isCertificateDownloadable: false, + status: 'ACTIVE' + }, + { + id: reactCourseId, + title: 'Modern Web Development with React', + description: + "By the end of this course, you'll be equipped to build interactive and responsive web applications, making you a proficient React developer ready for the demands of today's web development landscape.", + overview: 'Welcome to this amazing course 🚀 ', + groupId: reactGroupId, + isTemplate: true, + logo: 'https://images.unsplash.com/photo-1565843708714-52ecf69ab81f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w1MTE1NTV8MHwxfHNlYXJjaHwxOHx8cmVhY3QlMjBkZXZ8ZW58MHx8fHwxNzA3Nzk5NDMyfDA&ixlib=rb-4.0.3&q=80&w=1080', + slug: 'modern-web-development', + metadata: { + goals: '', + grading: true, + description: '', + requirements: '', + lessonDownload: false, + allowNewStudent: true, + lessonTabsOrder: [ + { id: 1, name: 'Note' }, + { id: 2, name: 'Slide' }, + { id: 3, name: 'Video' } + ] + }, + cost: 0, + currency: 'NGN', + isPublished: true, + isCertificateDownloadable: false, + status: 'ACTIVE' + }, + { + id: pandasCourseId, + title: 'Data Science with Python and Pandas', + description: + 'Unlock the power of data with our "Data Science with Python and Pandas" course. Dive into Python programming fundamentals and then journey into the world of Pandas for efficient data manipulation and analysis. Learn essential data cleaning and preprocessing techniques before venturing into statistical analysis using Pandas. Cap off your exploration with data visualization using Matplotlib and Seaborn.', + overview: 'Welcome to this amazing course 🚀 ', + groupId: pandasGroupId, + isTemplate: true, + logo: 'https://images.unsplash.com/photo-1518186285589-2f7649de83e0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w1MTE1NTV8MHwxfHNlYXJjaHwxOHx8ZGF0YSUyMHNjaWVuY2V8ZW58MHx8fHwxNzA3Nzk5MzMwfDA&ixlib=rb-4.0.3&q=80&w=1080', + slug: 'data-science-with-python-and-pandas-1702919269375', + metadata: { + goals: '', + grading: true, + description: '', + requirements: '', + lessonDownload: true, + allowNewStudent: true, + lessonTabsOrder: [ + { id: 1, name: 'Note' }, + { id: 2, name: 'Slide' }, + { id: 3, name: 'Video' } + ] + }, + cost: 0, + currency: 'NGN', + isPublished: true, + isCertificateDownloadable: false, + status: 'ACTIVE' + } + ].filter((c) => !existingCourseIds.includes(c.id)); + + if (coursesToInsert.length > 0) { + await db.insert(course).values(coursesToInsert); + console.log(` ✓ Inserted ${coursesToInsert.length} course(s)`); + } else { + console.log(' ✓ Courses already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/exercise.ts b/packages/db/src/utils/seed/exercise.ts new file mode 100644 index 00000000..139a8086 --- /dev/null +++ b/packages/db/src/utils/seed/exercise.ts @@ -0,0 +1,63 @@ +import { db, exercise } from '@db/drizzle'; + +export async function seedExercise() { + const existingExercises = await db.select().from(exercise); + const existingExerciseIds = existingExercises.map((e) => e.id); + + const exercisesToInsert = [ + { + id: 'e2ea9fb8-6448-4f6c-a1d5-02c2b12cf862', + title: 'MVC essentials quiz', + description: "

This exercise tests your knowledge of what we've covered in the first lesson.

", + lessonId: '5c75f4f1-c222-44a9-a8c6-81773ea33872' + }, + { + id: '8b6084b0-a936-411d-9408-a1289aeed068', + title: 'MVC Components quiz', + lessonId: 'a99e65b7-1394-4751-ad8d-a5fb670ccb9e' + }, + { + id: '43f43374-7df0-4e02-b839-56403f71473a', + title: 'NodeJS MVC Quiz', + lessonId: '266b3daa-1eb2-401e-9510-1819952b44b7' + }, + { + id: 'e78bfd24-8ac3-43e9-a117-a2f9d00f74b1', + title: 'Introduction To React: Quiz', + lessonId: '6f2d8142-0903-425c-8534-f5105b624752' + }, + { + id: '43ead5d7-af88-47cf-8f86-99124f5eb0cd', + title: 'Components And Props Quiz', + lessonId: '0a39ab2f-9451-4a90-902c-3030bf965637' + }, + { + id: 'b0770deb-a8a0-4efe-9d28-8bf1298c04b2', + title: 'React State And Lifecycle Quiz', + lessonId: '80b79665-733b-41bf-9853-34fd8ab50496' + }, + { + id: '6f1063ed-3791-43fe-81e9-ad3b007834fa', + title: 'Introduction to Python - Quiz', + lessonId: '5e5c8221-4c11-4c40-8664-11743bb79579', + dueBy: '2023-12-21T18:21:00.000Z' + }, + { + id: 'bd6e81c7-3d28-4037-acf0-a3028c583771', + title: 'Delving Into Data Analysis With Pandas - Quiz', + lessonId: '829da386-8ccd-4c81-b2fb-b9891102c83c' + }, + { + id: 'd8cd1cf7-1951-46b3-ad1c-41e415185bc1', + title: 'Data Cleaning And Preprocessing Techniques - Quiz', + lessonId: '05f03084-3ff1-49e3-aa2a-7a13840cc4b1' + } + ].filter((e) => !existingExerciseIds.includes(e.id)); + + if (exercisesToInsert.length > 0) { + await db.insert(exercise).values(exercisesToInsert); + console.log(` ✓ Inserted ${exercisesToInsert.length} exercise(s)`); + } else { + console.log(' ✓ Exercises already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/exerciseTemplate.ts b/packages/db/src/utils/seed/exerciseTemplate.ts new file mode 100644 index 00000000..05fed76d --- /dev/null +++ b/packages/db/src/utils/seed/exerciseTemplate.ts @@ -0,0 +1,39 @@ +import { db, exerciseTemplate } from '@db/drizzle'; + +import { TExerciseTemplate } from '@db/types'; + +const EXERCISE_TEMPLATES_CDN_URL = 'https://assets.cdn.clsrio.com/exerciseTemplates.json'; + +async function fetchExerciseTemplatesFromCDN(): Promise { + const response = await fetch(EXERCISE_TEMPLATES_CDN_URL); + if (!response.ok) { + throw new Error(`Failed to fetch exercise templates from CDN: ${response.status} ${response.statusText}`); + } + return response.json(); +} + +export async function seedExerciseTemplates() { + const existingExerciseTemplates = await db.select().from(exerciseTemplate); + const existingExerciseTemplateTitles = existingExerciseTemplates.map((t) => t.title); + + console.time('fetchExerciseTemplatesFromCDN'); + const templatesData = await fetchExerciseTemplatesFromCDN(); + console.timeEnd('fetchExerciseTemplatesFromCDN'); + + const templatesToInsert = templatesData + .filter((t) => !existingExerciseTemplateTitles.includes(t.title)) + .map((t) => ({ + title: t.title, + description: t.description, + questionnaire: t.questionnaire, + tag: t.tag + })); + + if (templatesToInsert.length > 0) { + await db.insert(exerciseTemplate).values(templatesToInsert); + + console.log(` ✓ Inserted ${templatesToInsert.length} template(s)`); + } else { + console.log(' ✓ Templates already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/group.ts b/packages/db/src/utils/seed/group.ts new file mode 100644 index 00000000..18d2b980 --- /dev/null +++ b/packages/db/src/utils/seed/group.ts @@ -0,0 +1,44 @@ +import { db, group } from '@db/drizzle'; + +interface SeedGroup { + mvcGroupId: string; + reactGroupId: string; + pandasGroupId: string; + testOrgId: string; +} + +export async function seedGroup({ mvcGroupId, reactGroupId, pandasGroupId, testOrgId }: SeedGroup) { + const existingGroups = await db.select().from(group); + const existingGroupIds = existingGroups.map((g) => g.id); + + const groupsToInsert = [ + { + id: mvcGroupId, + name: 'Getting started with MVC', + description: + "Embark on a comprehensive journey into the world of Model-View-Controller (MVC) architecture with our course, 'Getting Started with MVC.' Designed for beginners and aspiring developers, this course provides a solid foundation for understanding the principles and practices behind MVC, a widely adopted design pattern in software development.", + organizationId: testOrgId + }, + { + id: reactGroupId, + name: 'Modern Web Development with React', + description: + "By the end of this course, you'll be equipped to build interactive and responsive web applications, making you a proficient React developer ready for the demands of today's web development landscape.", + organizationId: testOrgId + }, + { + id: pandasGroupId, + name: 'Data Science with Python and Pandas', + description: + 'Unlock the power of data with our "Data Science with Python and Pandas" course. Dive into Python programming fundamentals and then journey into the world of Pandas for efficient data manipulation and analysis.', + organizationId: testOrgId + } + ].filter((g) => !existingGroupIds.includes(g.id)); + + if (groupsToInsert.length > 0) { + await db.insert(group).values(groupsToInsert); + console.log(` ✓ Inserted ${groupsToInsert.length} group(s)`); + } else { + console.log(' ✓ Groups already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/groupmember.ts b/packages/db/src/utils/seed/groupmember.ts new file mode 100644 index 00000000..7d352c41 --- /dev/null +++ b/packages/db/src/utils/seed/groupmember.ts @@ -0,0 +1,53 @@ +import { db, groupmember } from '@db/drizzle'; + +interface SeedGroupmember { + mvcGroupId: string; + reactGroupId: string; + pandasGroupId: string; + adminUserId: string; + studentUserId: string; +} + +export async function seedGroupmembers({ + mvcGroupId, + reactGroupId, + pandasGroupId, + adminUserId, + studentUserId +}: SeedGroupmember) { + const existingGroupMembers = await db.select().from(groupmember); + const existingGroupMemberKeys = existingGroupMembers.map((gm) => `${gm.groupId}-${gm.profileId || gm.email}`); + + const groupMembersToInsert = [ + { + groupId: mvcGroupId, + roleId: 2, // TUTOR + profileId: adminUserId, + email: 'admin@test.com' + }, + { + groupId: reactGroupId, + roleId: 2, // TUTOR + profileId: adminUserId, + email: 'admin@test.com' + }, + { + groupId: pandasGroupId, + roleId: 2, // TUTOR + profileId: adminUserId, + email: 'admin@test.com' + }, + { + groupId: pandasGroupId, + roleId: 3, // STUDENT + profileId: studentUserId + } + ].filter((gm) => !existingGroupMemberKeys.includes(`${gm.groupId}-${gm.profileId || gm.email}`)); + + if (groupMembersToInsert.length > 0) { + await db.insert(groupmember).values(groupMembersToInsert); + console.log(` ✓ Inserted ${groupMembersToInsert.length} group member(s)`); + } else { + console.log(' ✓ Group members already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/lesson.ts b/packages/db/src/utils/seed/lesson.ts new file mode 100644 index 00000000..05ac4386 --- /dev/null +++ b/packages/db/src/utils/seed/lesson.ts @@ -0,0 +1,98 @@ +import { db, lesson } from '@db/drizzle'; + +interface LessonTemplate { + mvcCourseId: string; + reactCourseId: string; + pandasCourseId: string; + adminUserId: string; +} + +export async function seedLessons({ mvcCourseId, adminUserId, reactCourseId, pandasCourseId }: LessonTemplate) { + const existingLessons = await db.select().from(lesson); + const existingLessonIds = existingLessons.map((l) => l.id); + + const lessonsToInsert = [ + // MVC Course lessons + { + id: '5c75f4f1-c222-44a9-a8c6-81773ea33872', + courseId: mvcCourseId, + title: 'Lesson 1: Introduction to MVC Architecture', + teacherId: adminUserId, + videos: [{ link: 'https://youtu.be/pXLWqkA87e4?si=rUHaBMnuFgAMjm2T', type: 'youtube', metadata: {} }], + isUnlocked: false + }, + { + id: 'a99e65b7-1394-4751-ad8d-a5fb670ccb9e', + courseId: mvcCourseId, + title: 'Anatomy of MVC Components', + teacherId: adminUserId, + videos: [{ link: 'https://youtu.be/4Qfk8MhtZJU?si=VZ7cF-pjvm_RmFMp', type: 'youtube', metadata: {} }], + isUnlocked: false + }, + { + id: '266b3daa-1eb2-401e-9510-1819952b44b7', + courseId: mvcCourseId, + title: 'Building Your First MVC Application', + teacherId: adminUserId, + videos: [{ link: 'https://www.youtube.com/watch?v=EMwu8F0dCXE', type: 'youtube', metadata: {} }], + isUnlocked: false + }, + // React Course lessons + { + id: '6f2d8142-0903-425c-8534-f5105b624752', + courseId: reactCourseId, + title: 'Introduction to React: Understanding the Basics', + teacherId: adminUserId, + videos: [{ link: 'https://www.youtube.com/watch?v=H-PkPKF2Tfk', type: 'youtube', metadata: {} }], + isUnlocked: false + }, + { + id: '0a39ab2f-9451-4a90-902c-3030bf965637', + courseId: reactCourseId, + title: 'Components and Props: Building Reusable UI Elements', + teacherId: adminUserId, + videos: [{ link: 'https://www.youtube.com/watch?v=H-PkPKF2Tfk', type: 'youtube', metadata: {} }], + isUnlocked: false + }, + { + id: '80b79665-733b-41bf-9853-34fd8ab50496', + courseId: reactCourseId, + title: 'State and Lifecycle: Managing Data in React Applications', + teacherId: adminUserId, + videos: [{ link: 'https://www.youtube.com/watch?v=DveeFlWzWzc', type: 'youtube', metadata: {} }], + isUnlocked: false + }, + // Pandas Course lessons + { + id: '5e5c8221-4c11-4c40-8664-11743bb79579', + courseId: pandasCourseId, + title: 'Python Essentials: An Introduction to Data Science', + teacherId: adminUserId, + videos: [{ link: 'https://www.youtube.com/watch?v=T5pRlIbr6gg&vl=en', type: 'youtube', metadata: {} }], + isUnlocked: true + }, + { + id: '829da386-8ccd-4c81-b2fb-b9891102c83c', + courseId: pandasCourseId, + title: 'Delving into Data Analysis with Pandas', + teacherId: adminUserId, + videos: [{ link: 'https://www.youtube.com/watch?v=T5pRlIbr6gg&vl=en', type: 'youtube', metadata: {} }], + isUnlocked: true + }, + { + id: '05f03084-3ff1-49e3-aa2a-7a13840cc4b1', + courseId: pandasCourseId, + title: 'Data Cleaning and Preprocessing Techniques', + teacherId: adminUserId, + videos: [{ link: 'https://www.youtube.com/watch?v=LI7s_lyooO8', type: 'youtube', metadata: {} }], + isUnlocked: true + } + ].filter((l) => !existingLessonIds.includes(l.id)); + + if (lessonsToInsert.length > 0) { + await db.insert(lesson).values(lessonsToInsert); + console.log(` ✓ Inserted ${lessonsToInsert.length} lesson(s)`); + } else { + console.log(' ✓ Lessons already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/option.ts b/packages/db/src/utils/seed/option.ts new file mode 100644 index 00000000..7c9406d4 --- /dev/null +++ b/packages/db/src/utils/seed/option.ts @@ -0,0 +1,136 @@ +import { db, eq, option, or, sql } from '@db/drizzle'; +import { TOption, TQuestion } from '@db/types'; + +export async function seedOptions(insertedQuestions: TQuestion[]) { + const maxIdResult = await db.select({ maxId: sql`COALESCE(MAX(${option.id}), 0)` }).from(option); + let nextId = (maxIdResult[0]?.maxId || 0) + 1; + + const questionIds = insertedQuestions.map((q) => q.id); + + let existingOptions: TOption[] = []; + if (questionIds.length > 0) { + const conditions = questionIds.map((id) => eq(option.questionId, id)); + existingOptions = await db + .select() + .from(option) + .where(or(...conditions)); + } + const existingOptionLabels = new Set(existingOptions.map((o) => o.label)); + + // Get question IDs from inserted or existing questions + const mvcQuestion = insertedQuestions.find((q) => q.exerciseId === 'e2ea9fb8-6448-4f6c-a1d5-02c2b12cf862'); + const reactQuestion = insertedQuestions.find((q) => q.exerciseId === 'e78bfd24-8ac3-43e9-a117-a2f9d00f74b1'); + const pandasQuestion1 = insertedQuestions.find((q) => q.exerciseId === 'bd6e81c7-3d28-4037-acf0-a3028c583771'); + const pandasQuestion2 = insertedQuestions.find((q) => q.exerciseId === 'd8cd1cf7-1951-46b3-ad1c-41e415185bc1'); + + const optionsToInsert = [ + // MVC question options + ...(mvcQuestion + ? [ + { + label: 'Modulus, View, Calculus', + isCorrect: false, + questionId: mvcQuestion.id + }, + { + label: 'Mode, Vue, Controller', + isCorrect: false, + questionId: mvcQuestion.id + }, + { + label: 'Model, View, Controller', + isCorrect: true, + questionId: mvcQuestion.id + } + ] + : []), + // React question options + ...(reactQuestion + ? [ + { + label: 'It stores React components in a centralized virtual memory.', + isCorrect: false, + questionId: reactQuestion.id + }, + { + label: 'It facilitates direct communication between the browser and the server.', + isCorrect: false, + questionId: reactQuestion.id + }, + { + label: + 'It is an in-memory representation of the actual DOM, allowing React to efficiently update and render UI changes.', + isCorrect: true, + questionId: reactQuestion.id + }, + { + label: 'It handles encryption and decryption for secure data transmission.', + isCorrect: false, + questionId: reactQuestion.id + } + ] + : []), + // Pandas question 1 options + ...(pandasQuestion1 + ? [ + { + label: 'drop_null()', + isCorrect: false, + questionId: pandasQuestion1.id + }, + { + label: 'remove_missing()', + isCorrect: false, + questionId: pandasQuestion1.id + }, + { + label: 'dropna()', + isCorrect: true, + questionId: pandasQuestion1.id + }, + { + label: 'clean_data()', + isCorrect: false, + questionId: pandasQuestion1.id + } + ] + : []), + // Pandas question 2 options + ...(pandasQuestion2 + ? [ + { + label: 'To increase the size of the dataset', + isCorrect: false, + questionId: pandasQuestion2.id + }, + { + label: 'To introduce noise and variability', + isCorrect: false, + questionId: pandasQuestion2.id + }, + { + label: 'To remove errors and inconsistencies', + isCorrect: true, + questionId: pandasQuestion2.id + }, + { + label: 'To speed up the data processing time', + isCorrect: false, + questionId: pandasQuestion2.id + } + ] + : []) + ] + .filter((o) => !existingOptionLabels.has(o.label)) + .map((o) => ({ + ...o, + id: nextId++ + })); + + if (optionsToInsert.length > 0) { + await db.insert(option).values(optionsToInsert); + console.log(` ✓ Inserted ${optionsToInsert.length} option(s)`); + } else { + console.log(' ✓ Options already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/organization.ts b/packages/db/src/utils/seed/organization.ts new file mode 100644 index 00000000..6b1dc37e --- /dev/null +++ b/packages/db/src/utils/seed/organization.ts @@ -0,0 +1,29 @@ +import { db, organization } from '@db/drizzle'; + +export async function seedOrganization({ testOrgId }: { testOrgId: string }) { + const existingOrgs = await db.select().from(organization); + const existingOrgIds = existingOrgs.map((o) => o.id); + + const organizationsToInsert = [ + { + id: testOrgId, + name: 'Udemy Test', + siteName: 'udemy-test', + settings: {}, + landingpage: {}, + theme: '', + customization: { + apps: { poll: true, comments: true }, + course: { grading: true, newsfeed: true }, + dashboard: { exercise: true, community: true, bannerText: '', bannerImage: '' } + } + } + ].filter((o) => !existingOrgIds.includes(o.id)); + + if (organizationsToInsert.length > 0) { + await db.insert(organization).values(organizationsToInsert); + console.log(` ✓ Inserted ${organizationsToInsert.length} organization(s)`); + } else { + console.log(' ✓ Organizations already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/organizationmember.ts b/packages/db/src/utils/seed/organizationmember.ts new file mode 100644 index 00000000..320a8736 --- /dev/null +++ b/packages/db/src/utils/seed/organizationmember.ts @@ -0,0 +1,35 @@ +import { db, organizationmember } from '@db/drizzle'; + +interface SeedOrganizationMember { + testOrgId: string; + adminUserId: string; +} + +export async function seedOrganizationMember({ testOrgId, adminUserId }: SeedOrganizationMember) { + const existingOrgMembers = await db.select().from(organizationmember); + const existingOrgMemberKeys = existingOrgMembers.map((om) => `${om.organizationId}-${om.profileId}`); + + const studentUserId = '0c256e75-aa40-4f62-8d30-0217ca1c60d9'; + + const orgMembersToInsert = [ + { + organizationId: testOrgId, + roleId: 1, // ADMIN + profileId: adminUserId, + verified: false + }, + { + organizationId: testOrgId, + roleId: 3, // STUDENT + profileId: studentUserId, + verified: false + } + ].filter((om) => !existingOrgMemberKeys.includes(`${om.organizationId}-${om.profileId}`)); + + if (orgMembersToInsert.length > 0) { + await db.insert(organizationmember).values(orgMembersToInsert); + console.log(` ✓ Inserted ${orgMembersToInsert.length} organization member(s)`); + } else { + console.log(' ✓ Organization members already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/profile.ts b/packages/db/src/utils/seed/profile.ts new file mode 100644 index 00000000..ee5e79f5 --- /dev/null +++ b/packages/db/src/utils/seed/profile.ts @@ -0,0 +1,26 @@ +import { db, profile } from '@db/drizzle'; +import { TUser } from '@db/types'; + +export async function seedProfile({ usersData }: { usersData: TUser[] }) { + const existingProfiles = await db.select().from(profile); + const existingProfileIds = existingProfiles.map((p) => p.id); + + const profilesToInsert = usersData + .map((user) => ({ + id: user.id, + fullname: user.name, + username: `${user.email.split('@')[0]}${Date.now()}`, + email: user.email, + avatarUrl: 'https://pgrest.classroomio.com/storage/v1/object/public/avatars/avatar.png', + canAddCourse: true, + isEmailVerified: true + })) + .filter((p) => !existingProfileIds.includes(p.id)); + + if (profilesToInsert.length > 0) { + await db.insert(profile).values(profilesToInsert); + console.log(` ✓ Inserted ${profilesToInsert.length} profile(s)`); + } else { + console.log(' ✓ Profiles already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/question.ts b/packages/db/src/utils/seed/question.ts new file mode 100644 index 00000000..b473275f --- /dev/null +++ b/packages/db/src/utils/seed/question.ts @@ -0,0 +1,67 @@ +import { db, eq, or, question, sql } from '@db/drizzle'; +import { seedOptions } from './option'; + +export async function seedQuestions() { + // Get max id from the question table + const maxIdResult = await db.select({ maxId: sql`COALESCE(MAX(${question.id}), 0)` }).from(question); + let nextId = (maxIdResult[0]?.maxId || 0) + 1; + + const questionsData = [ + { + questionTypeId: 2, // CHECKBOX + title: 'What does MVC stand for', + exerciseId: 'e2ea9fb8-6448-4f6c-a1d5-02c2b12cf862', + points: 0, + order: 0 + }, + { + questionTypeId: 2, // CHECKBOX + title: + "What is the purpose of the virtual DOM in React, and how does it contribute to the framework's performance optimization?", + exerciseId: 'e78bfd24-8ac3-43e9-a117-a2f9d00f74b1', + points: 10, + order: 0 + }, + { + questionTypeId: 2, // CHECKBOX + title: 'When working with Pandas, what function is used to drop missing values from a DataFrame?', + exerciseId: 'bd6e81c7-3d28-4037-acf0-a3028c583771', + points: 20, + order: 0 + }, + { + questionTypeId: 2, // CHECKBOX + title: 'What is the primary purpose of data cleaning in the context of data preprocessing?', + exerciseId: 'd8cd1cf7-1951-46b3-ad1c-41e415185bc1', + points: 0, + order: 0 + } + ]; + + const existingQuestions = await db + .select() + .from(question) + .where(or(...questionsData.map((q) => eq(question.title, q.title)))); + const existingQuestionTitles = existingQuestions.map((q) => q.title); + + const questionsToInsert = questionsData + .filter((q) => !existingQuestionTitles.includes(q.title)) + .map((q) => ({ + ...q, + id: nextId++ + })); + + let insertedQuestions = []; + if (questionsToInsert.length > 0) { + const result = await db.insert(question).values(questionsToInsert).returning(); + insertedQuestions = result; + console.log(` ✓ Inserted ${questionsToInsert.length} question(s)`); + } else { + console.log(' ✓ Questions already exist, skipping'); + insertedQuestions = existingQuestions; + } + + // Seed options for questions + console.log('📝 Seeding question options...'); + await seedOptions(insertedQuestions); +} diff --git a/packages/db/src/utils/seed/questionType.ts b/packages/db/src/utils/seed/questionType.ts new file mode 100644 index 00000000..9de448e2 --- /dev/null +++ b/packages/db/src/utils/seed/questionType.ts @@ -0,0 +1,34 @@ +import { db, questionType } from '@db/drizzle'; + +export async function seedQuestionTypes() { + const existingQuestionTypes = await db.select().from(questionType); + const questionTypeNames = existingQuestionTypes.map((q) => q.typename); + + const questionTypesToInsert = [ + { + label: 'Single answer', + typename: 'RADIO', + createdAt: '2021-08-07 18:49:46.246529+00', + updatedAt: '2021-08-15 00:57:08.12069+00' + }, + { + label: 'Multiple answers', + typename: 'CHECKBOX', + createdAt: '2021-08-07 18:49:46.246529+00', + updatedAt: '2021-08-15 00:57:27.935478+00' + }, + { + label: 'Paragraph', + typename: 'TEXTAREA', + createdAt: '2021-08-07 18:49:46.246529+00', + updatedAt: '2021-08-15 00:57:38.634665+00' + } + ].filter((q) => !questionTypeNames.includes(q.typename)); + + if (questionTypesToInsert.length > 0) { + await db.insert(questionType).values(questionTypesToInsert); + console.log(` ✓ Inserted ${questionTypesToInsert.length} question type(s)`); + } else { + console.log(' ✓ Question types already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/role.ts b/packages/db/src/utils/seed/role.ts new file mode 100644 index 00000000..bfa5d578 --- /dev/null +++ b/packages/db/src/utils/seed/role.ts @@ -0,0 +1,31 @@ +import { db, role } from '@db/drizzle'; + +export async function seedRoles() { + const existingRoles = await db.select().from(role); + const existingRoleIds = existingRoles.map((r) => r.id); + + const rolesToInsert = [ + { + id: 1, + type: 'ADMIN', + description: 'The main controller' + }, + { + id: 2, + type: 'TUTOR', + description: 'Can make changes to content, courses, but cant control passwords and cant add other tutors' + }, + { + id: 3, + type: 'STUDENT', + description: 'A student role, can interact with application but cant make changes' + } + ].filter((r) => !existingRoleIds.includes(r.id)); + + if (rolesToInsert.length > 0) { + await db.insert(role).values(rolesToInsert); + console.log(` ✓ Inserted ${rolesToInsert.length} role(s)`); + } else { + console.log(' ✓ Roles already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/submission.ts b/packages/db/src/utils/seed/submission.ts new file mode 100644 index 00000000..8b1f591f --- /dev/null +++ b/packages/db/src/utils/seed/submission.ts @@ -0,0 +1,25 @@ +import { db, submissionstatus } from '@db/drizzle'; + +export async function seedSubmissions() { + const existingStatuses = await db.select().from(submissionstatus); + const statusLabels = existingStatuses.map((s) => s.label); + + const statusesToInsert = [ + { + label: 'Submitted' + }, + { + label: 'In Progress' + }, + { + label: 'Graded' + } + ].filter((s) => !statusLabels.includes(s.label)); + + if (statusesToInsert.length > 0) { + await db.insert(submissionstatus).values(statusesToInsert); + console.log(` ✓ Inserted ${statusesToInsert.length} submission status(es)`); + } else { + console.log(' ✓ Submission statuses already exist, skipping'); + } +} diff --git a/packages/db/src/utils/seed/users.ts b/packages/db/src/utils/seed/users.ts new file mode 100644 index 00000000..663c1d1b --- /dev/null +++ b/packages/db/src/utils/seed/users.ts @@ -0,0 +1,27 @@ +import { db, user } from '@db/drizzle'; +import { TUser } from '@db/types'; + +export async function seedUsers({ usersData }: { usersData: TUser[] }) { + const existingUsers = await db.select().from(user); + const existingUserIds = existingUsers.map((u) => u.id); + + const usersToInsert = usersData + .map((userData) => ({ + id: userData.id, + name: userData.name, + email: userData.email, + emailVerified: userData.emailVerified, + image: null, + role: null, + banned: false, + isAnonymous: false + })) + .filter((u) => !existingUserIds.includes(u.id)); + + if (usersToInsert.length > 0) { + await db.insert(user).values(usersToInsert); + console.log(` ✓ Inserted ${usersToInsert.length} user(s)`); + } else { + console.log(' ✓ Users already exist, skipping'); + } +} diff --git a/packages/utils/src/validation/mocks/index.ts b/packages/utils/src/validation/mocks/index.ts new file mode 100644 index 00000000..1b4a22a4 --- /dev/null +++ b/packages/utils/src/validation/mocks/index.ts @@ -0,0 +1 @@ +export * from './template'; diff --git a/packages/utils/src/validation/mocks/template.ts b/packages/utils/src/validation/mocks/template.ts new file mode 100644 index 00000000..dc597312 --- /dev/null +++ b/packages/utils/src/validation/mocks/template.ts @@ -0,0 +1,12 @@ +import * as z from 'zod'; + +export const ZTemplateById = z.object({ + id: z.coerce.number().min(1, 'ID is required') +}); + +export const ZTemplateByTag = z.object({ + tag: z.string().min(1, 'Tag is required') +}); + +export type TTemplateById = z.infer; +export type TTemplateByTag = z.infer;