@headless-lms/server 0.2.0 → 0.4.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 +24 -0
- package/dist/app/community-mail.js +92 -0
- package/dist/app/community-mail.js.map +1 -0
- package/dist/app/container.d.ts +40 -19
- package/dist/app/container.js +294 -45
- 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/app/notification-channels.d.ts +47 -0
- package/dist/app/notification-channels.js +174 -0
- package/dist/app/notification-channels.js.map +1 -0
- package/dist/app/notification-reads.d.ts +23 -0
- package/dist/app/notification-reads.js +95 -0
- package/dist/app/notification-reads.js.map +1 -0
- package/dist/http/config.d.ts +2 -1
- package/dist/http/errors.d.ts +15 -0
- package/dist/http/errors.js +162 -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 +51 -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 +522 -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 +435 -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 +312 -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/devices.d.ts +7 -0
- package/dist/http/routes/member/devices.js +98 -0
- package/dist/http/routes/member/devices.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 +35 -0
- package/dist/http/routes/member/index.js.map +1 -0
- package/dist/http/routes/member/notification-settings.d.ts +7 -0
- package/dist/http/routes/member/notification-settings.js +108 -0
- package/dist/http/routes/member/notification-settings.js.map +1 -0
- package/dist/http/routes/member/notifications.d.ts +7 -0
- package/dist/http/routes/member/notifications.js +125 -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 +171 -25
- 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 +79 -80
- package/dist/http/schemas/assets.js +23 -13
- package/dist/http/schemas/assets.js.map +1 -1
- package/dist/http/schemas/automations.d.ts +6 -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 +409 -0
- package/dist/http/schemas/chats.js +56 -0
- package/dist/http/schemas/chats.js.map +1 -0
- package/dist/http/schemas/community.d.ts +597 -0
- package/dist/http/schemas/community.js +132 -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 +98 -14
- package/dist/http/schemas/discussion.js +25 -12
- 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 +4 -4
- 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 +35 -2
- package/dist/http/schemas/members.js +17 -3
- package/dist/http/schemas/members.js.map +1 -1
- package/dist/http/schemas/notifications.d.ts +550 -0
- package/dist/http/schemas/notifications.js +164 -0
- package/dist/http/schemas/notifications.js.map +1 -0
- package/dist/http/schemas/posts.d.ts +350 -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 +32 -1
- package/dist/http/schemas/shared.js +20 -2
- package/dist/http/schemas/shared.js.map +1 -1
- package/dist/http/schemas/students.d.ts +1 -7
- package/dist/http/schemas/students.js +1 -7
- package/dist/http/schemas/students.js.map +1 -1
- package/dist/http/scope.d.ts +3 -1
- package/dist/http/scope.js +26 -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 +3 -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 @@
|
|
|
1
|
+
{"version":3,"file":"posts.js","names":[],"sources":["../../../../src/http/routes/member/posts.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../../app/container.js';\nimport { actorOf } from './actor.js';\nimport {\n CommentView,\n CommunityParam,\n CommunitySpaceParam,\n CreatePost,\n FeedQuery,\n FlagReason,\n PageQuery,\n PostCommentsPage,\n PostComment,\n PostFeedPage,\n PostFlag,\n PostIdParam,\n PostView,\n Reactions,\n ResolvePostFlags,\n SchedulePost,\n SetPostReactions,\n SetPostSubscription,\n SetReaction,\n SpaceQueue,\n UpdatePost,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberPostRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const posts = container.posts;\n const tags = ['Posts'];\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/feed',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberFeed',\n tags,\n summary: 'Published posts across the spaces you can open',\n description:\n 'For a member of the community. Pinned posts first, then newest first. 404 when you are not a member.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.memberFeed(req.orgId, actor, req.params.communityId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/own-posts',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listOwnPosts',\n tags,\n summary: 'Your own posts in a community, newest first',\n description:\n 'For a member of the community. Your posts in any status, drafts included, in the spaces you can open. 404 when you are not a member.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.listOwn(req.orgId, actor, req.params.communityId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/bookmarks',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listBookmarkedPosts',\n tags,\n summary: 'The posts you bookmarked in a community, newest bookmark first',\n description:\n 'For a member of the community. Only posts you can still read. 404 when you are not a member.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.listBookmarked(req.orgId, actor, req.params.communityId, req.query);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId/feed',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getSpaceFeed',\n tags,\n summary: 'Published posts in a space',\n description:\n 'For anyone who can open the space (403 otherwise). Filter by `topicId`; an unknown topic is a 404.',\n params: CommunitySpaceParam,\n querystring: FeedQuery,\n response: { 200: PostFeedPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.spaceFeed(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.query,\n );\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId/queue',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getSpaceQueue',\n tags,\n summary: 'The moderation queue of a space you moderate',\n description:\n 'For a moderator who can open the space. Posts awaiting review, and the open flags on posts and on comments in the space. Each list holds at most the 100 oldest entries: pending posts by creation, flags by when they were raised; working the queue brings in the next ones.',\n params: CommunitySpaceParam,\n response: { 200: SpaceQueue, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.moderationQueue(req.orgId, actor, req.params.communityId, req.params.spaceId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/posts',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createPost',\n tags,\n summary: 'Create a post',\n description:\n 'For anyone who can open the space. `mode` decides the status: `draft`, `scheduled`, or on `submit` `published`, or `pending` when the space requires review and you are not staff. 409 when the space has topics and none is given. 400 when a schedule is missing or in the past. 503 when a scheduled post could not be timed; the post is then kept as a draft, and `details.postId` names it, so open that draft instead of creating the post again.',\n params: CommunitySpaceParam,\n body: CreatePost,\n response: { 201: PostView, ...refusals(400, 403, 404, 409, 503) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const post = await posts.create(req.orgId, actor, {\n ...req.body,\n communityId: req.params.communityId,\n spaceId: req.params.spaceId,\n });\n req.log.info({ postId: post.id, status: post.status }, 'post created');\n return reply.code(201).send(post);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/posts/:postId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getPost',\n tags,\n summary: 'Read a post',\n description:\n 'For anyone who can open the space while the post is published; for its author in any status; for moderators also while it is pending or removed. Any other post is a 404.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.getPost(req.orgId, actor, req.params.postId);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/posts/:postId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'updatePost',\n tags,\n summary: 'Edit your own post',\n description:\n 'Author only. Changes only the fields sent. 409 when the post is removed, or the topic rules of the space are not met.',\n params: PostIdParam,\n body: UpdatePost,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.update(req.orgId, req.params.postId, actor, req.body);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/submission',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'submitPost',\n tags,\n summary: 'Submit your draft or scheduled post',\n description:\n 'Author only. Publishes it now, or holds it for review when the space requires review and you are not staff. A scheduled post is no longer published at its time. 409 when the post is not a draft or scheduled.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.submit(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/schedule',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'schedulePost',\n tags,\n summary: 'Schedule your post',\n description:\n 'Author only. Schedules a draft, or moves a scheduled post to a new time; the post is submitted at `scheduledAt`. 409 when it is already submitted or removed, or the time is not in the future. 503 when the time could not be set; a draft stays a draft and a moved post keeps its previous time, and `details.postId` names the post.',\n params: PostIdParam,\n body: SchedulePost,\n response: { 200: PostView, ...refusals(403, 404, 409, 503) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.schedule(req.orgId, req.params.postId, actor, req.body.scheduledAt);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/unschedule',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unschedulePost',\n tags,\n summary: 'Unschedule your post',\n description:\n 'Author only. The scheduled post becomes a draft and is not submitted at its time. 409 when the post is not scheduled.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.unschedule(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/approval',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approvePost',\n tags,\n summary: 'Publish a post that awaits review in a space you moderate',\n description:\n 'For a moderator who can open the space. 409 when the post is not awaiting review.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.approve(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/removal',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removePost',\n tags,\n summary: 'Remove your post, or one in a space you moderate',\n description:\n \"For the author or a moderator. Takes a published or pending post down; a moderator removing someone else's post resolves its open flags as `removed`. 409 when it is already removed or was never submitted.\",\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.remove(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/restoration',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'restorePost',\n tags,\n summary: 'Restore a removed post in a space you moderate',\n description: 'For a moderator who can open the space. 409 when the post is not removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.restore(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/pin',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'pinPost',\n tags,\n summary: 'Pin a post in a space you moderate',\n description: 'For a moderator. Pinned posts lead the feeds. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.pin(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/unpin',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unpinPost',\n tags,\n summary: 'Unpin a post in a space you moderate',\n description: 'For a moderator. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.unpin(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/comments/close',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'closePostComments',\n tags,\n summary: 'Close comments on a post in a space you moderate',\n description:\n 'For a moderator. Nobody can comment until they are opened again. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.closeComments(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/comments/open',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'openPostComments',\n tags,\n summary: 'Open comments on a post in a space you moderate',\n description: 'For a moderator. 409 when the post is removed.',\n params: PostIdParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.openComments(req.orgId, req.params.postId, actor);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/posts/:postId/reactions',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setPostReactionPolicy',\n tags,\n summary: 'Turn reactions on or off for a post in a space you moderate',\n description:\n \"For a moderator. Overrides the space's reactions setting for this post. 409 when the post is removed.\",\n params: PostIdParam,\n body: SetPostReactions,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.setReactions(req.orgId, req.params.postId, actor, req.body.enabled);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/posts/:postId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setPostReaction',\n tags,\n summary: 'Set your reaction on a post',\n description:\n 'For anyone who can read the post, while reactions are on for it (403 otherwise). Replaces any reaction you already had and returns the new counts.',\n params: PostIdParam,\n body: SetReaction,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.setReaction(req.orgId, req.params.postId, actor, req.body.emoji);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/posts/:postId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearPostReaction',\n tags,\n summary: 'Withdraw your reaction from a post',\n description:\n 'For anyone who can read the post, while reactions are on for it (403 otherwise). Returns the new counts.',\n params: PostIdParam,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return posts.setReaction(req.orgId, req.params.postId, actor, null);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/posts/:postId/subscription',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setPostSubscription',\n tags,\n summary: 'Follow or mute a post, or clear either',\n description:\n 'For anyone who can read the post. `following` notifies you of its new comments. `muted` stops its activity notifying you, until `mutedUntil` when given; mentions and actions on you still notify. `none` clears both.',\n params: PostIdParam,\n body: SetPostSubscription,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await container.subscriptions.setPost(req.orgId, actor, req.params.postId, req.body);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/bookmark',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'bookmarkPost',\n tags,\n summary: 'Bookmark a post',\n description: 'For anyone who can read the post. Bookmarking twice is harmless.',\n params: PostIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await posts.bookmark(req.orgId, req.params.postId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/posts/:postId/bookmark',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unbookmarkPost',\n tags,\n summary: 'Remove a bookmark',\n description: 'For anyone who can read the post. Harmless when you have not bookmarked it.',\n params: PostIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await posts.unbookmark(req.orgId, req.params.postId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/flags',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'flagPost',\n tags,\n summary: 'Flag a post for the moderators',\n description:\n 'For anyone who can read the post. Flagging it again while your flag is open returns that flag.',\n params: PostIdParam,\n body: FlagReason,\n response: { 201: PostFlag, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const flag = await posts.flag(req.orgId, req.params.postId, actor, req.body.reason);\n return reply.code(201).send(flag);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/flags/resolution',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resolvePostFlags',\n tags,\n summary: 'Dismiss the open flags on a post in a space you moderate',\n description:\n 'For a moderator who can open the space. Closes every open flag and leaves the post up.',\n params: PostIdParam,\n body: ResolvePostFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await posts.resolveFlags(req.orgId, req.params.postId, actor, req.body.resolution);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/posts/:postId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listPostComments',\n tags,\n summary: \"Read a post's comments\",\n description:\n 'For anyone who can read the post. Pages by top-level comment, oldest first. Each page carries its top-level comments and all of their replies; `total` counts top-level comments.',\n params: PostIdParam,\n querystring: PageQuery,\n response: { 200: PostCommentsPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.discussion.postComments(req.orgId, req.params.postId, actor, req.query);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/posts/:postId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createPostComment',\n tags,\n summary: 'Comment on a post',\n description:\n 'For anyone who can read the post, while its comments are open (403 otherwise). Replies nest one level. 409 when the post is not published.',\n params: PostIdParam,\n body: PostComment,\n response: { 201: CommentView, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const comment = await container.discussion.postComment(req.orgId, {\n actor,\n postId: req.params.postId,\n parentId: req.body.parentId,\n body: req.body.body,\n });\n return reply.code(201).send(comment);\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Container } from "../../../app/container.js";
|
|
2
|
+
import { FastifyInstance } from "fastify";
|
|
3
|
+
//#region src/http/routes/member/profile.d.ts
|
|
4
|
+
declare function memberProfileRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { memberProfileRoutes };
|
|
7
|
+
//# sourceMappingURL=profile.d.ts.map
|
|
@@ -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,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { refusals } from "../schemas/shared.js";
|
|
2
|
+
import { resolveConfiguratorScope, resolveScope } from "../scope.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
|
+
import { NotificationCategoryDefaults, NotificationCategoryList, NotificationChannelInfos, NotificationListScopeQuery, NotificationTypeInfos, ReplaceNotificationCategories } from "../schemas/notifications.js";
|
|
6
7
|
import "../schemas/index.js";
|
|
7
|
-
import {
|
|
8
|
+
import { typesForList } from "@headless-lms/core/shared/notification-categories";
|
|
8
9
|
import { z } from "zod";
|
|
10
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
9
11
|
//#region src/http/routes/organizations.ts
|
|
10
12
|
async function organizationsRoutes(app, container) {
|
|
11
13
|
const r = app.withTypeProvider();
|
|
@@ -19,6 +21,7 @@ async function organizationsRoutes(app, container) {
|
|
|
19
21
|
operationId: "createOrganization",
|
|
20
22
|
tags,
|
|
21
23
|
summary: "Create an organization and make it active",
|
|
24
|
+
description: "For any signed-in person, including one with no organization yet. The caller becomes its owner.",
|
|
22
25
|
body: CreateOrganization,
|
|
23
26
|
response: { 201: Organization }
|
|
24
27
|
},
|
|
@@ -39,11 +42,12 @@ async function organizationsRoutes(app, container) {
|
|
|
39
42
|
schema: {
|
|
40
43
|
operationId: "updateOrganization",
|
|
41
44
|
tags,
|
|
42
|
-
summary: "Update the active organization",
|
|
45
|
+
summary: "Update the active organization (staff)",
|
|
46
|
+
description: "Owner or admin. Changes its name or slug; 409 when the slug is taken.",
|
|
43
47
|
body: UpdateOrganization,
|
|
44
48
|
response: {
|
|
45
49
|
200: Organization,
|
|
46
|
-
409
|
|
50
|
+
...refusals(403, 409)
|
|
47
51
|
}
|
|
48
52
|
},
|
|
49
53
|
handler: async (req, reply) => {
|
|
@@ -64,9 +68,13 @@ async function organizationsRoutes(app, container) {
|
|
|
64
68
|
schema: {
|
|
65
69
|
operationId: "listMembers",
|
|
66
70
|
tags,
|
|
67
|
-
summary: "List organization members",
|
|
71
|
+
summary: "List the organization's team members (staff)",
|
|
72
|
+
description: "Staff only (owner, admin, instructor). The team: everyone in the organization who is not a student.",
|
|
68
73
|
querystring: MembersQuery,
|
|
69
|
-
response: {
|
|
74
|
+
response: {
|
|
75
|
+
200: MembersPage,
|
|
76
|
+
...refusals(403)
|
|
77
|
+
}
|
|
70
78
|
},
|
|
71
79
|
handler: async (req) => {
|
|
72
80
|
const scope = await resolveScope(container, req);
|
|
@@ -80,13 +88,13 @@ async function organizationsRoutes(app, container) {
|
|
|
80
88
|
schema: {
|
|
81
89
|
operationId: "updateMemberRole",
|
|
82
90
|
tags,
|
|
83
|
-
summary: "Change a member's role",
|
|
91
|
+
summary: "Change a member's role (staff)",
|
|
92
|
+
description: "Owner or admin only. The owner role can be neither granted nor changed (409), and only a moderator can become a student.",
|
|
84
93
|
params: MemberIdParam,
|
|
85
94
|
body: UpdateMemberRole,
|
|
86
95
|
response: {
|
|
87
|
-
200:
|
|
88
|
-
404
|
|
89
|
-
409: ErrorBody
|
|
96
|
+
200: MemberRoleChange,
|
|
97
|
+
...refusals(403, 404, 409)
|
|
90
98
|
}
|
|
91
99
|
},
|
|
92
100
|
handler: async (req) => {
|
|
@@ -96,7 +104,10 @@ async function organizationsRoutes(app, container) {
|
|
|
96
104
|
authOrgId: scope.authOrgId,
|
|
97
105
|
headers: req.headers
|
|
98
106
|
};
|
|
99
|
-
const member = await organizations.updateMemberRole(ctx,
|
|
107
|
+
const member = await organizations.updateMemberRole(ctx, {
|
|
108
|
+
id: scope.orgUserId,
|
|
109
|
+
role: scope.role
|
|
110
|
+
}, req.params.id, req.body.role);
|
|
100
111
|
if (!member) throw new NotFoundError("Member", req.params.id);
|
|
101
112
|
return member;
|
|
102
113
|
}
|
|
@@ -108,12 +119,12 @@ async function organizationsRoutes(app, container) {
|
|
|
108
119
|
schema: {
|
|
109
120
|
operationId: "removeMember",
|
|
110
121
|
tags,
|
|
111
|
-
summary: "Remove an organization member",
|
|
122
|
+
summary: "Remove an organization member (staff)",
|
|
123
|
+
description: "Owner or admin only. Removing a pending member cancels their invite. The owner cannot be removed (409).",
|
|
112
124
|
params: MemberIdParam,
|
|
113
125
|
response: {
|
|
114
126
|
204: z.void(),
|
|
115
|
-
404
|
|
116
|
-
409: ErrorBody
|
|
127
|
+
...refusals(403, 404, 409)
|
|
117
128
|
}
|
|
118
129
|
},
|
|
119
130
|
handler: async (req, reply) => {
|
|
@@ -123,7 +134,10 @@ async function organizationsRoutes(app, container) {
|
|
|
123
134
|
authOrgId: scope.authOrgId,
|
|
124
135
|
headers: req.headers
|
|
125
136
|
};
|
|
126
|
-
if (!await organizations.removeMember(ctx,
|
|
137
|
+
if (!await organizations.removeMember(ctx, {
|
|
138
|
+
id: scope.orgUserId,
|
|
139
|
+
role: scope.role
|
|
140
|
+
}, req.params.id)) throw new NotFoundError("Member", req.params.id);
|
|
127
141
|
return reply.code(204).send();
|
|
128
142
|
}
|
|
129
143
|
});
|
|
@@ -134,16 +148,20 @@ async function organizationsRoutes(app, container) {
|
|
|
134
148
|
schema: {
|
|
135
149
|
operationId: "createInvite",
|
|
136
150
|
tags,
|
|
137
|
-
summary: "Invite a member or student into the active organization",
|
|
151
|
+
summary: "Invite a member or student into the active organization (staff)",
|
|
152
|
+
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
153
|
body: CreateInvite,
|
|
139
154
|
response: {
|
|
140
155
|
201: Invite,
|
|
141
|
-
409
|
|
156
|
+
...refusals(403, 409)
|
|
142
157
|
}
|
|
143
158
|
},
|
|
144
|
-
handler: async (req) => {
|
|
159
|
+
handler: async (req, reply) => {
|
|
145
160
|
const scope = await resolveScope(container, req);
|
|
146
161
|
const invite = await organizations.createInvite({
|
|
162
|
+
id: scope.orgUserId,
|
|
163
|
+
role: scope.role
|
|
164
|
+
}, {
|
|
147
165
|
orgId: scope.orgId,
|
|
148
166
|
inviterUserId: scope.userId,
|
|
149
167
|
email: req.body.email,
|
|
@@ -152,8 +170,15 @@ async function organizationsRoutes(app, container) {
|
|
|
152
170
|
...req.body.lastName !== void 0 && { lastName: req.body.lastName },
|
|
153
171
|
sendEmail: req.body.sendEmail
|
|
154
172
|
});
|
|
155
|
-
req.log.debug({ invite }, "created invite");
|
|
156
|
-
return
|
|
173
|
+
req.log.debug({ inviteId: invite.id }, "created invite");
|
|
174
|
+
return reply.code(201).send({
|
|
175
|
+
id: invite.id,
|
|
176
|
+
email: invite.email,
|
|
177
|
+
role: invite.role,
|
|
178
|
+
status: invite.status,
|
|
179
|
+
expiresAt: invite.expiresAt,
|
|
180
|
+
createdAt: invite.createdAt
|
|
181
|
+
});
|
|
157
182
|
}
|
|
158
183
|
});
|
|
159
184
|
r.route({
|
|
@@ -163,10 +188,11 @@ async function organizationsRoutes(app, container) {
|
|
|
163
188
|
operationId: "getInvite",
|
|
164
189
|
tags,
|
|
165
190
|
summary: "Read an invite by its token",
|
|
191
|
+
description: "Public: no session needed. The same 404 answers forged, expired and accepted tokens, so a probe learns nothing.",
|
|
166
192
|
params: InviteTokenParam,
|
|
167
193
|
response: {
|
|
168
194
|
200: GetInviteResult,
|
|
169
|
-
404
|
|
195
|
+
...refusals(404)
|
|
170
196
|
}
|
|
171
197
|
},
|
|
172
198
|
handler: async (req) => {
|
|
@@ -187,11 +213,12 @@ async function organizationsRoutes(app, container) {
|
|
|
187
213
|
schema: {
|
|
188
214
|
operationId: "acceptInvite",
|
|
189
215
|
tags,
|
|
190
|
-
summary: "Accept an invite with the
|
|
216
|
+
summary: "Accept an invite with the signed-in account",
|
|
217
|
+
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
218
|
body: AcceptInvite,
|
|
192
219
|
response: {
|
|
193
220
|
200: AcceptInviteResult,
|
|
194
|
-
400
|
|
221
|
+
...refusals(400, 404)
|
|
195
222
|
}
|
|
196
223
|
},
|
|
197
224
|
handler: async (req) => {
|
|
@@ -212,6 +239,125 @@ async function organizationsRoutes(app, container) {
|
|
|
212
239
|
return {};
|
|
213
240
|
}
|
|
214
241
|
});
|
|
242
|
+
const notificationTags = ["Notifications"];
|
|
243
|
+
r.route({
|
|
244
|
+
method: "GET",
|
|
245
|
+
url: "/api/notification-types",
|
|
246
|
+
preHandler: app.requireOrgSession,
|
|
247
|
+
schema: {
|
|
248
|
+
operationId: "listNotificationTypes",
|
|
249
|
+
tags: notificationTags,
|
|
250
|
+
summary: "List the notification types a category list can cover (staff)",
|
|
251
|
+
description: "Owner or admin only. `scope=org` lists the types the org list can cover, `scope=community` those a community list can cover. Each type carries all its scopes, so an editor can tell which types can be per space.",
|
|
252
|
+
querystring: NotificationListScopeQuery,
|
|
253
|
+
response: {
|
|
254
|
+
200: NotificationTypeInfos,
|
|
255
|
+
...refusals(403)
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
handler: async (req) => {
|
|
259
|
+
await resolveConfiguratorScope(container, req);
|
|
260
|
+
return typesForList(container.notificationTypes, req.query.scope);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
r.route({
|
|
264
|
+
method: "GET",
|
|
265
|
+
url: "/api/notification-channels",
|
|
266
|
+
preHandler: app.requireOrgSession,
|
|
267
|
+
schema: {
|
|
268
|
+
operationId: "listNotificationChannels",
|
|
269
|
+
tags: notificationTags,
|
|
270
|
+
summary: "List the notification channels this deployment can deliver on (staff)",
|
|
271
|
+
description: "Owner or admin only. `inApp` is always first.",
|
|
272
|
+
response: {
|
|
273
|
+
200: NotificationChannelInfos,
|
|
274
|
+
...refusals(403)
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
handler: async (req) => {
|
|
278
|
+
await resolveConfiguratorScope(container, req);
|
|
279
|
+
return container.notificationChannels.list().map((channel) => ({ channel }));
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
r.route({
|
|
283
|
+
method: "GET",
|
|
284
|
+
url: "/api/notification-categories",
|
|
285
|
+
preHandler: app.requireOrgSession,
|
|
286
|
+
schema: {
|
|
287
|
+
operationId: "getOrgNotificationCategories",
|
|
288
|
+
tags: notificationTags,
|
|
289
|
+
summary: "Get the organization's notification categories (staff)",
|
|
290
|
+
description: "Owner or admin only. The list in use for notifications outside any community, with `source` saying whether it is the default or a stored list.",
|
|
291
|
+
response: {
|
|
292
|
+
200: NotificationCategoryList,
|
|
293
|
+
...refusals(403)
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
handler: async (req) => {
|
|
297
|
+
const scope = await resolveConfiguratorScope(container, req);
|
|
298
|
+
return container.notificationCategories.org.get(scope.orgId, scope.orgId);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
r.route({
|
|
302
|
+
method: "PUT",
|
|
303
|
+
url: "/api/notification-categories",
|
|
304
|
+
preHandler: app.requireOrgSession,
|
|
305
|
+
schema: {
|
|
306
|
+
operationId: "replaceOrgNotificationCategories",
|
|
307
|
+
tags: notificationTags,
|
|
308
|
+
summary: "Replace the organization's notification categories (staff)",
|
|
309
|
+
description: "Owner or admin only. Stores the whole list. 422 when a key repeats, a type cannot be in the org list, a reason is not allowed for its type, a channel is not configured, or earlier categories already cover everything a category covers.",
|
|
310
|
+
body: ReplaceNotificationCategories,
|
|
311
|
+
response: {
|
|
312
|
+
200: NotificationCategoryList,
|
|
313
|
+
...refusals(400, 403, 422)
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
handler: async (req) => {
|
|
317
|
+
const scope = await resolveConfiguratorScope(container, req);
|
|
318
|
+
return container.notificationCategories.org.replace(scope.orgId, scope.orgId, req.body.categories);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
r.route({
|
|
322
|
+
method: "DELETE",
|
|
323
|
+
url: "/api/notification-categories",
|
|
324
|
+
preHandler: app.requireOrgSession,
|
|
325
|
+
schema: {
|
|
326
|
+
operationId: "resetOrgNotificationCategories",
|
|
327
|
+
tags: notificationTags,
|
|
328
|
+
summary: "Go back to the default notification categories for the organization (staff)",
|
|
329
|
+
description: "Owner or admin only. Removes the stored list; the default applies again.",
|
|
330
|
+
response: {
|
|
331
|
+
204: z.void(),
|
|
332
|
+
...refusals(403)
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
handler: async (req, reply) => {
|
|
336
|
+
const scope = await resolveConfiguratorScope(container, req);
|
|
337
|
+
await container.notificationCategories.org.reset(scope.orgId, scope.orgId);
|
|
338
|
+
return reply.code(204).send();
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
r.route({
|
|
342
|
+
method: "GET",
|
|
343
|
+
url: "/api/notification-categories/defaults",
|
|
344
|
+
preHandler: app.requireOrgSession,
|
|
345
|
+
schema: {
|
|
346
|
+
operationId: "getDefaultNotificationCategories",
|
|
347
|
+
tags: notificationTags,
|
|
348
|
+
summary: "Get the default notification categories (staff)",
|
|
349
|
+
description: "Owner or admin only. The default org list (`scope=org`) or community list (`scope=community`), offering only the configured channels.",
|
|
350
|
+
querystring: NotificationListScopeQuery,
|
|
351
|
+
response: {
|
|
352
|
+
200: NotificationCategoryDefaults,
|
|
353
|
+
...refusals(403)
|
|
354
|
+
}
|
|
355
|
+
},
|
|
356
|
+
handler: async (req) => {
|
|
357
|
+
await resolveConfiguratorScope(container, req);
|
|
358
|
+
return { categories: (req.query.scope === "org" ? container.notificationCategories.org : container.notificationCategories.community).defaults() };
|
|
359
|
+
}
|
|
360
|
+
});
|
|
215
361
|
}
|
|
216
362
|
//#endregion
|
|
217
363
|
export { organizationsRoutes };
|
|
@@ -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 NotificationCategoryDefaults,\n NotificationCategoryList,\n NotificationChannelInfos,\n NotificationListScopeQuery,\n NotificationTypeInfos,\n ReplaceNotificationCategories,\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 { resolveConfiguratorScope, resolveScope } from '../scope.js';\nimport { typesForList } from '@headless-lms/core/shared/notification-categories';\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 const notificationTags = ['Notifications'];\n\n r.route({\n method: 'GET',\n url: '/api/notification-types',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listNotificationTypes',\n tags: notificationTags,\n summary: 'List the notification types a category list can cover (staff)',\n description:\n 'Owner or admin only. `scope=org` lists the types the org list can cover, `scope=community` those a community list can cover. Each type carries all its scopes, so an editor can tell which types can be per space.',\n querystring: NotificationListScopeQuery,\n response: { 200: NotificationTypeInfos, ...refusals(403) },\n },\n handler: async (req) => {\n await resolveConfiguratorScope(container, req);\n return typesForList(container.notificationTypes, req.query.scope);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/notification-channels',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listNotificationChannels',\n tags: notificationTags,\n summary: 'List the notification channels this deployment can deliver on (staff)',\n description: 'Owner or admin only. `inApp` is always first.',\n response: { 200: NotificationChannelInfos, ...refusals(403) },\n },\n handler: async (req) => {\n await resolveConfiguratorScope(container, req);\n return container.notificationChannels.list().map((channel) => ({ channel }));\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/notification-categories',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getOrgNotificationCategories',\n tags: notificationTags,\n summary: \"Get the organization's notification categories (staff)\",\n description:\n 'Owner or admin only. The list in use for notifications outside any community, with `source` saying whether it is the default or a stored list.',\n response: { 200: NotificationCategoryList, ...refusals(403) },\n },\n handler: async (req) => {\n const scope = await resolveConfiguratorScope(container, req);\n return container.notificationCategories.org.get(scope.orgId, scope.orgId);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/notification-categories',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'replaceOrgNotificationCategories',\n tags: notificationTags,\n summary: \"Replace the organization's notification categories (staff)\",\n description:\n 'Owner or admin only. Stores the whole list. 422 when a key repeats, a type cannot be in the org list, a reason is not allowed for its type, a channel is not configured, or earlier categories already cover everything a category covers.',\n body: ReplaceNotificationCategories,\n response: { 200: NotificationCategoryList, ...refusals(400, 403, 422) },\n },\n handler: async (req) => {\n const scope = await resolveConfiguratorScope(container, req);\n return container.notificationCategories.org.replace(\n scope.orgId,\n scope.orgId,\n req.body.categories,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/notification-categories',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resetOrgNotificationCategories',\n tags: notificationTags,\n summary: 'Go back to the default notification categories for the organization (staff)',\n description: 'Owner or admin only. Removes the stored list; the default applies again.',\n response: { 204: z.void(), ...refusals(403) },\n },\n handler: async (req, reply) => {\n const scope = await resolveConfiguratorScope(container, req);\n await container.notificationCategories.org.reset(scope.orgId, scope.orgId);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/notification-categories/defaults',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getDefaultNotificationCategories',\n tags: notificationTags,\n summary: 'Get the default notification categories (staff)',\n description:\n 'Owner or admin only. The default org list (`scope=org`) or community list (`scope=community`), offering only the configured channels.',\n querystring: NotificationListScopeQuery,\n response: { 200: NotificationCategoryDefaults, ...refusals(403) },\n },\n handler: async (req) => {\n await resolveConfiguratorScope(container, req);\n const service =\n req.query.scope === 'org'\n ? container.notificationCategories.org\n : container.notificationCategories.community;\n return { categories: service.defaults() };\n },\n });\n}\n"],"mappings":"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"}
|