@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 @@
|
|
|
1
|
+
{"version":3,"file":"communities.js","names":[],"sources":["../../../src/http/routes/communities.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 { NotFoundError } from '@headless-lms/core/shared/errors';\nimport { resolveScope } from '../scope.js';\nimport {\n Community,\n CommunityAnalytics,\n CommunityIdParam,\n CommunitySpaceParam,\n CommunitiesPage,\n CommunitiesQuery,\n CreateCommunity,\n CreateSpace,\n MessageQueue,\n OrderedIds,\n PatchDmSettings,\n SetSpaceTopics,\n SpaceDetail,\n SpaceLinkBody,\n SpaceLinkParam,\n UpdateCommunity,\n UpdateSpace,\n refusals,\n} from '../schemas/index.js';\n\nexport async function communitiesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const content = container.content;\n const tags = ['Communities'];\n\n r.route({\n method: 'GET',\n url: '/api/communities',\n schema: {\n operationId: 'listCommunities',\n tags,\n summary: 'List communities (staff)',\n description:\n 'Staff only (owner, admin, instructor). Pages every community in the organization, drafts included, filtered by `status` and searched by title.',\n querystring: CommunitiesQuery,\n response: { 200: CommunitiesPage, ...refusals(403) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listCommunities(scope.orgId, req.query);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities',\n schema: {\n operationId: 'createCommunity',\n tags,\n summary: 'Create a community (staff)',\n description:\n 'Owner or admin only. The community starts as a draft with the default direct-message settings and no spaces. Members see it once it is published and they hold it.',\n body: CreateCommunity,\n response: { 201: Community, ...refusals(403, 409) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const community = await content.createCommunity(scope.orgId, req.body, {\n id: scope.orgUserId,\n role: scope.role,\n });\n req.log.info({ communityId: community.id }, 'community created');\n return reply.code(201).send(community);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId',\n schema: {\n operationId: 'getCommunity',\n tags,\n summary: 'Get a community (staff)',\n description:\n 'Staff only (owner, admin, instructor). Any community of the organization, drafts included.',\n params: CommunityIdParam,\n response: { 200: Community, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const community = await content.getCommunity(scope.orgId, req.params.communityId);\n if (!community) {\n throw new NotFoundError('Community', req.params.communityId);\n }\n return community;\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/communities/:communityId',\n schema: {\n operationId: 'updateCommunity',\n tags,\n summary: 'Update a community (staff)',\n description:\n 'Owner or admin only. Changes only the fields sent. `thumbnailAssetId` must be a ready library image (409 otherwise); null clears it.',\n params: CommunityIdParam,\n body: UpdateCommunity,\n response: { 200: Community, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.updateCommunity(scope.orgId, req.params.communityId, req.body, {\n id: scope.orgUserId,\n role: scope.role,\n });\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId',\n schema: {\n operationId: 'deleteCommunity',\n tags,\n summary: 'Delete a community (staff)',\n description:\n 'Owner or admin only. Links to the community from other spaces are removed with it.',\n params: CommunityIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await content.deleteCommunity(scope.orgId, req.params.communityId, {\n id: scope.orgUserId,\n role: scope.role,\n });\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/communities/:communityId/settings',\n schema: {\n operationId: 'patchCommunityDmSettings',\n tags,\n summary: 'Change direct-message settings for a community (staff)',\n description:\n 'Owner or admin only. Changes only the settings sent; the rest keep their values. They decide who may message whom from then on.',\n params: CommunityIdParam,\n body: PatchDmSettings,\n response: { 200: Community, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.patchDmSettings(scope.orgId, req.params.communityId, req.body, {\n id: scope.orgUserId,\n role: scope.role,\n });\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/publish',\n schema: {\n operationId: 'publishCommunity',\n tags,\n summary: 'Publish a community (staff)',\n description: 'Owner or admin only. Makes the community visible to the people who hold it.',\n params: CommunityIdParam,\n response: { 200: Community, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.publishCommunity(scope.orgId, req.params.communityId, {\n id: scope.orgUserId,\n role: scope.role,\n });\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/unpublish',\n schema: {\n operationId: 'unpublishCommunity',\n tags,\n summary: 'Unpublish a community (staff)',\n description:\n 'Owner or admin only. Hides the community from members again; nothing in it is deleted.',\n params: CommunityIdParam,\n response: { 200: Community, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.unpublishCommunity(scope.orgId, req.params.communityId, {\n id: scope.orgUserId,\n role: scope.role,\n });\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/analytics',\n schema: {\n operationId: 'getCommunityAnalytics',\n tags,\n summary: 'Staff analytics for a community',\n description:\n 'Staff only (owner, admin, instructor). Membership, activity and moderation counts for one community.',\n params: CommunityIdParam,\n response: { 200: CommunityAnalytics, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const stats = await container.reporting.admin.communityAnalytics(\n scope.orgId,\n req.params.communityId,\n );\n if (!stats) {\n throw new NotFoundError('Community', req.params.communityId);\n }\n return stats;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/message-queue',\n schema: {\n operationId: 'getCommunityMessageQueue',\n tags,\n summary: 'Open message flags in a community (staff)',\n description:\n \"Staff only (owner, admin, instructor). The messages in the community's chats with open flags, with the flags and the chat each is in. Holds at most the 100 oldest open flags; working the queue brings in the next ones.\",\n params: CommunityIdParam,\n response: { 200: MessageQueue, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.chats.messageQueue(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n );\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/spaces',\n schema: {\n operationId: 'listCommunitySpaces',\n tags,\n summary: 'List the spaces in a community (staff)',\n description:\n 'Staff only (owner, admin, instructor). Every space in order, with its topics and links.',\n params: CommunityIdParam,\n response: { 200: z.array(SpaceDetail), ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.listSpaces(scope.orgId, req.params.communityId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces',\n schema: {\n operationId: 'createSpace',\n tags,\n summary: 'Create a space (staff)',\n description:\n 'Owner or admin only. The space is added last. An `all_members` space cannot have a capacity (409).',\n params: CommunityIdParam,\n body: CreateSpace,\n response: { 201: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const space = await content.createSpace(scope.orgId, req.params.communityId, req.body, {\n id: scope.orgUserId,\n role: scope.role,\n });\n return reply.code(201).send(space);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/spaces/order',\n schema: {\n operationId: 'reorderSpaces',\n tags,\n summary: 'Reorder the spaces in a community (staff)',\n description:\n 'Owner or admin only. Send every space id of the community exactly once (409 otherwise).',\n params: CommunityIdParam,\n body: OrderedIds,\n response: { 200: z.array(SpaceDetail), ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.reorderSpaces(scope.orgId, req.params.communityId, req.body.ids, {\n id: scope.orgUserId,\n role: scope.role,\n });\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/spaces/:spaceId',\n schema: {\n operationId: 'getSpace',\n tags,\n summary: 'Get a space (staff)',\n description: 'Staff only (owner, admin, instructor). The space with its topics and links.',\n params: CommunitySpaceParam,\n response: { 200: SpaceDetail, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const space = await content.getCommunitySpace(\n scope.orgId,\n req.params.communityId,\n req.params.spaceId,\n );\n if (!space) {\n throw new NotFoundError('Space', req.params.spaceId);\n }\n return space;\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/communities/:communityId/spaces/:spaceId',\n schema: {\n operationId: 'updateSpace',\n tags,\n summary: 'Update a space (staff)',\n description:\n 'Owner or admin only. Changes only the fields sent; `settings` merges into the current settings. An `all_members` space cannot have a capacity (409).',\n params: CommunitySpaceParam,\n body: UpdateSpace,\n response: { 200: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.updateSpace(\n scope.orgId,\n req.params.communityId,\n req.params.spaceId,\n req.body,\n { id: scope.orgUserId, role: scope.role },\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId/spaces/:spaceId',\n schema: {\n operationId: 'deleteSpace',\n tags,\n summary: 'Delete a space (staff)',\n description: 'Owner or admin only. The space and everything in it go.',\n params: CommunitySpaceParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await content.deleteSpace(scope.orgId, req.params.communityId, req.params.spaceId, {\n id: scope.orgUserId,\n role: scope.role,\n });\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/spaces/:spaceId/topics',\n schema: {\n operationId: 'setSpaceTopics',\n tags,\n summary: 'Replace the topics on a space (staff)',\n description:\n 'Owner or admin only. The list replaces the current topics: an entry with an `id` keeps and renames that topic, one without adds a topic, and a topic left out is deleted. Names must be unique (409), and a topic that posts use cannot be deleted (409).',\n params: CommunitySpaceParam,\n body: SetSpaceTopics,\n response: { 200: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.setSpaceTopics(\n scope.orgId,\n req.params.communityId,\n req.params.spaceId,\n req.body,\n { id: scope.orgUserId, role: scope.role },\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces/:spaceId/links',\n schema: {\n operationId: 'addSpaceLink',\n tags,\n summary: 'Link a course, download, or community into a space (staff)',\n description:\n 'Owner or admin only. The link is added last. Linking the same content twice is a 409.',\n params: CommunitySpaceParam,\n body: SpaceLinkBody,\n response: { 201: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const space = await content.addSpaceLink(\n scope.orgId,\n req.params.communityId,\n req.params.spaceId,\n req.body.contentId,\n { id: scope.orgUserId, role: scope.role },\n );\n return reply.code(201).send(space);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId/spaces/:spaceId/links/:linkId',\n schema: {\n operationId: 'removeSpaceLink',\n tags,\n summary: 'Remove a link from a space (staff)',\n description: 'Owner or admin only. The linked content itself is untouched.',\n params: SpaceLinkParam,\n response: { 200: SpaceDetail, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.removeSpaceLink(\n scope.orgId,\n req.params.communityId,\n req.params.spaceId,\n req.params.linkId,\n { id: scope.orgUserId, role: scope.role },\n );\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/spaces/:spaceId/links/order',\n schema: {\n operationId: 'reorderSpaceLinks',\n tags,\n summary: 'Reorder the links in a space (staff)',\n description:\n 'Owner or admin only. Send every link id of the space exactly once (409 otherwise).',\n params: CommunitySpaceParam,\n body: OrderedIds,\n response: { 200: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return content.reorderSpaceLinks(\n scope.orgId,\n req.params.communityId,\n req.params.spaceId,\n req.body.ids,\n { id: scope.orgUserId, role: scope.role },\n );\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/community-moderation.d.ts
|
|
4
|
+
declare function communityModerationRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { communityModerationRoutes };
|
|
7
|
+
//# sourceMappingURL=community-moderation.d.ts.map
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import { PageQuery, refusals } from "../schemas/shared.js";
|
|
2
|
+
import { resolveScope } from "../scope.js";
|
|
3
|
+
import { CommentView, ResolveCommentFlags } from "../schemas/discussion.js";
|
|
4
|
+
import { CommunityChatParam, CommunityCommentParam, CommunityIdParam, CommunityMembersPage, CommunityMembersQuery, CommunityMessageParam, CommunityPostParam, CommunitySpaceParam, SpaceMember, SpaceMemberParam, SpaceMembersPage } from "../schemas/community.js";
|
|
5
|
+
import { PostView, ResolvePostFlags, SpaceQueue } from "../schemas/posts.js";
|
|
6
|
+
import { Chat, OrgUserBody, ResolveMessageFlags } from "../schemas/chats.js";
|
|
7
|
+
import "../schemas/index.js";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
10
|
+
//#region src/http/routes/community-moderation.ts
|
|
11
|
+
async function communityModerationRoutes(app, container) {
|
|
12
|
+
const r = app.withTypeProvider();
|
|
13
|
+
const tags = ["Communities"];
|
|
14
|
+
r.route({
|
|
15
|
+
method: "GET",
|
|
16
|
+
url: "/api/communities/:communityId/members",
|
|
17
|
+
schema: {
|
|
18
|
+
operationId: "listCommunityMembers",
|
|
19
|
+
tags,
|
|
20
|
+
summary: "List the people who hold a community (staff)",
|
|
21
|
+
description: "Staff only (owner, admin, instructor). Everyone with active access to the community, granted directly or through a bundle, paged and searched by name or email.",
|
|
22
|
+
params: CommunityIdParam,
|
|
23
|
+
querystring: CommunityMembersQuery,
|
|
24
|
+
response: {
|
|
25
|
+
200: CommunityMembersPage,
|
|
26
|
+
...refusals(403, 404)
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
handler: async (req) => {
|
|
30
|
+
const scope = await resolveScope(container, req);
|
|
31
|
+
const page = await container.reporting.admin.communityMembers(scope.orgId, req.params.communityId, req.query);
|
|
32
|
+
if (!page) throw new NotFoundError("Community", req.params.communityId);
|
|
33
|
+
return page;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
r.route({
|
|
37
|
+
method: "GET",
|
|
38
|
+
url: "/api/communities/:communityId/spaces/:spaceId/members",
|
|
39
|
+
schema: {
|
|
40
|
+
operationId: "listCommunitySpaceMembers",
|
|
41
|
+
tags,
|
|
42
|
+
summary: "List the members, requests and invitations of a space (staff)",
|
|
43
|
+
description: "Staff only (owner, admin, instructor). The space's members, its open requests to join and its open invitations.",
|
|
44
|
+
params: CommunitySpaceParam,
|
|
45
|
+
querystring: PageQuery,
|
|
46
|
+
response: {
|
|
47
|
+
200: SpaceMembersPage,
|
|
48
|
+
...refusals(403, 404)
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
handler: async (req) => {
|
|
52
|
+
const scope = await resolveScope(container, req);
|
|
53
|
+
return container.entitlements.listSpaceMembers(scope.orgId, {
|
|
54
|
+
id: scope.orgUserId,
|
|
55
|
+
role: scope.role
|
|
56
|
+
}, req.params.communityId, req.params.spaceId, req.query);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
r.route({
|
|
60
|
+
method: "POST",
|
|
61
|
+
url: "/api/communities/:communityId/spaces/:spaceId/members",
|
|
62
|
+
schema: {
|
|
63
|
+
operationId: "addCommunitySpaceMember",
|
|
64
|
+
tags,
|
|
65
|
+
summary: "Add a person to a space (staff)",
|
|
66
|
+
description: "Staff only (owner, admin, instructor). Places a member of the community straight into the space, with no request or invitation. 409 when the space is open to all members, the person is already placed, or the space is full.",
|
|
67
|
+
params: CommunitySpaceParam,
|
|
68
|
+
body: OrgUserBody,
|
|
69
|
+
response: {
|
|
70
|
+
201: SpaceMember,
|
|
71
|
+
...refusals(403, 404, 409)
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
handler: async (req, reply) => {
|
|
75
|
+
const scope = await resolveScope(container, req);
|
|
76
|
+
const member = await container.entitlements.addSpaceMember(scope.orgId, {
|
|
77
|
+
id: scope.orgUserId,
|
|
78
|
+
role: scope.role
|
|
79
|
+
}, req.params.communityId, req.params.spaceId, req.body.orgUserId);
|
|
80
|
+
return reply.code(201).send(member);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
r.route({
|
|
84
|
+
method: "POST",
|
|
85
|
+
url: "/api/communities/:communityId/spaces/:spaceId/invites",
|
|
86
|
+
schema: {
|
|
87
|
+
operationId: "inviteCommunitySpaceMember",
|
|
88
|
+
tags,
|
|
89
|
+
summary: "Invite a person into a space (staff)",
|
|
90
|
+
description: "Staff only (owner, admin, instructor). Invites a member of the community into a `by_invite` space; they join when they accept. 403 when the person is staff or does not hold the community; 409 when the space is not invite-only or the person is already placed.",
|
|
91
|
+
params: CommunitySpaceParam,
|
|
92
|
+
body: OrgUserBody,
|
|
93
|
+
response: {
|
|
94
|
+
201: SpaceMember,
|
|
95
|
+
...refusals(403, 404, 409)
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
handler: async (req, reply) => {
|
|
99
|
+
const scope = await resolveScope(container, req);
|
|
100
|
+
const member = await container.entitlements.invite(scope.orgId, {
|
|
101
|
+
id: scope.orgUserId,
|
|
102
|
+
role: scope.role
|
|
103
|
+
}, req.params.communityId, req.params.spaceId, req.body.orgUserId);
|
|
104
|
+
return reply.code(201).send(member);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
r.route({
|
|
108
|
+
method: "POST",
|
|
109
|
+
url: "/api/communities/:communityId/spaces/:spaceId/members/:orgUserId/approval",
|
|
110
|
+
schema: {
|
|
111
|
+
operationId: "approveCommunitySpaceRequest",
|
|
112
|
+
tags,
|
|
113
|
+
summary: "Approve a person's request to join a space (staff)",
|
|
114
|
+
description: "Staff only (owner, admin, instructor). Makes the requester an active member. 404 when the person has no open request; 409 when the space is full or they are already a member.",
|
|
115
|
+
params: SpaceMemberParam,
|
|
116
|
+
response: {
|
|
117
|
+
200: SpaceMember,
|
|
118
|
+
...refusals(403, 404, 409)
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
handler: async (req) => {
|
|
122
|
+
const scope = await resolveScope(container, req);
|
|
123
|
+
return container.entitlements.approve(scope.orgId, {
|
|
124
|
+
id: scope.orgUserId,
|
|
125
|
+
role: scope.role
|
|
126
|
+
}, req.params.communityId, req.params.spaceId, req.params.orgUserId);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
r.route({
|
|
130
|
+
method: "POST",
|
|
131
|
+
url: "/api/communities/:communityId/spaces/:spaceId/members/:orgUserId/decline",
|
|
132
|
+
schema: {
|
|
133
|
+
operationId: "declineCommunitySpaceRequest",
|
|
134
|
+
tags,
|
|
135
|
+
summary: "Decline a person's request to join a space (staff)",
|
|
136
|
+
description: "Staff only (owner, admin, instructor). Closes the open request. 404 when the person has no open request; 409 when they are already a member.",
|
|
137
|
+
params: SpaceMemberParam,
|
|
138
|
+
response: {
|
|
139
|
+
200: SpaceMember,
|
|
140
|
+
...refusals(403, 404, 409)
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
handler: async (req) => {
|
|
144
|
+
const scope = await resolveScope(container, req);
|
|
145
|
+
return container.entitlements.decline(scope.orgId, {
|
|
146
|
+
id: scope.orgUserId,
|
|
147
|
+
role: scope.role
|
|
148
|
+
}, req.params.communityId, req.params.spaceId, req.params.orgUserId);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
r.route({
|
|
152
|
+
method: "POST",
|
|
153
|
+
url: "/api/communities/:communityId/spaces/:spaceId/members/:orgUserId/removal",
|
|
154
|
+
schema: {
|
|
155
|
+
operationId: "removeCommunitySpaceMember",
|
|
156
|
+
tags,
|
|
157
|
+
summary: "Remove a person from a space (staff)",
|
|
158
|
+
description: "Staff only (owner, admin, instructor). Revokes the placement, whether it is a membership, a request or an invitation. 409 when the space is open to all members or the placement is already revoked.",
|
|
159
|
+
params: SpaceMemberParam,
|
|
160
|
+
response: {
|
|
161
|
+
200: SpaceMember,
|
|
162
|
+
...refusals(403, 404, 409)
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
handler: async (req) => {
|
|
166
|
+
const scope = await resolveScope(container, req);
|
|
167
|
+
return container.entitlements.remove(scope.orgId, {
|
|
168
|
+
id: scope.orgUserId,
|
|
169
|
+
role: scope.role
|
|
170
|
+
}, req.params.communityId, req.params.spaceId, req.params.orgUserId);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
r.route({
|
|
174
|
+
method: "GET",
|
|
175
|
+
url: "/api/communities/:communityId/spaces/:spaceId/queue",
|
|
176
|
+
schema: {
|
|
177
|
+
operationId: "getCommunitySpaceQueue",
|
|
178
|
+
tags,
|
|
179
|
+
summary: "The moderation queue of a space (staff)",
|
|
180
|
+
description: "Staff only (owner, admin, instructor). 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.",
|
|
181
|
+
params: CommunitySpaceParam,
|
|
182
|
+
response: {
|
|
183
|
+
200: SpaceQueue,
|
|
184
|
+
...refusals(403, 404)
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
handler: async (req) => {
|
|
188
|
+
const scope = await resolveScope(container, req);
|
|
189
|
+
return container.posts.moderationQueue(scope.orgId, {
|
|
190
|
+
id: scope.orgUserId,
|
|
191
|
+
role: scope.role
|
|
192
|
+
}, req.params.communityId, req.params.spaceId);
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
r.route({
|
|
196
|
+
method: "POST",
|
|
197
|
+
url: "/api/communities/:communityId/posts/:postId/approval",
|
|
198
|
+
schema: {
|
|
199
|
+
operationId: "approveCommunityPost",
|
|
200
|
+
tags,
|
|
201
|
+
summary: "Publish a post that is awaiting review (staff)",
|
|
202
|
+
description: "Staff only (owner, admin, instructor). 409 when the post is not awaiting review.",
|
|
203
|
+
params: CommunityPostParam,
|
|
204
|
+
response: {
|
|
205
|
+
200: PostView,
|
|
206
|
+
...refusals(403, 404, 409)
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
handler: async (req) => {
|
|
210
|
+
const scope = await resolveScope(container, req);
|
|
211
|
+
return container.posts.approve(scope.orgId, req.params.postId, {
|
|
212
|
+
id: scope.orgUserId,
|
|
213
|
+
role: scope.role
|
|
214
|
+
}, req.params.communityId);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
r.route({
|
|
218
|
+
method: "POST",
|
|
219
|
+
url: "/api/communities/:communityId/posts/:postId/removal",
|
|
220
|
+
schema: {
|
|
221
|
+
operationId: "removeCommunityPost",
|
|
222
|
+
tags,
|
|
223
|
+
summary: "Remove a post (staff)",
|
|
224
|
+
description: "Staff only (owner, admin, instructor). Takes a published or pending post down and resolves its open flags as `removed`. 409 when it is already removed or was never submitted.",
|
|
225
|
+
params: CommunityPostParam,
|
|
226
|
+
response: {
|
|
227
|
+
200: PostView,
|
|
228
|
+
...refusals(403, 404, 409)
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
handler: async (req) => {
|
|
232
|
+
const scope = await resolveScope(container, req);
|
|
233
|
+
return container.posts.remove(scope.orgId, req.params.postId, {
|
|
234
|
+
id: scope.orgUserId,
|
|
235
|
+
role: scope.role
|
|
236
|
+
}, req.params.communityId);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
r.route({
|
|
240
|
+
method: "POST",
|
|
241
|
+
url: "/api/communities/:communityId/posts/:postId/restoration",
|
|
242
|
+
schema: {
|
|
243
|
+
operationId: "restoreCommunityPost",
|
|
244
|
+
tags,
|
|
245
|
+
summary: "Restore a removed post (staff)",
|
|
246
|
+
description: "Staff only (owner, admin, instructor). 409 when the post is not removed.",
|
|
247
|
+
params: CommunityPostParam,
|
|
248
|
+
response: {
|
|
249
|
+
200: PostView,
|
|
250
|
+
...refusals(403, 404, 409)
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
handler: async (req) => {
|
|
254
|
+
const scope = await resolveScope(container, req);
|
|
255
|
+
return container.posts.restore(scope.orgId, req.params.postId, {
|
|
256
|
+
id: scope.orgUserId,
|
|
257
|
+
role: scope.role
|
|
258
|
+
}, req.params.communityId);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
r.route({
|
|
262
|
+
method: "POST",
|
|
263
|
+
url: "/api/communities/:communityId/posts/:postId/flags/resolution",
|
|
264
|
+
schema: {
|
|
265
|
+
operationId: "resolveCommunityPostFlags",
|
|
266
|
+
tags,
|
|
267
|
+
summary: "Dismiss the open flags on a post (staff)",
|
|
268
|
+
description: "Staff only (owner, admin, instructor). Closes every open flag on the post and leaves it up.",
|
|
269
|
+
params: CommunityPostParam,
|
|
270
|
+
body: ResolvePostFlags,
|
|
271
|
+
response: {
|
|
272
|
+
204: z.void(),
|
|
273
|
+
...refusals(403, 404)
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
handler: async (req, reply) => {
|
|
277
|
+
const scope = await resolveScope(container, req);
|
|
278
|
+
await container.posts.resolveFlags(scope.orgId, req.params.postId, {
|
|
279
|
+
id: scope.orgUserId,
|
|
280
|
+
role: scope.role
|
|
281
|
+
}, req.body.resolution, req.params.communityId);
|
|
282
|
+
return reply.code(204).send();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
r.route({
|
|
286
|
+
method: "POST",
|
|
287
|
+
url: "/api/communities/:communityId/comments/:commentId/removal",
|
|
288
|
+
schema: {
|
|
289
|
+
operationId: "removeCommunityComment",
|
|
290
|
+
tags,
|
|
291
|
+
summary: "Remove a comment on a post (staff)",
|
|
292
|
+
description: "Staff only (owner, admin, instructor). Hides the comment and resolves its open flags as `removed`. Removing a removed comment returns it unchanged.",
|
|
293
|
+
params: CommunityCommentParam,
|
|
294
|
+
response: {
|
|
295
|
+
200: CommentView,
|
|
296
|
+
...refusals(403, 404)
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
handler: async (req) => {
|
|
300
|
+
const scope = await resolveScope(container, req);
|
|
301
|
+
return container.discussion.remove(scope.orgId, req.params.commentId, {
|
|
302
|
+
id: scope.orgUserId,
|
|
303
|
+
role: scope.role
|
|
304
|
+
}, req.params.communityId);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
r.route({
|
|
308
|
+
method: "POST",
|
|
309
|
+
url: "/api/communities/:communityId/comments/:commentId/restoration",
|
|
310
|
+
schema: {
|
|
311
|
+
operationId: "restoreCommunityComment",
|
|
312
|
+
tags,
|
|
313
|
+
summary: "Restore a removed comment on a post (staff)",
|
|
314
|
+
description: "Staff only (owner, admin, instructor). Publishes the comment again. Restoring a comment that is not removed returns it unchanged.",
|
|
315
|
+
params: CommunityCommentParam,
|
|
316
|
+
response: {
|
|
317
|
+
200: CommentView,
|
|
318
|
+
...refusals(403, 404)
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
handler: async (req) => {
|
|
322
|
+
const scope = await resolveScope(container, req);
|
|
323
|
+
return container.discussion.restore(scope.orgId, req.params.commentId, {
|
|
324
|
+
id: scope.orgUserId,
|
|
325
|
+
role: scope.role
|
|
326
|
+
}, req.params.communityId);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
r.route({
|
|
330
|
+
method: "POST",
|
|
331
|
+
url: "/api/communities/:communityId/comments/:commentId/flags/resolution",
|
|
332
|
+
schema: {
|
|
333
|
+
operationId: "resolveCommunityCommentFlags",
|
|
334
|
+
tags,
|
|
335
|
+
summary: "Dismiss the open flags on a comment (staff)",
|
|
336
|
+
description: "Staff only (owner, admin, instructor). Closes every open flag on the comment and leaves it up.",
|
|
337
|
+
params: CommunityCommentParam,
|
|
338
|
+
body: ResolveCommentFlags,
|
|
339
|
+
response: {
|
|
340
|
+
204: z.void(),
|
|
341
|
+
...refusals(403, 404)
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
handler: async (req, reply) => {
|
|
345
|
+
const scope = await resolveScope(container, req);
|
|
346
|
+
await container.discussion.resolveReports(scope.orgId, req.params.commentId, {
|
|
347
|
+
id: scope.orgUserId,
|
|
348
|
+
role: scope.role
|
|
349
|
+
}, req.params.communityId);
|
|
350
|
+
return reply.code(204).send();
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
r.route({
|
|
354
|
+
method: "POST",
|
|
355
|
+
url: "/api/communities/:communityId/messages/:messageId/flags/resolution",
|
|
356
|
+
schema: {
|
|
357
|
+
operationId: "resolveCommunityMessageFlags",
|
|
358
|
+
tags,
|
|
359
|
+
summary: "Dismiss the open flags on a message (staff)",
|
|
360
|
+
description: "Staff only (owner, admin, instructor). Closes every open flag on the message and leaves it up.",
|
|
361
|
+
params: CommunityMessageParam,
|
|
362
|
+
body: ResolveMessageFlags,
|
|
363
|
+
response: {
|
|
364
|
+
204: z.void(),
|
|
365
|
+
...refusals(403, 404)
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
handler: async (req, reply) => {
|
|
369
|
+
const scope = await resolveScope(container, req);
|
|
370
|
+
await container.chats.resolveMessageFlags(scope.orgId, {
|
|
371
|
+
id: scope.orgUserId,
|
|
372
|
+
role: scope.role
|
|
373
|
+
}, req.params.messageId, req.body.resolution, req.params.communityId);
|
|
374
|
+
return reply.code(204).send();
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
r.route({
|
|
378
|
+
method: "POST",
|
|
379
|
+
url: "/api/communities/:communityId/chats/:chatId/lock",
|
|
380
|
+
schema: {
|
|
381
|
+
operationId: "lockCommunityChat",
|
|
382
|
+
tags,
|
|
383
|
+
summary: "Lock a chat (staff)",
|
|
384
|
+
description: "Staff only (owner, admin, instructor). Nobody can send or edit messages until it is unlocked; its open message flags are resolved as `locked`. Locking a locked chat returns it unchanged.",
|
|
385
|
+
params: CommunityChatParam,
|
|
386
|
+
response: {
|
|
387
|
+
200: Chat,
|
|
388
|
+
...refusals(403, 404)
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
handler: async (req) => {
|
|
392
|
+
const scope = await resolveScope(container, req);
|
|
393
|
+
return container.chats.lock(scope.orgId, {
|
|
394
|
+
id: scope.orgUserId,
|
|
395
|
+
role: scope.role
|
|
396
|
+
}, req.params.chatId, req.params.communityId);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
r.route({
|
|
400
|
+
method: "POST",
|
|
401
|
+
url: "/api/communities/:communityId/chats/:chatId/unlock",
|
|
402
|
+
schema: {
|
|
403
|
+
operationId: "unlockCommunityChat",
|
|
404
|
+
tags,
|
|
405
|
+
summary: "Unlock a chat (staff)",
|
|
406
|
+
description: "Staff only (owner, admin, instructor). Lets participants send messages again. Unlocking an unlocked chat returns it unchanged.",
|
|
407
|
+
params: CommunityChatParam,
|
|
408
|
+
response: {
|
|
409
|
+
200: Chat,
|
|
410
|
+
...refusals(403, 404)
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
handler: async (req) => {
|
|
414
|
+
const scope = await resolveScope(container, req);
|
|
415
|
+
return container.chats.unlock(scope.orgId, {
|
|
416
|
+
id: scope.orgUserId,
|
|
417
|
+
role: scope.role
|
|
418
|
+
}, req.params.chatId, req.params.communityId);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
//#endregion
|
|
423
|
+
export { communityModerationRoutes };
|
|
424
|
+
|
|
425
|
+
//# sourceMappingURL=community-moderation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"community-moderation.js","names":[],"sources":["../../../src/http/routes/community-moderation.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 { NotFoundError } from '@headless-lms/core/shared/errors';\nimport { resolveScope } from '../scope.js';\nimport {\n Chat,\n CommentView,\n CommunityChatParam,\n CommunityCommentParam,\n CommunityIdParam,\n CommunityMembersPage,\n CommunityMembersQuery,\n CommunityMessageParam,\n CommunityPostParam,\n CommunitySpaceParam,\n OrgUserBody,\n PageQuery,\n PostView,\n ResolveCommentFlags,\n ResolveMessageFlags,\n ResolvePostFlags,\n SpaceMember,\n SpaceMemberParam,\n SpaceMembersPage,\n SpaceQueue,\n refusals,\n} from '../schemas/index.js';\n\nexport async function communityModerationRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Communities'];\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/members',\n schema: {\n operationId: 'listCommunityMembers',\n tags,\n summary: 'List the people who hold a community (staff)',\n description:\n 'Staff only (owner, admin, instructor). Everyone with active access to the community, granted directly or through a bundle, paged and searched by name or email.',\n params: CommunityIdParam,\n querystring: CommunityMembersQuery,\n response: { 200: CommunityMembersPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const page = await container.reporting.admin.communityMembers(\n scope.orgId,\n req.params.communityId,\n req.query,\n );\n if (!page) {\n throw new NotFoundError('Community', req.params.communityId);\n }\n return page;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/spaces/:spaceId/members',\n schema: {\n operationId: 'listCommunitySpaceMembers',\n tags,\n summary: 'List the members, requests and invitations of a space (staff)',\n description:\n \"Staff only (owner, admin, instructor). The space's members, its open requests to join and its open invitations.\",\n params: CommunitySpaceParam,\n querystring: PageQuery,\n response: { 200: SpaceMembersPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.entitlements.listSpaceMembers(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n req.params.spaceId,\n req.query,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces/:spaceId/members',\n schema: {\n operationId: 'addCommunitySpaceMember',\n tags,\n summary: 'Add a person to a space (staff)',\n description:\n 'Staff only (owner, admin, instructor). Places a member of the community straight into the space, with no request or invitation. 409 when the space is open to all members, the person is already placed, or the space is full.',\n params: CommunitySpaceParam,\n body: OrgUserBody,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const member = await container.entitlements.addSpaceMember(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n req.params.spaceId,\n req.body.orgUserId,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces/:spaceId/invites',\n schema: {\n operationId: 'inviteCommunitySpaceMember',\n tags,\n summary: 'Invite a person into a space (staff)',\n description:\n 'Staff only (owner, admin, instructor). Invites a member of the community into a `by_invite` space; they join when they accept. 403 when the person is staff or does not hold the community; 409 when the space is not invite-only or the person is already placed.',\n params: CommunitySpaceParam,\n body: OrgUserBody,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n const member = await container.entitlements.invite(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n req.params.spaceId,\n req.body.orgUserId,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces/:spaceId/members/:orgUserId/approval',\n schema: {\n operationId: 'approveCommunitySpaceRequest',\n tags,\n summary: \"Approve a person's request to join a space (staff)\",\n description:\n 'Staff only (owner, admin, instructor). Makes the requester an active member. 404 when the person has no open request; 409 when the space is full or they are already a member.',\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.entitlements.approve(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces/:spaceId/members/:orgUserId/decline',\n schema: {\n operationId: 'declineCommunitySpaceRequest',\n tags,\n summary: \"Decline a person's request to join a space (staff)\",\n description:\n 'Staff only (owner, admin, instructor). Closes the open request. 404 when the person has no open request; 409 when they are already a member.',\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.entitlements.decline(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces/:spaceId/members/:orgUserId/removal',\n schema: {\n operationId: 'removeCommunitySpaceMember',\n tags,\n summary: 'Remove a person from a space (staff)',\n description:\n 'Staff only (owner, admin, instructor). Revokes the placement, whether it is a membership, a request or an invitation. 409 when the space is open to all members or the placement is already revoked.',\n params: SpaceMemberParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.entitlements.remove(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/spaces/:spaceId/queue',\n schema: {\n operationId: 'getCommunitySpaceQueue',\n tags,\n summary: 'The moderation queue of a space (staff)',\n description:\n 'Staff only (owner, admin, instructor). 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 scope = await resolveScope(container, req);\n return container.posts.moderationQueue(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/posts/:postId/approval',\n schema: {\n operationId: 'approveCommunityPost',\n tags,\n summary: 'Publish a post that is awaiting review (staff)',\n description:\n 'Staff only (owner, admin, instructor). 409 when the post is not awaiting review.',\n params: CommunityPostParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.posts.approve(\n scope.orgId,\n req.params.postId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/posts/:postId/removal',\n schema: {\n operationId: 'removeCommunityPost',\n tags,\n summary: 'Remove a post (staff)',\n description:\n 'Staff only (owner, admin, instructor). Takes a published or pending post down and resolves its open flags as `removed`. 409 when it is already removed or was never submitted.',\n params: CommunityPostParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.posts.remove(\n scope.orgId,\n req.params.postId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/posts/:postId/restoration',\n schema: {\n operationId: 'restoreCommunityPost',\n tags,\n summary: 'Restore a removed post (staff)',\n description: 'Staff only (owner, admin, instructor). 409 when the post is not removed.',\n params: CommunityPostParam,\n response: { 200: PostView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.posts.restore(\n scope.orgId,\n req.params.postId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/posts/:postId/flags/resolution',\n schema: {\n operationId: 'resolveCommunityPostFlags',\n tags,\n summary: 'Dismiss the open flags on a post (staff)',\n description:\n 'Staff only (owner, admin, instructor). Closes every open flag on the post and leaves it up.',\n params: CommunityPostParam,\n body: ResolvePostFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await container.posts.resolveFlags(\n scope.orgId,\n req.params.postId,\n { id: scope.orgUserId, role: scope.role },\n req.body.resolution,\n req.params.communityId,\n );\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/comments/:commentId/removal',\n schema: {\n operationId: 'removeCommunityComment',\n tags,\n summary: 'Remove a comment on a post (staff)',\n description:\n 'Staff only (owner, admin, instructor). Hides the comment and resolves its open flags as `removed`. Removing a removed comment returns it unchanged.',\n params: CommunityCommentParam,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.discussion.remove(\n scope.orgId,\n req.params.commentId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/comments/:commentId/restoration',\n schema: {\n operationId: 'restoreCommunityComment',\n tags,\n summary: 'Restore a removed comment on a post (staff)',\n description:\n 'Staff only (owner, admin, instructor). Publishes the comment again. Restoring a comment that is not removed returns it unchanged.',\n params: CommunityCommentParam,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.discussion.restore(\n scope.orgId,\n req.params.commentId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/comments/:commentId/flags/resolution',\n schema: {\n operationId: 'resolveCommunityCommentFlags',\n tags,\n summary: 'Dismiss the open flags on a comment (staff)',\n description:\n 'Staff only (owner, admin, instructor). Closes every open flag on the comment and leaves it up.',\n params: CommunityCommentParam,\n body: ResolveCommentFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await container.discussion.resolveReports(\n scope.orgId,\n req.params.commentId,\n { id: scope.orgUserId, role: scope.role },\n req.params.communityId,\n );\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/messages/:messageId/flags/resolution',\n schema: {\n operationId: 'resolveCommunityMessageFlags',\n tags,\n summary: 'Dismiss the open flags on a message (staff)',\n description:\n 'Staff only (owner, admin, instructor). Closes every open flag on the message and leaves it up.',\n params: CommunityMessageParam,\n body: ResolveMessageFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const scope = await resolveScope(container, req);\n await container.chats.resolveMessageFlags(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.messageId,\n req.body.resolution,\n req.params.communityId,\n );\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/chats/:chatId/lock',\n schema: {\n operationId: 'lockCommunityChat',\n tags,\n summary: 'Lock a chat (staff)',\n description:\n 'Staff only (owner, admin, instructor). Nobody can send or edit messages until it is unlocked; its open message flags are resolved as `locked`. Locking a locked chat returns it unchanged.',\n params: CommunityChatParam,\n response: { 200: Chat, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.chats.lock(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.chatId,\n req.params.communityId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/chats/:chatId/unlock',\n schema: {\n operationId: 'unlockCommunityChat',\n tags,\n summary: 'Unlock a chat (staff)',\n description:\n 'Staff only (owner, admin, instructor). Lets participants send messages again. Unlocking an unlocked chat returns it unchanged.',\n params: CommunityChatParam,\n response: { 200: Chat, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n return container.chats.unlock(\n scope.orgId,\n { id: scope.orgUserId, role: scope.role },\n req.params.chatId,\n req.params.communityId,\n );\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { refusals } from "../schemas/shared.js";
|
|
1
2
|
import { resolveScope } from "../scope.js";
|
|
2
|
-
import { ErrorBody } from "../schemas/shared.js";
|
|
3
3
|
import { CourseIdParam } from "../schemas/content.js";
|
|
4
4
|
import { EnrollmentSeries, EnrollmentSeriesQuery } from "../schemas/dashboard.js";
|
|
5
5
|
import { CourseAnalytics } from "../schemas/course-analytics.js";
|
|
@@ -15,16 +15,17 @@ async function courseAnalyticsRoutes(app, container) {
|
|
|
15
15
|
schema: {
|
|
16
16
|
operationId: "getCourseAnalytics",
|
|
17
17
|
tags: ["Reporting"],
|
|
18
|
-
summary: "Completion and engagement stats for a course",
|
|
18
|
+
summary: "Completion and engagement stats for a course (staff)",
|
|
19
|
+
description: "Staff only (owner, admin, instructor).",
|
|
19
20
|
params: CourseIdParam,
|
|
20
21
|
response: {
|
|
21
22
|
200: CourseAnalytics,
|
|
22
|
-
404
|
|
23
|
+
...refusals(403, 404)
|
|
23
24
|
}
|
|
24
25
|
},
|
|
25
26
|
handler: async (req) => {
|
|
26
27
|
const scope = await resolveScope(container, req);
|
|
27
|
-
const analytics = await container.reporting.
|
|
28
|
+
const analytics = await container.reporting.admin.courseAnalytics(scope.orgId, req.params.id);
|
|
28
29
|
if (!analytics) throw new NotFoundError("Course", req.params.id);
|
|
29
30
|
return analytics;
|
|
30
31
|
}
|
|
@@ -36,17 +37,18 @@ async function courseAnalyticsRoutes(app, container) {
|
|
|
36
37
|
schema: {
|
|
37
38
|
operationId: "getCourseEnrollmentSeries",
|
|
38
39
|
tags: ["Reporting"],
|
|
39
|
-
summary: "Enrollments granted per day for a course over a trailing window",
|
|
40
|
+
summary: "Enrollments granted per day for a course over a trailing window (staff)",
|
|
41
|
+
description: "Staff only (owner, admin, instructor).",
|
|
40
42
|
params: CourseIdParam,
|
|
41
43
|
querystring: EnrollmentSeriesQuery,
|
|
42
44
|
response: {
|
|
43
45
|
200: EnrollmentSeries,
|
|
44
|
-
404
|
|
46
|
+
...refusals(403, 404)
|
|
45
47
|
}
|
|
46
48
|
},
|
|
47
49
|
handler: async (req) => {
|
|
48
50
|
const scope = await resolveScope(container, req);
|
|
49
|
-
const series = await container.reporting.
|
|
51
|
+
const series = await container.reporting.admin.courseEnrollments(scope.orgId, req.params.id, req.query.days);
|
|
50
52
|
if (!series) throw new NotFoundError("Course", req.params.id);
|
|
51
53
|
return series;
|
|
52
54
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"course-analytics.js","names":[],"sources":["../../../src/http/routes/course-analytics.ts"],"sourcesContent":["// HTTP routes for per-course analytics (reporting).\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n CourseAnalytics,\n CourseIdParam,\n EnrollmentSeries,\n EnrollmentSeriesQuery,\n
|
|
1
|
+
{"version":3,"file":"course-analytics.js","names":[],"sources":["../../../src/http/routes/course-analytics.ts"],"sourcesContent":["// HTTP routes for per-course analytics (reporting).\nimport type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport {\n CourseAnalytics,\n CourseIdParam,\n EnrollmentSeries,\n EnrollmentSeriesQuery,\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 courseAnalyticsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n\n r.route({\n method: 'GET',\n url: '/api/courses/:id/analytics',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getCourseAnalytics',\n tags: ['Reporting'],\n summary: 'Completion and engagement stats for a course (staff)',\n description: 'Staff only (owner, admin, instructor).',\n params: CourseIdParam,\n response: { 200: CourseAnalytics, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const analytics = await container.reporting.admin.courseAnalytics(scope.orgId, req.params.id);\n if (!analytics) {\n throw new NotFoundError('Course', req.params.id);\n }\n return analytics;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/courses/:id/analytics/enrollments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getCourseEnrollmentSeries',\n tags: ['Reporting'],\n summary: 'Enrollments granted per day for a course over a trailing window (staff)',\n description: 'Staff only (owner, admin, instructor).',\n params: CourseIdParam,\n querystring: EnrollmentSeriesQuery,\n response: { 200: EnrollmentSeries, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const scope = await resolveScope(container, req);\n const series = await container.reporting.admin.courseEnrollments(\n scope.orgId,\n req.params.id,\n req.query.days,\n );\n if (!series) {\n throw new NotFoundError('Course', req.params.id);\n }\n return series;\n },\n });\n}\n"],"mappings":";;;;;;;;AAcA,eAAsB,sBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAEhD,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;IAAiB,GAAG,SAAS,KAAK,GAAG;GAAE;EAC1D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,YAAY,MAAM,UAAU,UAAU,MAAM,gBAAgB,MAAM,OAAO,IAAI,OAAO,EAAE;GAC5F,IAAI,CAAC,WACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,EAAE;GAEjD,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,aAAa;GACb,UAAU;IAAE,KAAK;IAAkB,GAAG,SAAS,KAAK,GAAG;GAAE;EAC3D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,aAAa,WAAW,GAAG;GAC/C,MAAM,SAAS,MAAM,UAAU,UAAU,MAAM,kBAC7C,MAAM,OACN,IAAI,OAAO,IACX,IAAI,MAAM,IACZ;GACA,IAAI,CAAC,QACH,MAAM,IAAI,cAAc,UAAU,IAAI,OAAO,EAAE;GAEjD,OAAO;EACT;CACF,CAAC;AACH"}
|