@headless-lms/server 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_virtual/_rolldown/runtime.js +5 -0
- package/dist/app/community-mail.d.ts +26 -0
- package/dist/app/community-mail.js +96 -0
- package/dist/app/community-mail.js.map +1 -0
- package/dist/app/container.d.ts +18 -14
- package/dist/app/container.js +136 -43
- package/dist/app/container.js.map +1 -1
- package/dist/app/mcp-endpoints.d.ts +13 -0
- package/dist/app/mcp-endpoints.js +25 -0
- package/dist/app/mcp-endpoints.js.map +1 -0
- package/dist/http/config.d.ts +2 -1
- package/dist/http/errors.d.ts +14 -0
- package/dist/http/errors.js +147 -0
- package/dist/http/errors.js.map +1 -0
- package/dist/http/mcp/catalogue.d.ts +37 -0
- package/dist/http/mcp/catalogue.js +69 -0
- package/dist/http/mcp/catalogue.js.map +1 -0
- package/dist/http/mcp/challenge.d.ts +7 -0
- package/dist/http/mcp/challenge.js +35 -0
- package/dist/http/mcp/challenge.js.map +1 -0
- package/dist/http/mcp/input-schema.d.ts +7 -0
- package/dist/http/mcp/input-schema.js +28 -0
- package/dist/http/mcp/input-schema.js.map +1 -0
- package/dist/http/mcp/route.d.ts +8 -0
- package/dist/http/mcp/route.js +75 -0
- package/dist/http/mcp/route.js.map +1 -0
- package/dist/http/mcp/server.d.ts +8 -0
- package/dist/http/mcp/server.js +31 -0
- package/dist/http/mcp/server.js.map +1 -0
- package/dist/http/mcp/test-support.d.ts +55 -0
- package/dist/http/mcp/test-support.js +151 -0
- package/dist/http/mcp/test-support.js.map +1 -0
- package/dist/http/mcp/tool.d.ts +48 -0
- package/dist/http/mcp/tool.js +58 -0
- package/dist/http/mcp/tool.js.map +1 -0
- package/dist/http/mcp/tools/automations.d.ts +6 -0
- package/dist/http/mcp/tools/automations.js +120 -0
- package/dist/http/mcp/tools/automations.js.map +1 -0
- package/dist/http/mcp/tools/catalog.d.ts +6 -0
- package/dist/http/mcp/tools/catalog.js +200 -0
- package/dist/http/mcp/tools/catalog.js.map +1 -0
- package/dist/http/mcp/tools/comments.d.ts +6 -0
- package/dist/http/mcp/tools/comments.js +70 -0
- package/dist/http/mcp/tools/comments.js.map +1 -0
- package/dist/http/mcp/tools/communities.d.ts +6 -0
- package/dist/http/mcp/tools/communities.js +175 -0
- package/dist/http/mcp/tools/communities.js.map +1 -0
- package/dist/http/mcp/tools/community-members.d.ts +6 -0
- package/dist/http/mcp/tools/community-members.js +89 -0
- package/dist/http/mcp/tools/community-members.js.map +1 -0
- package/dist/http/mcp/tools/community-moderation.d.ts +6 -0
- package/dist/http/mcp/tools/community-moderation.js +144 -0
- package/dist/http/mcp/tools/community-moderation.js.map +1 -0
- package/dist/http/mcp/tools/courses.d.ts +6 -0
- package/dist/http/mcp/tools/courses.js +121 -0
- package/dist/http/mcp/tools/courses.js.map +1 -0
- package/dist/http/mcp/tools/index.d.ts +6 -0
- package/dist/http/mcp/tools/index.js +23 -0
- package/dist/http/mcp/tools/index.js.map +1 -0
- package/dist/http/mcp/tools/people.d.ts +6 -0
- package/dist/http/mcp/tools/people.js +145 -0
- package/dist/http/mcp/tools/people.js.map +1 -0
- package/dist/http/plugins/auth.js +13 -5
- package/dist/http/plugins/auth.js.map +1 -1
- package/dist/http/plugins/error-handler.d.ts +2 -1
- package/dist/http/plugins/error-handler.js +49 -65
- package/dist/http/plugins/error-handler.js.map +1 -1
- package/dist/http/plugins/openapi.js +49 -2
- package/dist/http/plugins/openapi.js.map +1 -1
- package/dist/http/routes/access.d.ts +7 -0
- package/dist/http/routes/access.js +140 -0
- package/dist/http/routes/access.js.map +1 -0
- package/dist/http/routes/activities.js +68 -23
- package/dist/http/routes/activities.js.map +1 -1
- package/dist/http/routes/assets.js +60 -55
- package/dist/http/routes/assets.js.map +1 -1
- package/dist/http/routes/automations.js +44 -24
- package/dist/http/routes/automations.js.map +1 -1
- package/dist/http/routes/bundles.js +29 -18
- package/dist/http/routes/bundles.js.map +1 -1
- package/dist/http/routes/communities.d.ts +7 -0
- package/dist/http/routes/communities.js +462 -0
- package/dist/http/routes/communities.js.map +1 -0
- package/dist/http/routes/community-moderation.d.ts +7 -0
- package/dist/http/routes/community-moderation.js +425 -0
- package/dist/http/routes/community-moderation.js.map +1 -0
- package/dist/http/routes/course-analytics.js +9 -7
- package/dist/http/routes/course-analytics.js.map +1 -1
- package/dist/http/routes/courses.js +30 -15
- package/dist/http/routes/courses.js.map +1 -1
- package/dist/http/routes/dashboard.js +15 -6
- package/dist/http/routes/dashboard.js.map +1 -1
- package/dist/http/routes/discussion.js +34 -26
- package/dist/http/routes/discussion.js.map +1 -1
- package/dist/http/routes/downloads.js +48 -30
- package/dist/http/routes/downloads.js.map +1 -1
- package/dist/http/routes/entitlements.js +16 -7
- package/dist/http/routes/entitlements.js.map +1 -1
- package/dist/http/routes/integrations.js +38 -41
- package/dist/http/routes/integrations.js.map +1 -1
- package/dist/http/routes/member/actor.d.ts +14 -0
- package/dist/http/routes/member/actor.js +11 -0
- package/dist/http/routes/member/actor.js.map +1 -0
- package/dist/http/routes/member/assets.d.ts +7 -0
- package/dist/http/routes/member/assets.js +79 -0
- package/dist/http/routes/member/assets.js.map +1 -0
- package/dist/http/routes/member/chats.d.ts +7 -0
- package/dist/http/routes/member/chats.js +456 -0
- package/dist/http/routes/member/chats.js.map +1 -0
- package/dist/http/routes/member/comments.d.ts +7 -0
- package/dist/http/routes/member/comments.js +187 -0
- package/dist/http/routes/member/comments.js.map +1 -0
- package/dist/http/routes/member/community.d.ts +7 -0
- package/dist/http/routes/member/community.js +288 -0
- package/dist/http/routes/member/community.js.map +1 -0
- package/dist/http/routes/member/courses.d.ts +7 -0
- package/dist/http/routes/member/courses.js +101 -0
- package/dist/http/routes/member/courses.js.map +1 -0
- package/dist/http/routes/member/downloads.d.ts +7 -0
- package/dist/http/routes/member/downloads.js +56 -0
- package/dist/http/routes/member/downloads.js.map +1 -0
- package/dist/http/routes/member/files.d.ts +7 -0
- package/dist/http/routes/member/files.js +184 -0
- package/dist/http/routes/member/files.js.map +1 -0
- package/dist/http/routes/member/index.d.ts +7 -0
- package/dist/http/routes/member/index.js +31 -0
- package/dist/http/routes/member/index.js.map +1 -0
- package/dist/http/routes/member/notifications.d.ts +7 -0
- package/dist/http/routes/member/notifications.js +101 -0
- package/dist/http/routes/member/notifications.js.map +1 -0
- package/dist/http/routes/member/org.d.ts +7 -0
- package/dist/http/routes/member/org.js +56 -0
- package/dist/http/routes/member/org.js.map +1 -0
- package/dist/http/routes/member/posts.d.ts +7 -0
- package/dist/http/routes/member/posts.js +612 -0
- package/dist/http/routes/member/posts.js.map +1 -0
- package/dist/http/routes/member/profile.d.ts +7 -0
- package/dist/http/routes/member/profile.js +48 -0
- package/dist/http/routes/member/profile.js.map +1 -0
- package/dist/http/routes/member/progress.d.ts +7 -0
- package/dist/http/routes/member/progress.js +60 -0
- package/dist/http/routes/member/progress.js.map +1 -0
- package/dist/http/routes/organizations.js +49 -24
- package/dist/http/routes/organizations.js.map +1 -1
- package/dist/http/routes/students.js +32 -47
- package/dist/http/routes/students.js.map +1 -1
- package/dist/http/routes/webhooks.js +26 -18
- package/dist/http/routes/webhooks.js.map +1 -1
- package/dist/http/routes.js +8 -2
- package/dist/http/routes.js.map +1 -1
- package/dist/http/schemas/access.d.ts +146 -0
- package/dist/http/schemas/access.js +36 -0
- package/dist/http/schemas/access.js.map +1 -0
- package/dist/http/schemas/activities.d.ts +3 -1
- package/dist/http/schemas/activities.js +4 -4
- package/dist/http/schemas/activities.js.map +1 -1
- package/dist/http/schemas/assets.d.ts +73 -74
- package/dist/http/schemas/assets.js +23 -13
- package/dist/http/schemas/assets.js.map +1 -1
- package/dist/http/schemas/automations.d.ts +10 -0
- package/dist/http/schemas/bundles.js +6 -6
- package/dist/http/schemas/bundles.js.map +1 -1
- package/dist/http/schemas/chats.d.ts +386 -0
- package/dist/http/schemas/chats.js +58 -0
- package/dist/http/schemas/chats.js.map +1 -0
- package/dist/http/schemas/community.d.ts +537 -0
- package/dist/http/schemas/community.js +129 -0
- package/dist/http/schemas/community.js.map +1 -0
- package/dist/http/schemas/content.d.ts +4 -2
- package/dist/http/schemas/content.js +11 -8
- package/dist/http/schemas/content.js.map +1 -1
- package/dist/http/schemas/discussion.d.ts +76 -14
- package/dist/http/schemas/discussion.js +12 -11
- package/dist/http/schemas/discussion.js.map +1 -1
- package/dist/http/schemas/downloads.d.ts +11 -5
- package/dist/http/schemas/downloads.js +14 -11
- package/dist/http/schemas/downloads.js.map +1 -1
- package/dist/http/schemas/entitlements.d.ts +6 -6
- package/dist/http/schemas/entitlements.js +6 -6
- package/dist/http/schemas/entitlements.js.map +1 -1
- package/dist/http/schemas/files.d.ts +45 -0
- package/dist/http/schemas/files.js +25 -0
- package/dist/http/schemas/files.js.map +1 -0
- package/dist/http/schemas/index.d.ts +15 -8
- package/dist/http/schemas/index.js +15 -8
- package/dist/http/schemas/integrations.d.ts +2 -2
- package/dist/http/schemas/invites.d.ts +4 -0
- package/dist/http/schemas/invites.js +1 -0
- package/dist/http/schemas/invites.js.map +1 -1
- package/dist/http/schemas/{learn.d.ts → member.d.ts} +31 -18
- package/dist/http/schemas/{learn.js → member.js} +17 -13
- package/dist/http/schemas/member.js.map +1 -0
- package/dist/http/schemas/members.d.ts +39 -6
- package/dist/http/schemas/members.js +17 -3
- package/dist/http/schemas/members.js.map +1 -1
- package/dist/http/schemas/notifications.d.ts +227 -0
- package/dist/http/schemas/notifications.js +44 -0
- package/dist/http/schemas/notifications.js.map +1 -0
- package/dist/http/schemas/posts.d.ts +316 -0
- package/dist/http/schemas/posts.js +47 -0
- package/dist/http/schemas/posts.js.map +1 -0
- package/dist/http/schemas/profile.d.ts +23 -0
- package/dist/http/schemas/profile.js +15 -0
- package/dist/http/schemas/profile.js.map +1 -0
- package/dist/http/schemas/shared.d.ts +25 -1
- package/dist/http/schemas/shared.js +19 -2
- package/dist/http/schemas/shared.js.map +1 -1
- package/dist/http/schemas/students.d.ts +4 -10
- package/dist/http/schemas/students.js +1 -7
- package/dist/http/schemas/students.js.map +1 -1
- package/dist/http/scope.d.ts +2 -1
- package/dist/http/scope.js +20 -4
- package/dist/http/scope.js.map +1 -1
- package/dist/http/server.js +6 -1
- package/dist/http/server.js.map +1 -1
- package/dist/http/web-bridge.d.ts +1 -1
- package/dist/http/web-bridge.js +2 -2
- package/dist/http/web-bridge.js.map +1 -1
- package/dist/http/well-known.d.ts +7 -0
- package/dist/http/well-known.js +22 -0
- package/dist/http/well-known.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js +78 -0
- package/dist/node_modules/.pnpm/@jridgewell_sourcemap-codec@1.5.5/node_modules/@jridgewell/sourcemap-codec/dist/sourcemap-codec.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_expect@2.1.9/node_modules/@vitest/expect/dist/index.js +1267 -0
- package/dist/node_modules/.pnpm/@vitest_expect@2.1.9/node_modules/@vitest/expect/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_pretty-format@2.1.9/node_modules/@vitest/pretty-format/dist/index.js +814 -0
- package/dist/node_modules/.pnpm/@vitest_pretty-format@2.1.9/node_modules/@vitest/pretty-format/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/chunk-tasks.js +43 -0
- package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/chunk-tasks.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/index.js +527 -0
- package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_runner@2.1.9/node_modules/@vitest/runner/dist/utils.js +2 -0
- package/dist/node_modules/.pnpm/@vitest_snapshot@2.1.9/node_modules/@vitest/snapshot/dist/index.js +1737 -0
- package/dist/node_modules/.pnpm/@vitest_snapshot@2.1.9/node_modules/@vitest/snapshot/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_spy@2.1.9/node_modules/@vitest/spy/dist/index.js +137 -0
- package/dist/node_modules/.pnpm/@vitest_spy@2.1.9/node_modules/@vitest/spy/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.js +118 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/chunk-_commonjsHelpers.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/diff.js +1222 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/diff.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/error.js +91 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/error.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/helpers.js +113 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/helpers.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/index.js +449 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/source-map.js +163 -0
- package/dist/node_modules/.pnpm/@vitest_utils@2.1.9/node_modules/@vitest/utils/dist/source-map.js.map +1 -0
- package/dist/node_modules/.pnpm/chai@5.3.3/node_modules/chai/index.js +3187 -0
- package/dist/node_modules/.pnpm/chai@5.3.3/node_modules/chai/index.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.js +11 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/arguments.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.js +16 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/array.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.js +11 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/bigint.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.js +15 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/class.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.js +13 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/date.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.js +34 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/error.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.js +12 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/function.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.js +122 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/helpers.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.js +39 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/html.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.js +99 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/index.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.js +25 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/map.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.js +14 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/number.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.js +21 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/object.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.js +6 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/promise.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.js +12 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/regexp.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.js +18 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/set.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.js +24 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/string.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.js +9 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/symbol.js.map +1 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.js +30 -0
- package/dist/node_modules/.pnpm/loupe@3.2.1/node_modules/loupe/lib/typedarray.js.map +1 -0
- package/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js +939 -0
- package/dist/node_modules/.pnpm/magic-string@0.30.21/node_modules/magic-string/dist/magic-string.es.js.map +1 -0
- package/dist/node_modules/.pnpm/pathe@1.1.2/node_modules/pathe/dist/shared/pathe.ff20891b.js +82 -0
- package/dist/node_modules/.pnpm/pathe@1.1.2/node_modules/pathe/dist/shared/pathe.ff20891b.js.map +1 -0
- package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js +82 -0
- package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/chunk-BVHSVHOK.js.map +1 -0
- package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/node.js +8 -0
- package/dist/node_modules/.pnpm/tinyrainbow@1.2.0/node_modules/tinyrainbow/dist/node.js.map +1 -0
- package/dist/node_modules/.pnpm/tinyspy@3.0.2/node_modules/tinyspy/dist/index.js +83 -0
- package/dist/node_modules/.pnpm/tinyspy@3.0.2/node_modules/tinyspy/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/_commonjsHelpers.BFTU3MAI.js +9 -0
- package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/_commonjsHelpers.BFTU3MAI.js.map +1 -0
- package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/date.W2xKR2qe.js +51 -0
- package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/date.W2xKR2qe.js.map +1 -0
- package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/utils.C8RiOc4B.js +48 -0
- package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/utils.C8RiOc4B.js.map +1 -0
- package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/vi.DgezovHB.js +2426 -0
- package/dist/node_modules/.pnpm/vitest@2.1.9_@types_node@22.20.0_jsdom@25.0.1_lightningcss@1.32.0/node_modules/vitest/dist/chunks/vi.DgezovHB.js.map +1 -0
- package/dist/packages/server/package.js +6 -0
- package/dist/packages/server/package.js.map +1 -0
- package/package.json +9 -7
- package/dist/app/notifications.d.ts +0 -7
- package/dist/app/notifications.js +0 -26
- package/dist/app/notifications.js.map +0 -1
- package/dist/http/routes/learn/assets.d.ts +0 -8
- package/dist/http/routes/learn/assets.js +0 -32
- package/dist/http/routes/learn/assets.js.map +0 -1
- package/dist/http/routes/learn/comments.d.ts +0 -7
- package/dist/http/routes/learn/comments.js +0 -175
- package/dist/http/routes/learn/comments.js.map +0 -1
- package/dist/http/routes/learn/courses.d.ts +0 -7
- package/dist/http/routes/learn/courses.js +0 -98
- package/dist/http/routes/learn/courses.js.map +0 -1
- package/dist/http/routes/learn/downloads.d.ts +0 -7
- package/dist/http/routes/learn/downloads.js +0 -76
- package/dist/http/routes/learn/downloads.js.map +0 -1
- package/dist/http/routes/learn/index.d.ts +0 -7
- package/dist/http/routes/learn/index.js +0 -19
- package/dist/http/routes/learn/index.js.map +0 -1
- package/dist/http/routes/learn/org.d.ts +0 -7
- package/dist/http/routes/learn/org.js +0 -50
- package/dist/http/routes/learn/org.js.map +0 -1
- package/dist/http/routes/learn/progress.d.ts +0 -7
- package/dist/http/routes/learn/progress.js +0 -63
- package/dist/http/routes/learn/progress.js.map +0 -1
- package/dist/http/schemas/learn.js.map +0 -1
- package/dist/package.js +0 -6
- package/dist/package.js.map +0 -1
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { refusals } from "../../schemas/shared.js";
|
|
2
|
+
import { UpdateProfile, UserProfile } from "../../schemas/profile.js";
|
|
3
|
+
import "../../schemas/index.js";
|
|
4
|
+
import { actorOf } from "./actor.js";
|
|
5
|
+
//#region src/http/routes/member/profile.ts
|
|
6
|
+
async function memberProfileRoutes(app, container) {
|
|
7
|
+
const r = app.withTypeProvider();
|
|
8
|
+
const tags = ["Profile"];
|
|
9
|
+
r.route({
|
|
10
|
+
method: "GET",
|
|
11
|
+
url: "/api/member/profile",
|
|
12
|
+
preHandler: app.requireOrgSession,
|
|
13
|
+
schema: {
|
|
14
|
+
operationId: "getMemberProfile",
|
|
15
|
+
tags,
|
|
16
|
+
summary: "Read your profile",
|
|
17
|
+
description: "For any signed-in person. The profile is yours across organizations; `imageUrl` is set only when the photo was uploaded in this organization. Before you save one, every field is empty.",
|
|
18
|
+
response: { 200: UserProfile }
|
|
19
|
+
},
|
|
20
|
+
handler: async (req) => {
|
|
21
|
+
return container.identity.profileOf(req.orgId, req.userId);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
r.route({
|
|
25
|
+
method: "PATCH",
|
|
26
|
+
url: "/api/member/profile",
|
|
27
|
+
preHandler: app.requireOrgSession,
|
|
28
|
+
schema: {
|
|
29
|
+
operationId: "updateMemberProfile",
|
|
30
|
+
tags,
|
|
31
|
+
summary: "Update your profile",
|
|
32
|
+
description: "For a member of the organization. Changes only the fields sent; null clears one. 404 when `imageAssetId` is not a finalized image you uploaded here.",
|
|
33
|
+
body: UpdateProfile,
|
|
34
|
+
response: {
|
|
35
|
+
200: UserProfile,
|
|
36
|
+
...refusals(403, 404)
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
handler: async (req) => {
|
|
40
|
+
const orgUser = await actorOf(container, req.orgId, req.userId);
|
|
41
|
+
return container.identity.updateProfile(req.orgId, req.userId, req.userId, orgUser.id, req.body);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { memberProfileRoutes };
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.js","names":[],"sources":["../../../../src/http/routes/member/profile.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport type { Container } from '../../../app/container.js';\nimport { actorOf } from './actor.js';\nimport { UpdateProfile, UserProfile, refusals } from '../../schemas/index.js';\n\nexport async function memberProfileRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Profile'];\n\n r.route({\n method: 'GET',\n url: '/api/member/profile',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberProfile',\n tags,\n summary: 'Read your profile',\n description:\n 'For any signed-in person. The profile is yours across organizations; `imageUrl` is set only when the photo was uploaded in this organization. Before you save one, every field is empty.',\n response: { 200: UserProfile },\n },\n handler: async (req) => {\n return container.identity.profileOf(req.orgId, req.userId);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/profile',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateMemberProfile',\n tags,\n summary: 'Update your profile',\n description:\n 'For a member of the organization. Changes only the fields sent; null clears one. 404 when `imageAssetId` is not a finalized image you uploaded here.',\n body: UpdateProfile,\n response: { 200: UserProfile, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return container.identity.updateProfile(\n req.orgId,\n req.userId,\n req.userId,\n orgUser.id,\n req.body,\n );\n },\n });\n}\n"],"mappings":";;;;;AAMA,eAAsB,oBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,OAAO,CAAC,SAAS;CAEvB,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,UAAU,EAAE,KAAK,YAAY;EAC/B;EACA,SAAS,OAAO,QAAQ;GACtB,OAAO,UAAU,SAAS,UAAU,IAAI,OAAO,IAAI,MAAM;EAC3D;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,MAAM;GACN,UAAU;IAAE,KAAK;IAAa,GAAG,SAAS,KAAK,GAAG;GAAE;EACtD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,OAAO,UAAU,SAAS,cACxB,IAAI,OACJ,IAAI,QACJ,IAAI,QACJ,QAAQ,IACR,IAAI,IACN;EACF;CACF,CAAC;AACH"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Container } from "../../../app/container.js";
|
|
2
|
+
import { FastifyInstance } from "fastify";
|
|
3
|
+
//#region src/http/routes/member/progress.d.ts
|
|
4
|
+
declare function memberProgressRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { memberProgressRoutes };
|
|
7
|
+
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { refusals } from "../../schemas/shared.js";
|
|
2
|
+
import { CourseProgress, MemberCourseIdParam, ProgressStatus, ReportProgress } from "../../schemas/member.js";
|
|
3
|
+
import "../../schemas/index.js";
|
|
4
|
+
import { actorOf } from "./actor.js";
|
|
5
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
6
|
+
//#region src/http/routes/member/progress.ts
|
|
7
|
+
async function memberProgressRoutes(app, container) {
|
|
8
|
+
const r = app.withTypeProvider();
|
|
9
|
+
const member = container.reporting.member;
|
|
10
|
+
r.route({
|
|
11
|
+
method: "POST",
|
|
12
|
+
url: "/api/member/progress",
|
|
13
|
+
preHandler: app.requireOrgSession,
|
|
14
|
+
schema: {
|
|
15
|
+
operationId: "reportProgress",
|
|
16
|
+
tags: ["Progress"],
|
|
17
|
+
summary: "Report usage on an activity; the progress service decides completion",
|
|
18
|
+
description: "For a member who can open the course. Reports facts, never a decision: the service decides whether the activity is complete. 404 when the activity or its course is not published, or you cannot open it.",
|
|
19
|
+
body: ReportProgress,
|
|
20
|
+
response: {
|
|
21
|
+
200: ProgressStatus,
|
|
22
|
+
...refusals(403, 404)
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
handler: async (req) => {
|
|
26
|
+
const orgUser = await actorOf(container, req.orgId, req.userId);
|
|
27
|
+
return { status: (await container.progress.report(req.orgId, {
|
|
28
|
+
orgUserId: orgUser.id,
|
|
29
|
+
activityId: req.body.activity,
|
|
30
|
+
reports: req.body.reports
|
|
31
|
+
})).completedAt ? "completed" : "in-progress" };
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
r.route({
|
|
35
|
+
method: "GET",
|
|
36
|
+
url: "/api/member/courses/:courseId/progress",
|
|
37
|
+
preHandler: app.requireOrgSession,
|
|
38
|
+
schema: {
|
|
39
|
+
operationId: "getMemberCourseProgress",
|
|
40
|
+
tags: ["Progress"],
|
|
41
|
+
summary: "Your progress in one course",
|
|
42
|
+
description: "For a member of the organization, derived from the course's current structure. 404 when the course is not published or you do not hold it.",
|
|
43
|
+
params: MemberCourseIdParam,
|
|
44
|
+
response: {
|
|
45
|
+
200: CourseProgress,
|
|
46
|
+
...refusals(403, 404)
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
handler: async (req) => {
|
|
50
|
+
const orgUser = await actorOf(container, req.orgId, req.userId);
|
|
51
|
+
const view = await member.courseProgress(req.orgId, orgUser.id, req.params.courseId);
|
|
52
|
+
if (!view) throw new NotFoundError("Course", req.params.courseId);
|
|
53
|
+
return view;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { memberProgressRoutes };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.js","names":[],"sources":["../../../../src/http/routes/member/progress.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n CourseProgress,\n MemberCourseIdParam,\n ProgressStatus,\n ReportProgress,\n refusals,\n} from '../../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../../app/container.js';\nimport { actorOf } from './actor.js';\n\nexport async function memberProgressRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const member = container.reporting.member;\n\n r.route({\n method: 'POST',\n url: '/api/member/progress',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reportProgress',\n tags: ['Progress'],\n summary: 'Report usage on an activity; the progress service decides completion',\n description:\n 'For a member who can open the course. Reports facts, never a decision: the service decides whether the activity is complete. 404 when the activity or its course is not published, or you cannot open it.',\n body: ReportProgress,\n response: { 200: ProgressStatus, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n const record = await container.progress.report(req.orgId, {\n orgUserId: orgUser.id,\n activityId: req.body.activity,\n reports: req.body.reports,\n });\n return { status: record.completedAt ? ('completed' as const) : ('in-progress' as const) };\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/courses/:courseId/progress',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCourseProgress',\n tags: ['Progress'],\n summary: 'Your progress in one course',\n description:\n \"For a member of the organization, derived from the course's current structure. 404 when the course is not published or you do not hold it.\",\n params: MemberCourseIdParam,\n response: { 200: CourseProgress, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n const view = await member.courseProgress(req.orgId, orgUser.id, req.params.courseId);\n if (!view) {\n throw new NotFoundError('Course', req.params.courseId);\n }\n return view;\n },\n });\n}\n"],"mappings":";;;;;;AAaA,eAAsB,qBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,SAAS,UAAU,UAAU;CAEnC,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,UAAU;GACjB,SAAS;GACT,aACE;GACF,MAAM;GACN,UAAU;IAAE,KAAK;IAAgB,GAAG,SAAS,KAAK,GAAG;GAAE;EACzD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAM9D,OAAO,EAAE,SAAQ,MALI,UAAU,SAAS,OAAO,IAAI,OAAO;IACxD,WAAW,QAAQ;IACnB,YAAY,IAAI,KAAK;IACrB,SAAS,IAAI,KAAK;GACpB,CAAC,EAAA,CACuB,cAAe,cAAyB,cAAwB;EAC1F;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,UAAU;GACjB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAAgB,GAAG,SAAS,KAAK,GAAG;GAAE;EACzD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,UAAU,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9D,MAAM,OAAO,MAAM,OAAO,eAAe,IAAI,OAAO,QAAQ,IAAI,IAAI,OAAO,QAAQ;GACnF,IAAI,CAAC,MACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,QAAQ;GAEvD,OAAO;EACT;CACF,CAAC;AACH"}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
+
import { refusals } from "../schemas/shared.js";
|
|
1
2
|
import { resolveScope } from "../scope.js";
|
|
2
|
-
import { ErrorBody } from "../schemas/shared.js";
|
|
3
3
|
import { CreateOrganization, Organization, UpdateOrganization } from "../schemas/organizations.js";
|
|
4
|
-
import {
|
|
4
|
+
import { MemberIdParam, MemberRoleChange, MembersPage, MembersQuery, UpdateMemberRole } from "../schemas/members.js";
|
|
5
5
|
import { AcceptInvite, AcceptInviteResult, CreateInvite, GetInviteResult, Invite, InviteTokenParam } from "../schemas/invites.js";
|
|
6
6
|
import "../schemas/index.js";
|
|
7
|
-
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
8
7
|
import { z } from "zod";
|
|
8
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
9
9
|
//#region src/http/routes/organizations.ts
|
|
10
10
|
async function organizationsRoutes(app, container) {
|
|
11
11
|
const r = app.withTypeProvider();
|
|
@@ -19,6 +19,7 @@ async function organizationsRoutes(app, container) {
|
|
|
19
19
|
operationId: "createOrganization",
|
|
20
20
|
tags,
|
|
21
21
|
summary: "Create an organization and make it active",
|
|
22
|
+
description: "For any signed-in person, including one with no organization yet. The caller becomes its owner.",
|
|
22
23
|
body: CreateOrganization,
|
|
23
24
|
response: { 201: Organization }
|
|
24
25
|
},
|
|
@@ -39,11 +40,12 @@ async function organizationsRoutes(app, container) {
|
|
|
39
40
|
schema: {
|
|
40
41
|
operationId: "updateOrganization",
|
|
41
42
|
tags,
|
|
42
|
-
summary: "Update the active organization",
|
|
43
|
+
summary: "Update the active organization (staff)",
|
|
44
|
+
description: "Owner or admin. Changes its name or slug; 409 when the slug is taken.",
|
|
43
45
|
body: UpdateOrganization,
|
|
44
46
|
response: {
|
|
45
47
|
200: Organization,
|
|
46
|
-
409
|
|
48
|
+
...refusals(403, 409)
|
|
47
49
|
}
|
|
48
50
|
},
|
|
49
51
|
handler: async (req, reply) => {
|
|
@@ -64,9 +66,13 @@ async function organizationsRoutes(app, container) {
|
|
|
64
66
|
schema: {
|
|
65
67
|
operationId: "listMembers",
|
|
66
68
|
tags,
|
|
67
|
-
summary: "List organization members",
|
|
69
|
+
summary: "List the organization's team members (staff)",
|
|
70
|
+
description: "Staff only (owner, admin, instructor). The team: everyone in the organization who is not a student.",
|
|
68
71
|
querystring: MembersQuery,
|
|
69
|
-
response: {
|
|
72
|
+
response: {
|
|
73
|
+
200: MembersPage,
|
|
74
|
+
...refusals(403)
|
|
75
|
+
}
|
|
70
76
|
},
|
|
71
77
|
handler: async (req) => {
|
|
72
78
|
const scope = await resolveScope(container, req);
|
|
@@ -80,13 +86,13 @@ async function organizationsRoutes(app, container) {
|
|
|
80
86
|
schema: {
|
|
81
87
|
operationId: "updateMemberRole",
|
|
82
88
|
tags,
|
|
83
|
-
summary: "Change a member's role",
|
|
89
|
+
summary: "Change a member's role (staff)",
|
|
90
|
+
description: "Owner or admin only. The owner role can be neither granted nor changed (409), and only a moderator can become a student.",
|
|
84
91
|
params: MemberIdParam,
|
|
85
92
|
body: UpdateMemberRole,
|
|
86
93
|
response: {
|
|
87
|
-
200:
|
|
88
|
-
404
|
|
89
|
-
409: ErrorBody
|
|
94
|
+
200: MemberRoleChange,
|
|
95
|
+
...refusals(403, 404, 409)
|
|
90
96
|
}
|
|
91
97
|
},
|
|
92
98
|
handler: async (req) => {
|
|
@@ -96,7 +102,10 @@ async function organizationsRoutes(app, container) {
|
|
|
96
102
|
authOrgId: scope.authOrgId,
|
|
97
103
|
headers: req.headers
|
|
98
104
|
};
|
|
99
|
-
const member = await organizations.updateMemberRole(ctx,
|
|
105
|
+
const member = await organizations.updateMemberRole(ctx, {
|
|
106
|
+
id: scope.orgUserId,
|
|
107
|
+
role: scope.role
|
|
108
|
+
}, req.params.id, req.body.role);
|
|
100
109
|
if (!member) throw new NotFoundError("Member", req.params.id);
|
|
101
110
|
return member;
|
|
102
111
|
}
|
|
@@ -108,12 +117,12 @@ async function organizationsRoutes(app, container) {
|
|
|
108
117
|
schema: {
|
|
109
118
|
operationId: "removeMember",
|
|
110
119
|
tags,
|
|
111
|
-
summary: "Remove an organization member",
|
|
120
|
+
summary: "Remove an organization member (staff)",
|
|
121
|
+
description: "Owner or admin only. Removing a pending member cancels their invite. The owner cannot be removed (409).",
|
|
112
122
|
params: MemberIdParam,
|
|
113
123
|
response: {
|
|
114
124
|
204: z.void(),
|
|
115
|
-
404
|
|
116
|
-
409: ErrorBody
|
|
125
|
+
...refusals(403, 404, 409)
|
|
117
126
|
}
|
|
118
127
|
},
|
|
119
128
|
handler: async (req, reply) => {
|
|
@@ -123,7 +132,10 @@ async function organizationsRoutes(app, container) {
|
|
|
123
132
|
authOrgId: scope.authOrgId,
|
|
124
133
|
headers: req.headers
|
|
125
134
|
};
|
|
126
|
-
if (!await organizations.removeMember(ctx,
|
|
135
|
+
if (!await organizations.removeMember(ctx, {
|
|
136
|
+
id: scope.orgUserId,
|
|
137
|
+
role: scope.role
|
|
138
|
+
}, req.params.id)) throw new NotFoundError("Member", req.params.id);
|
|
127
139
|
return reply.code(204).send();
|
|
128
140
|
}
|
|
129
141
|
});
|
|
@@ -134,16 +146,20 @@ async function organizationsRoutes(app, container) {
|
|
|
134
146
|
schema: {
|
|
135
147
|
operationId: "createInvite",
|
|
136
148
|
tags,
|
|
137
|
-
summary: "Invite a member or student into the active organization",
|
|
149
|
+
summary: "Invite a member or student into the active organization (staff)",
|
|
150
|
+
description: "Owner or admin only. A student is created by this call, so their access can be granted before they accept. 409 when a student with that email is already in the organization.",
|
|
138
151
|
body: CreateInvite,
|
|
139
152
|
response: {
|
|
140
153
|
201: Invite,
|
|
141
|
-
409
|
|
154
|
+
...refusals(403, 409)
|
|
142
155
|
}
|
|
143
156
|
},
|
|
144
|
-
handler: async (req) => {
|
|
157
|
+
handler: async (req, reply) => {
|
|
145
158
|
const scope = await resolveScope(container, req);
|
|
146
159
|
const invite = await organizations.createInvite({
|
|
160
|
+
id: scope.orgUserId,
|
|
161
|
+
role: scope.role
|
|
162
|
+
}, {
|
|
147
163
|
orgId: scope.orgId,
|
|
148
164
|
inviterUserId: scope.userId,
|
|
149
165
|
email: req.body.email,
|
|
@@ -152,8 +168,15 @@ async function organizationsRoutes(app, container) {
|
|
|
152
168
|
...req.body.lastName !== void 0 && { lastName: req.body.lastName },
|
|
153
169
|
sendEmail: req.body.sendEmail
|
|
154
170
|
});
|
|
155
|
-
req.log.debug({ invite }, "created invite");
|
|
156
|
-
return
|
|
171
|
+
req.log.debug({ inviteId: invite.id }, "created invite");
|
|
172
|
+
return reply.code(201).send({
|
|
173
|
+
id: invite.id,
|
|
174
|
+
email: invite.email,
|
|
175
|
+
role: invite.role,
|
|
176
|
+
status: invite.status,
|
|
177
|
+
expiresAt: invite.expiresAt,
|
|
178
|
+
createdAt: invite.createdAt
|
|
179
|
+
});
|
|
157
180
|
}
|
|
158
181
|
});
|
|
159
182
|
r.route({
|
|
@@ -163,10 +186,11 @@ async function organizationsRoutes(app, container) {
|
|
|
163
186
|
operationId: "getInvite",
|
|
164
187
|
tags,
|
|
165
188
|
summary: "Read an invite by its token",
|
|
189
|
+
description: "Public: no session needed. The same 404 answers forged, expired and accepted tokens, so a probe learns nothing.",
|
|
166
190
|
params: InviteTokenParam,
|
|
167
191
|
response: {
|
|
168
192
|
200: GetInviteResult,
|
|
169
|
-
404
|
|
193
|
+
...refusals(404)
|
|
170
194
|
}
|
|
171
195
|
},
|
|
172
196
|
handler: async (req) => {
|
|
@@ -187,11 +211,12 @@ async function organizationsRoutes(app, container) {
|
|
|
187
211
|
schema: {
|
|
188
212
|
operationId: "acceptInvite",
|
|
189
213
|
tags,
|
|
190
|
-
summary: "Accept an invite with the
|
|
214
|
+
summary: "Accept an invite with the signed-in account",
|
|
215
|
+
description: "For any signed-in person whose email matches the invitation. Makes the invite's organization the session's active one. 400 when the token is invalid or expired, or the email does not match.",
|
|
191
216
|
body: AcceptInvite,
|
|
192
217
|
response: {
|
|
193
218
|
200: AcceptInviteResult,
|
|
194
|
-
400
|
|
219
|
+
...refusals(400, 404)
|
|
195
220
|
}
|
|
196
221
|
},
|
|
197
222
|
handler: async (req) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"organizations.js","names":[],"sources":["../../../src/http/routes/organizations.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n AcceptInvite,\n AcceptInviteResult,\n CreateInvite,\n CreateOrganization,\n ErrorBody,\n Invite,\n Member,\n MemberIdParam,\n MembersPage,\n MembersQuery,\n GetInviteResult,\n InviteTokenParam,\n Organization,\n UpdateMemberRole,\n UpdateOrganization,\n} from '../schemas/index.js';\nimport type { MemberWriteContext } from '@headless-lms/core/organizations';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function organizationsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const organizations = container.organizations;\n const tags = ['Organizations'];\n\n // Create a new organization on the caller's behalf and make it their active\n // org. This is the API's own front door for org creation — it drives Better\n // Auth internally, so callers use the typed SDK, not the auth namespace. No\n // resolveScope here: the caller has no active org yet, only a session.\n r.route({\n method: 'POST',\n url: '/api/organizations',\n // Session only: a first-time signup has no active org yet, so requiring one\n // here would 401 the very call that produces it.\n preHandler: app.requireSession,\n schema: {\n operationId: 'createOrganization',\n tags,\n summary: 'Create an organization and make it active',\n body: CreateOrganization,\n response: { 201: Organization },\n },\n handler: async (req, reply) => {\n const org = await container.orgProvider.createOrganization(req.headers, req.body);\n return reply.code(201).send({\n name: org.name,\n slug: org.slug,\n id: org.id,\n createdAt: org.createdAt.toISOString(),\n });\n },\n });\n\n // Update the caller's active organization (name/slug). Writes go through Better\n // Auth, which enforces the caller's org-update permission (owner/admin).\n r.route({\n method: 'PATCH',\n url: '/api/organizations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateOrganization',\n tags,\n summary: 'Update the active organization',\n body: UpdateOrganization,\n response: { 200: Organization, 409: ErrorBody },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const org = await container.orgProvider.updateOrganization(\n req.headers,\n scope.authOrgId,\n req.body,\n );\n return reply.send({\n id: org.id,\n name: org.name,\n slug: org.slug,\n createdAt: org.createdAt.toISOString(),\n });\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/organizations/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMembers',\n tags,\n summary: 'List organization members',\n querystring: MembersQuery,\n response: { 200: MembersPage },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return organizations.listMembers(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/organizations/members/:id/role',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateMemberRole',\n tags,\n summary: \"Change a member's role\",\n params: MemberIdParam,\n body: UpdateMemberRole,\n response: { 200: Member, 404: ErrorBody, 409: ErrorBody },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const ctx: MemberWriteContext = {\n orgId: scope.orgId,\n authOrgId: scope.authOrgId,\n headers: req.headers,\n };\n const member = await organizations.updateMemberRole(ctx, req.params.id, req.body.role);\n if (!member) {\n throw new NotFoundError('Member', req.params.id);\n }\n return member;\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/organizations/members/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeMember',\n tags,\n summary: 'Remove an organization member',\n params: MemberIdParam,\n response: { 204: z.void(), 404: ErrorBody, 409: ErrorBody },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const ctx: MemberWriteContext = {\n orgId: scope.orgId,\n authOrgId: scope.authOrgId,\n headers: req.headers,\n };\n const removed = await organizations.removeMember(ctx, req.params.id);\n if (!removed) {\n throw new NotFoundError('Member', req.params.id);\n }\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/organizations/invites',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createInvite',\n tags,\n summary: 'Invite a member or student into the active organization',\n body: CreateInvite,\n response: { 201: Invite, 409: ErrorBody },\n },\n handler: async (req) => {\n // Domain ids, not the session's auth ids: invites FK to organizations.id\n // and users.id.\n const scope = await resolveScope(container, req);\n const invite = await organizations.createInvite({\n orgId: scope.orgId,\n inviterUserId: scope.userId,\n email: req.body.email,\n role: req.body.role,\n ...(req.body.firstName !== undefined && { firstName: req.body.firstName }),\n ...(req.body.lastName !== undefined && { lastName: req.body.lastName }),\n sendEmail: req.body.sendEmail,\n });\n\n req.log.debug({ invite }, 'created invite');\n return invite;\n },\n });\n\n // The invite landing page's first call, made before the invitee has any\n // session — so no guard. It turns the emailed token into the two facts the\n // page needs: who the invitation is for, and whether that address already has\n // an account. The email comes from the invitation row rather than the URL, so\n // it cannot be steered by editing the link.\n r.route({\n method: 'GET',\n url: '/api/organizations/invites/:token',\n schema: {\n operationId: 'getInvite',\n tags,\n summary: 'Read an invite by its token',\n params: InviteTokenParam,\n response: { 200: GetInviteResult, 404: ErrorBody },\n },\n handler: async (req) => {\n const invite = await organizations.peekInvite(req.params.token);\n if (!invite) {\n // Same answer for forged, expired, and already-accepted tokens: a\n // probe learns nothing about which invitations exist.\n throw new NotFoundError('Invite', 'token');\n }\n // Whether the address already has a login is deliberately not answered\n // here. The mirror cannot say — an admin's student is a person long\n // before they are an account — and asking the auth engine would mean\n // reading its tables for one boolean. The sign-up form discovers it\n // instead: a duplicate signup comes back 422 and the page flips to\n // sign-in.\n const person = await container.identity.getUserByEmail(invite.email);\n return {\n email: invite.email,\n firstName: person?.firstName ?? null,\n lastName: person?.lastName ?? null,\n };\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/organizations/invites/accept',\n preHandler: app.requireSession,\n schema: {\n operationId: 'acceptInvite',\n tags,\n summary: 'Accept an invite with the logged-in account',\n body: AcceptInvite,\n response: { 200: AcceptInviteResult, 400: ErrorBody },\n },\n handler: async (req) => {\n // No active org yet, so no resolveScope — but acceptInvite links a domain\n // users.id, never the auth account id.\n const user = await container.identity.getUserByExternalId(req.authUser.id);\n if (!user) {\n throw new NotFoundError('User', req.authUser.id);\n }\n const accepted = await container.organizations.acceptInvite({\n token: req.body.token,\n userId: user.id,\n email: req.authUser.email,\n });\n\n // The session predates this grant, so it carries no active org and every\n // org-scoped read would 401. Students are never org members, so the org\n // plugin's set-active cannot do it — stamp the session row directly.\n const org = await container.organizations.getById(accepted.orgId);\n if (!org) {\n throw new NotFoundError('Organization', accepted.orgId);\n }\n const stamped = await container.sessions.stampActiveOrganization(req.headers, org.id);\n\n req.log.info({ accepted, stamped }, 'accepted invite');\n return {};\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"organizations.js","names":[],"sources":["../../../src/http/routes/organizations.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n AcceptInvite,\n AcceptInviteResult,\n CreateInvite,\n CreateOrganization,\n Invite,\n MemberIdParam,\n MemberRoleChange,\n MembersPage,\n MembersQuery,\n GetInviteResult,\n InviteTokenParam,\n Organization,\n UpdateMemberRole,\n UpdateOrganization,\n refusals,\n} from '../schemas/index.js';\nimport type { MemberWriteContext } from '@headless-lms/core/organizations';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function organizationsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const organizations = container.organizations;\n const tags = ['Organizations'];\n\n // Create a new organization on the caller's behalf and make it their active\n // org. This is the API's own front door for org creation — it drives Better\n // Auth internally, so callers use the typed SDK, not the auth namespace. No\n // resolveScope here: the caller has no active org yet, only a session.\n r.route({\n method: 'POST',\n url: '/api/organizations',\n // Session only: a first-time signup has no active org yet, so requiring one\n // here would 401 the very call that produces it.\n preHandler: app.requireSession,\n schema: {\n operationId: 'createOrganization',\n tags,\n summary: 'Create an organization and make it active',\n description:\n 'For any signed-in person, including one with no organization yet. The caller becomes its owner.',\n body: CreateOrganization,\n response: { 201: Organization },\n },\n handler: async (req, reply) => {\n const org = await container.orgProvider.createOrganization(req.headers, req.body);\n return reply.code(201).send({\n name: org.name,\n slug: org.slug,\n id: org.id,\n createdAt: org.createdAt.toISOString(),\n });\n },\n });\n\n // Update the caller's active organization (name/slug). Writes go through Better\n // Auth, which enforces the caller's org-update permission (owner/admin).\n r.route({\n method: 'PATCH',\n url: '/api/organizations',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateOrganization',\n tags,\n summary: 'Update the active organization (staff)',\n description: 'Owner or admin. Changes its name or slug; 409 when the slug is taken.',\n body: UpdateOrganization,\n response: { 200: Organization, ...refusals(403, 409) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const org = await container.orgProvider.updateOrganization(\n req.headers,\n scope.authOrgId,\n req.body,\n );\n return reply.send({\n id: org.id,\n name: org.name,\n slug: org.slug,\n createdAt: org.createdAt.toISOString(),\n });\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/organizations/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMembers',\n tags,\n summary: \"List the organization's team members (staff)\",\n description:\n 'Staff only (owner, admin, instructor). The team: everyone in the organization who is not a student.',\n querystring: MembersQuery,\n response: { 200: MembersPage, ...refusals(403) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return organizations.listMembers(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/organizations/members/:id/role',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updateMemberRole',\n tags,\n summary: \"Change a member's role (staff)\",\n description:\n 'Owner or admin only. The owner role can be neither granted nor changed (409), and only a moderator can become a student.',\n params: MemberIdParam,\n body: UpdateMemberRole,\n response: { 200: MemberRoleChange, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const ctx: MemberWriteContext = {\n orgId: scope.orgId,\n authOrgId: scope.authOrgId,\n headers: req.headers,\n };\n const member = await organizations.updateMemberRole(\n ctx,\n { id: scope.orgUserId, role: scope.role },\n req.params.id,\n req.body.role,\n );\n if (!member) {\n throw new NotFoundError('Member', req.params.id);\n }\n return member;\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/organizations/members/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeMember',\n tags,\n summary: 'Remove an organization member (staff)',\n description:\n 'Owner or admin only. Removing a pending member cancels their invite. The owner cannot be removed (409).',\n params: MemberIdParam,\n response: { 204: z.void(), ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const ctx: MemberWriteContext = {\n orgId: scope.orgId,\n authOrgId: scope.authOrgId,\n headers: req.headers,\n };\n const removed = await organizations.removeMember(\n ctx,\n { id: scope.orgUserId, role: scope.role },\n req.params.id,\n );\n if (!removed) {\n throw new NotFoundError('Member', req.params.id);\n }\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/organizations/invites',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createInvite',\n tags,\n summary: 'Invite a member or student into the active organization (staff)',\n description:\n 'Owner or admin only. A student is created by this call, so their access can be granted before they accept. 409 when a student with that email is already in the organization.',\n body: CreateInvite,\n response: { 201: Invite, ...refusals(403, 409) },\n },\n handler: async (req, reply) => {\n // Domain ids, not the session's auth ids: invites FK to organizations.id\n // and users.id.\n const scope = await resolveScope(container, req);\n const invite = await organizations.createInvite(\n { id: scope.orgUserId, role: scope.role },\n {\n orgId: scope.orgId,\n inviterUserId: scope.userId,\n email: req.body.email,\n role: req.body.role,\n ...(req.body.firstName !== undefined && { firstName: req.body.firstName }),\n ...(req.body.lastName !== undefined && { lastName: req.body.lastName }),\n sendEmail: req.body.sendEmail,\n },\n );\n\n req.log.debug({ inviteId: invite.id }, 'created invite');\n return reply.code(201).send({\n id: invite.id,\n email: invite.email,\n role: invite.role,\n status: invite.status,\n expiresAt: invite.expiresAt,\n createdAt: invite.createdAt,\n });\n },\n });\n\n // The invite landing page's first call, made before the invitee has any\n // session — so no guard. It turns the emailed token into the two facts the\n // page needs: who the invitation is for, and whether that address already has\n // an account. The email comes from the invitation row rather than the URL, so\n // it cannot be steered by editing the link.\n r.route({\n method: 'GET',\n url: '/api/organizations/invites/:token',\n schema: {\n operationId: 'getInvite',\n tags,\n summary: 'Read an invite by its token',\n description:\n 'Public: no session needed. The same 404 answers forged, expired and accepted tokens, so a probe learns nothing.',\n params: InviteTokenParam,\n response: { 200: GetInviteResult, ...refusals(404) },\n },\n handler: async (req) => {\n const invite = await organizations.peekInvite(req.params.token);\n if (!invite) {\n // Same answer for forged, expired, and already-accepted tokens: a\n // probe learns nothing about which invitations exist.\n throw new NotFoundError('Invite', 'token');\n }\n // Whether the address already has a login is deliberately not answered\n // here. The mirror cannot say — an admin's student is a person long\n // before they are an account — and asking the auth engine would mean\n // reading its tables for one boolean. The sign-up form discovers it\n // instead: a duplicate signup comes back 422 and the page flips to\n // sign-in.\n const person = await container.identity.getUserByEmail(invite.email);\n return {\n email: invite.email,\n firstName: person?.firstName ?? null,\n lastName: person?.lastName ?? null,\n };\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/organizations/invites/accept',\n preHandler: app.requireSession,\n schema: {\n operationId: 'acceptInvite',\n tags,\n summary: 'Accept an invite with the signed-in account',\n description:\n \"For any signed-in person whose email matches the invitation. Makes the invite's organization the session's active one. 400 when the token is invalid or expired, or the email does not match.\",\n body: AcceptInvite,\n response: { 200: AcceptInviteResult, ...refusals(400, 404) },\n },\n handler: async (req) => {\n // No active org yet, so no resolveScope — but acceptInvite links a domain\n // users.id, never the auth account id.\n const user = await container.identity.getUserByExternalId(req.authUser.id);\n if (!user) {\n throw new NotFoundError('User', req.authUser.id);\n }\n const accepted = await container.organizations.acceptInvite({\n token: req.body.token,\n userId: user.id,\n email: req.authUser.email,\n });\n\n // The session predates this grant, so it carries no active org and every\n // org-scoped read would 401. Students are never org members, so the org\n // plugin's set-active cannot do it — stamp the session row directly.\n const org = await container.organizations.getById(accepted.orgId);\n if (!org) {\n throw new NotFoundError('Organization', accepted.orgId);\n }\n const stamped = await container.sessions.stampActiveOrganization(req.headers, org.id);\n\n req.log.info({ accepted, stamped }, 'accepted invite');\n return {};\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import { refusals } from "../schemas/shared.js";
|
|
1
2
|
import { resolveScope } from "../scope.js";
|
|
2
|
-
import {
|
|
3
|
-
import { Student, StudentAnalytics, StudentIdParam, StudentsPage, StudentsQuery, UpdateStudentBody } from "../schemas/students.js";
|
|
3
|
+
import { Student, StudentAnalytics, StudentIdParam, StudentsPage, StudentsQuery } from "../schemas/students.js";
|
|
4
4
|
import "../schemas/index.js";
|
|
5
|
-
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
6
5
|
import { z } from "zod";
|
|
6
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
7
7
|
//#region src/http/routes/students.ts
|
|
8
8
|
async function studentsRoutes(app, container) {
|
|
9
9
|
const r = app.withTypeProvider();
|
|
10
|
-
const students = container.reporting.
|
|
10
|
+
const students = container.reporting.admin;
|
|
11
11
|
r.route({
|
|
12
12
|
method: "GET",
|
|
13
13
|
url: "/api/students",
|
|
@@ -15,13 +15,17 @@ async function studentsRoutes(app, container) {
|
|
|
15
15
|
schema: {
|
|
16
16
|
operationId: "listStudents",
|
|
17
17
|
tags: ["Organizations"],
|
|
18
|
-
summary: "List students",
|
|
18
|
+
summary: "List students (staff)",
|
|
19
|
+
description: "Staff only (owner, admin, instructor).",
|
|
19
20
|
querystring: StudentsQuery,
|
|
20
|
-
response: {
|
|
21
|
+
response: {
|
|
22
|
+
200: StudentsPage,
|
|
23
|
+
...refusals(403)
|
|
24
|
+
}
|
|
21
25
|
},
|
|
22
26
|
handler: async (req) => {
|
|
23
27
|
const scope = await resolveScope(container, req);
|
|
24
|
-
return students.
|
|
28
|
+
return students.listStudents(scope.orgId, req.query);
|
|
25
29
|
}
|
|
26
30
|
});
|
|
27
31
|
r.route({
|
|
@@ -31,16 +35,17 @@ async function studentsRoutes(app, container) {
|
|
|
31
35
|
schema: {
|
|
32
36
|
operationId: "getStudent",
|
|
33
37
|
tags: ["Organizations"],
|
|
34
|
-
summary: "Get a student
|
|
38
|
+
summary: "Get a student (staff)",
|
|
39
|
+
description: "Staff only (owner, admin, instructor).",
|
|
35
40
|
params: StudentIdParam,
|
|
36
41
|
response: {
|
|
37
42
|
200: Student,
|
|
38
|
-
404
|
|
43
|
+
...refusals(403, 404)
|
|
39
44
|
}
|
|
40
45
|
},
|
|
41
46
|
handler: async (req) => {
|
|
42
47
|
const scope = await resolveScope(container, req);
|
|
43
|
-
const student = await students.
|
|
48
|
+
const student = await students.getStudent(scope.orgId, req.params.id);
|
|
44
49
|
if (!student) throw new NotFoundError("Student", req.params.id);
|
|
45
50
|
return student;
|
|
46
51
|
}
|
|
@@ -52,48 +57,21 @@ async function studentsRoutes(app, container) {
|
|
|
52
57
|
schema: {
|
|
53
58
|
operationId: "getStudentAnalytics",
|
|
54
59
|
tags: ["Reporting"],
|
|
55
|
-
summary: "A student's learner record
|
|
60
|
+
summary: "A student's learner record (staff)",
|
|
61
|
+
description: "Staff only (owner, admin, instructor). Per-course progress and completion.",
|
|
56
62
|
params: StudentIdParam,
|
|
57
63
|
response: {
|
|
58
64
|
200: StudentAnalytics,
|
|
59
|
-
404
|
|
65
|
+
...refusals(403, 404)
|
|
60
66
|
}
|
|
61
67
|
},
|
|
62
68
|
handler: async (req) => {
|
|
63
69
|
const scope = await resolveScope(container, req);
|
|
64
|
-
const analytics = await students.
|
|
70
|
+
const analytics = await students.studentAnalytics(scope.orgId, req.params.id);
|
|
65
71
|
if (!analytics) throw new NotFoundError("Student", req.params.id);
|
|
66
72
|
return analytics;
|
|
67
73
|
}
|
|
68
74
|
});
|
|
69
|
-
r.route({
|
|
70
|
-
method: "PATCH",
|
|
71
|
-
url: "/api/students/:id",
|
|
72
|
-
preHandler: app.requireOrgSession,
|
|
73
|
-
schema: {
|
|
74
|
-
operationId: "updateStudent",
|
|
75
|
-
tags: ["Organizations"],
|
|
76
|
-
summary: "Update a student's profile",
|
|
77
|
-
params: StudentIdParam,
|
|
78
|
-
body: UpdateStudentBody,
|
|
79
|
-
response: {
|
|
80
|
-
200: Student,
|
|
81
|
-
404: ErrorBody,
|
|
82
|
-
409: ErrorBody
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
handler: async (req) => {
|
|
86
|
-
const scope = await resolveScope(container, req);
|
|
87
|
-
await container.organizations.updateStudent({
|
|
88
|
-
orgId: scope.orgId,
|
|
89
|
-
orgUserId: req.params.id,
|
|
90
|
-
...req.body
|
|
91
|
-
});
|
|
92
|
-
const student = await students.get(scope.orgId, req.params.id);
|
|
93
|
-
if (!student) throw new NotFoundError("Student", req.params.id);
|
|
94
|
-
return student;
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
75
|
r.route({
|
|
98
76
|
method: "POST",
|
|
99
77
|
url: "/api/students/:id/invite/resend",
|
|
@@ -101,17 +79,20 @@ async function studentsRoutes(app, container) {
|
|
|
101
79
|
schema: {
|
|
102
80
|
operationId: "resendStudentInvite",
|
|
103
81
|
tags: ["Organizations"],
|
|
104
|
-
summary: "Re-send a pending student's invite email",
|
|
82
|
+
summary: "Re-send a pending student's invite email (staff)",
|
|
83
|
+
description: "Owner or admin only. Issues a new token, so the previous link stops working. 404 when the student has no pending invite; 409 when it was already accepted.",
|
|
105
84
|
params: StudentIdParam,
|
|
106
85
|
response: {
|
|
107
86
|
204: z.void(),
|
|
108
|
-
404
|
|
109
|
-
409: ErrorBody
|
|
87
|
+
...refusals(403, 404, 409)
|
|
110
88
|
}
|
|
111
89
|
},
|
|
112
90
|
handler: async (req, reply) => {
|
|
113
91
|
const scope = await resolveScope(container, req);
|
|
114
92
|
await container.organizations.resendStudentInvite({
|
|
93
|
+
id: scope.orgUserId,
|
|
94
|
+
role: scope.role
|
|
95
|
+
}, {
|
|
115
96
|
orgId: scope.orgId,
|
|
116
97
|
orgUserId: req.params.id,
|
|
117
98
|
inviterUserId: scope.userId
|
|
@@ -126,16 +107,20 @@ async function studentsRoutes(app, container) {
|
|
|
126
107
|
schema: {
|
|
127
108
|
operationId: "deleteStudent",
|
|
128
109
|
tags: ["Organizations"],
|
|
129
|
-
summary: "Delete a student",
|
|
110
|
+
summary: "Delete a student (staff)",
|
|
111
|
+
description: "Owner or admin only. 409 when the person has written posts, comments or messages.",
|
|
130
112
|
params: StudentIdParam,
|
|
131
113
|
response: {
|
|
132
114
|
204: z.void(),
|
|
133
|
-
404
|
|
115
|
+
...refusals(403, 404, 409)
|
|
134
116
|
}
|
|
135
117
|
},
|
|
136
118
|
handler: async (req, reply) => {
|
|
137
119
|
const scope = await resolveScope(container, req);
|
|
138
|
-
await container.organizations.deleteOrgUser(scope.orgId,
|
|
120
|
+
await container.organizations.deleteOrgUser(scope.orgId, {
|
|
121
|
+
id: scope.orgUserId,
|
|
122
|
+
role: scope.role
|
|
123
|
+
}, req.params.id);
|
|
139
124
|
return reply.code(204).send();
|
|
140
125
|
}
|
|
141
126
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"students.js","names":[],"sources":["../../../src/http/routes/students.ts"],"sourcesContent":["// HTTP routes for students: the reporting-backed list/read endpoints, the\n// delete, and re-issuing a pending invite. There is no create here — adding a\n// student IS inviting one, at /api/organizations/invites, which provisions the\n// student record up front.\nimport type { FastifyInstance } from 'fastify';\nimport { z } from 'zod';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n
|
|
1
|
+
{"version":3,"file":"students.js","names":[],"sources":["../../../src/http/routes/students.ts"],"sourcesContent":["// HTTP routes for students: the reporting-backed list/read endpoints, the\n// delete, and re-issuing a pending invite. There is no create here — adding a\n// student IS inviting one, at /api/organizations/invites, which provisions the\n// student record up front.\nimport type { FastifyInstance } from 'fastify';\nimport { z } from 'zod';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n Student,\n StudentAnalytics,\n StudentIdParam,\n StudentsPage,\n StudentsQuery,\n refusals,\n} from '../schemas/index.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport type { Container } from '../../app/container.js';\nimport { resolveScope } from '../scope.js';\n\nexport async function studentsRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const students = container.reporting.admin;\n\n r.route({\n method: 'GET',\n url: '/api/students',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listStudents',\n tags: ['Organizations'],\n summary: 'List students (staff)',\n description: 'Staff only (owner, admin, instructor).',\n querystring: StudentsQuery,\n response: { 200: StudentsPage, ...refusals(403) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return students.listStudents(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/students/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getStudent',\n tags: ['Organizations'],\n summary: 'Get a student (staff)',\n description: 'Staff only (owner, admin, instructor).',\n params: StudentIdParam,\n response: { 200: Student, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const student = await students.getStudent(scope.orgId, req.params.id);\n if (!student) {\n throw new NotFoundError('Student', req.params.id);\n }\n return student;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/students/:id/analytics',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getStudentAnalytics',\n tags: ['Reporting'],\n summary: \"A student's learner record (staff)\",\n description: 'Staff only (owner, admin, instructor). Per-course progress and completion.',\n params: StudentIdParam,\n response: { 200: StudentAnalytics, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const analytics = await students.studentAnalytics(scope.orgId, req.params.id);\n if (!analytics) {\n throw new NotFoundError('Student', req.params.id);\n }\n return analytics;\n },\n });\n\n // Re-mints the token on the student's pending invite and emails it. Lives\n // here rather than under invites because the admin app knows the student,\n // not the invitation.\n r.route({\n method: 'POST',\n url: '/api/students/:id/invite/resend',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resendStudentInvite',\n tags: ['Organizations'],\n summary: \"Re-send a pending student's invite email (staff)\",\n description:\n 'Owner or admin only. Issues a new token, so the previous link stops working. 404 when the student has no pending invite; 409 when it was already accepted.',\n params: StudentIdParam,\n response: { 204: z.void(), ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await container.organizations.resendStudentInvite(\n { id: scope.orgUserId, role: scope.role },\n {\n orgId: scope.orgId,\n orgUserId: req.params.id,\n inviterUserId: scope.userId,\n },\n );\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/students/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteStudent',\n tags: ['Organizations'],\n summary: 'Delete a student (staff)',\n description:\n 'Owner or admin only. 409 when the person has written posts, comments or messages.',\n params: StudentIdParam,\n response: { 204: z.void(), ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await container.organizations.deleteOrgUser(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.id,\n );\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":";;;;;;;AAmBA,eAAsB,eAAe,KAAsB,WAAqC;CAC9F,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,WAAW,UAAU,UAAU;CAErC,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,eAAe;GACtB,SAAS;GACT,aAAa;GACb,aAAa;GACb,UAAU;IAAE,KAAK;IAAc,GAAG,SAAS,GAAG;GAAE;EAClD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,OAAO,SAAS,aAAa,MAAM,OAAO,IAAI,KAAK;EACrD;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,eAAe;GACtB,SAAS;GACT,aAAa;GACb,QAAQ;GACR,UAAU;IAAE,KAAK;IAAS,GAAG,SAAS,KAAK,GAAG;GAAE;EAClD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,UAAU,MAAM,SAAS,WAAW,MAAM,OAAO,IAAI,OAAO,EAAE;GACpE,IAAI,CAAC,SACH,MAAM,IAAI,cAAc,WAAW,IAAI,OAAO,EAAE;GAElD,OAAO;EACT;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,WAAW;GAClB,SAAS;GACT,aAAa;GACb,QAAQ;GACR,UAAU;IAAE,KAAK;IAAkB,GAAG,SAAS,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,YAAY,MAAM,SAAS,iBAAiB,MAAM,OAAO,IAAI,OAAO,EAAE;GAC5E,IAAI,CAAC,WACH,MAAM,IAAI,cAAc,WAAW,IAAI,OAAO,EAAE;GAElD,OAAO;EACT;CACF,CAAC;CAKD,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,eAAe;GACtB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK,EAAE,KAAK;IAAG,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,UAAU,cAAc,oBAC5B;IAAE,IAAI,MAAM;IAAW,MAAM,MAAM;GAAK,GACxC;IACE,OAAO,MAAM;IACb,WAAW,IAAI,OAAO;IACtB,eAAe,MAAM;GACvB,CACF;GACA,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb,MAAM,CAAC,eAAe;GACtB,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK,EAAE,KAAK;IAAG,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,UAAU,cAAc,cAC5B,MAAM,OACN;IAAE,IAAI,MAAM;IAAW,MAAM,MAAM;GAAK,GACxC,IAAI,OAAO,EACb;GACA,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;AACH"}
|