@headless-lms/server 0.3.0 → 0.5.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/README.md +14 -0
- package/dist/app/community-mail.d.ts +10 -10
- package/dist/app/community-mail.js +72 -28
- package/dist/app/community-mail.js.map +1 -1
- package/dist/app/container.d.ts +48 -10
- package/dist/app/container.js +268 -48
- package/dist/app/container.js.map +1 -1
- package/dist/app/notification-channels.d.ts +47 -0
- package/dist/app/notification-channels.js +175 -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 +74 -0
- package/dist/app/notification-reads.js.map +1 -0
- package/dist/app/platform.d.ts +14 -0
- package/dist/app/platform.js +14 -0
- package/dist/app/platform.js.map +1 -0
- package/dist/http/config.d.ts +4 -0
- package/dist/http/errors.d.ts +1 -0
- package/dist/http/errors.js +24 -4
- package/dist/http/errors.js.map +1 -1
- package/dist/http/fastify.d.ts +8 -0
- package/dist/http/mcp/catalogue.d.ts +11 -9
- package/dist/http/mcp/catalogue.js +15 -21
- package/dist/http/mcp/catalogue.js.map +1 -1
- package/dist/http/mcp/challenge.js +3 -3
- package/dist/http/mcp/challenge.js.map +1 -1
- package/dist/http/mcp/route.js +7 -7
- package/dist/http/mcp/route.js.map +1 -1
- package/dist/http/mcp/test-support.d.ts +6 -5
- package/dist/http/mcp/test-support.js +32 -11
- package/dist/http/mcp/test-support.js.map +1 -1
- package/dist/http/mcp/tool.d.ts +3 -9
- package/dist/http/mcp/tool.js +14 -6
- package/dist/http/mcp/tool.js.map +1 -1
- package/dist/http/mcp/tools/automations.js +10 -20
- package/dist/http/mcp/tools/automations.js.map +1 -1
- package/dist/http/mcp/tools/catalog.js +25 -42
- package/dist/http/mcp/tools/catalog.js.map +1 -1
- package/dist/http/mcp/tools/comments.js +9 -19
- package/dist/http/mcp/tools/comments.js.map +1 -1
- package/dist/http/mcp/tools/communities.js +16 -31
- package/dist/http/mcp/tools/communities.js.map +1 -1
- package/dist/http/mcp/tools/community-members.js +9 -16
- package/dist/http/mcp/tools/community-members.js.map +1 -1
- package/dist/http/mcp/tools/community-moderation.js +13 -25
- package/dist/http/mcp/tools/community-moderation.js.map +1 -1
- package/dist/http/mcp/tools/courses.js +11 -21
- package/dist/http/mcp/tools/courses.js.map +1 -1
- package/dist/http/mcp/tools/events.d.ts +6 -0
- package/dist/http/mcp/tools/events.js +170 -0
- package/dist/http/mcp/tools/events.js.map +1 -0
- package/dist/http/mcp/tools/index.d.ts +5 -1
- package/dist/http/mcp/tools/index.js +45 -10
- package/dist/http/mcp/tools/index.js.map +1 -1
- package/dist/http/mcp/tools/organization.d.ts +6 -0
- package/dist/http/mcp/tools/organization.js +214 -0
- package/dist/http/mcp/tools/organization.js.map +1 -0
- package/dist/http/mcp/tools/people.js +30 -49
- package/dist/http/mcp/tools/people.js.map +1 -1
- package/dist/http/plugins/auth.js +37 -8
- package/dist/http/plugins/auth.js.map +1 -1
- package/dist/http/plugins/error-handler.d.ts +5 -1
- package/dist/http/plugins/error-handler.js +13 -3
- package/dist/http/plugins/error-handler.js.map +1 -1
- package/dist/http/plugins/openapi.js +18 -2
- package/dist/http/plugins/openapi.js.map +1 -1
- package/dist/http/plugins/platform.d.ts +7 -0
- package/dist/http/plugins/platform.js +13 -0
- package/dist/http/plugins/platform.js.map +1 -0
- package/dist/http/routes/access.js +54 -36
- package/dist/http/routes/access.js.map +1 -1
- package/dist/http/routes/activities.js +45 -45
- package/dist/http/routes/activities.js.map +1 -1
- package/dist/http/routes/assets.js +30 -65
- package/dist/http/routes/assets.js.map +1 -1
- package/dist/http/routes/automations.js +37 -42
- package/dist/http/routes/automations.js.map +1 -1
- package/dist/http/routes/bundles.js +33 -36
- package/dist/http/routes/bundles.js.map +1 -1
- package/dist/http/routes/communities.js +144 -136
- package/dist/http/routes/communities.js.map +1 -1
- package/dist/http/routes/community-moderation.js +83 -137
- package/dist/http/routes/community-moderation.js.map +1 -1
- package/dist/http/routes/course-analytics.js +32 -15
- package/dist/http/routes/course-analytics.js.map +1 -1
- package/dist/http/routes/courses.js +25 -32
- package/dist/http/routes/courses.js.map +1 -1
- package/dist/http/routes/dashboard.js +9 -9
- package/dist/http/routes/dashboard.js.map +1 -1
- package/dist/http/routes/discussion.js +21 -39
- package/dist/http/routes/discussion.js.map +1 -1
- package/dist/http/routes/downloads.js +41 -47
- package/dist/http/routes/downloads.js.map +1 -1
- package/dist/http/routes/entitlements.js +19 -23
- package/dist/http/routes/entitlements.js.map +1 -1
- package/dist/http/routes/events.d.ts +7 -0
- package/dist/http/routes/events.js +333 -0
- package/dist/http/routes/events.js.map +1 -0
- package/dist/http/routes/integrations.js +40 -73
- package/dist/http/routes/integrations.js.map +1 -1
- package/dist/http/routes/member/assets.js +10 -13
- package/dist/http/routes/member/assets.js.map +1 -1
- package/dist/http/routes/member/chats.js +177 -75
- package/dist/http/routes/member/chats.js.map +1 -1
- package/dist/http/routes/member/comments.js +65 -26
- package/dist/http/routes/member/comments.js.map +1 -1
- package/dist/http/routes/member/community.js +68 -51
- package/dist/http/routes/member/community.js.map +1 -1
- package/dist/http/routes/member/courses.js +12 -19
- package/dist/http/routes/member/courses.js.map +1 -1
- package/dist/http/routes/member/devices.d.ts +7 -0
- package/dist/http/routes/member/devices.js +97 -0
- package/dist/http/routes/member/devices.js.map +1 -0
- package/dist/http/routes/member/downloads.js +6 -9
- package/dist/http/routes/member/downloads.js.map +1 -1
- package/dist/http/routes/member/events.d.ts +7 -0
- package/dist/http/routes/member/events.js +386 -0
- package/dist/http/routes/member/events.js.map +1 -0
- package/dist/http/routes/member/files.js +71 -46
- package/dist/http/routes/member/files.js.map +1 -1
- package/dist/http/routes/member/index.js +6 -0
- package/dist/http/routes/member/index.js.map +1 -1
- package/dist/http/routes/member/notification-settings.d.ts +7 -0
- package/dist/http/routes/member/notification-settings.js +100 -0
- package/dist/http/routes/member/notification-settings.js.map +1 -0
- package/dist/http/routes/member/notifications.js +69 -45
- package/dist/http/routes/member/notifications.js.map +1 -1
- package/dist/http/routes/member/org.js +5 -15
- package/dist/http/routes/member/org.js.map +1 -1
- package/dist/http/routes/member/posts.js +94 -95
- package/dist/http/routes/member/posts.js.map +1 -1
- package/dist/http/routes/member/profile.js +15 -6
- package/dist/http/routes/member/profile.js.map +1 -1
- package/dist/http/routes/member/progress.js +7 -11
- package/dist/http/routes/member/progress.js.map +1 -1
- package/dist/http/routes/organizations.js +415 -110
- package/dist/http/routes/organizations.js.map +1 -1
- package/dist/http/routes/platform.d.ts +7 -0
- package/dist/http/routes/platform.js +61 -0
- package/dist/http/routes/platform.js.map +1 -0
- package/dist/http/routes/students.js +12 -75
- package/dist/http/routes/students.js.map +1 -1
- package/dist/http/routes/webhooks.js +30 -39
- package/dist/http/routes/webhooks.js.map +1 -1
- package/dist/http/routes.js +9 -0
- package/dist/http/routes.js.map +1 -1
- package/dist/http/schemas/access.d.ts +346 -64
- package/dist/http/schemas/access.js +39 -12
- package/dist/http/schemas/access.js.map +1 -1
- package/dist/http/schemas/assets.d.ts +13 -8
- package/dist/http/schemas/assets.js +6 -5
- package/dist/http/schemas/assets.js.map +1 -1
- package/dist/http/schemas/automations.d.ts +4 -8
- package/dist/http/schemas/chats.d.ts +238 -36
- package/dist/http/schemas/chats.js +14 -7
- package/dist/http/schemas/chats.js.map +1 -1
- package/dist/http/schemas/community.d.ts +140 -33
- package/dist/http/schemas/community.js +25 -9
- package/dist/http/schemas/community.js.map +1 -1
- package/dist/http/schemas/course-analytics.d.ts +51 -1
- package/dist/http/schemas/course-analytics.js +19 -1
- package/dist/http/schemas/course-analytics.js.map +1 -1
- package/dist/http/schemas/dashboard.d.ts +1 -0
- package/dist/http/schemas/dashboard.js +2 -1
- package/dist/http/schemas/dashboard.js.map +1 -1
- package/dist/http/schemas/discussion.d.ts +45 -73
- package/dist/http/schemas/discussion.js +16 -4
- package/dist/http/schemas/discussion.js.map +1 -1
- package/dist/http/schemas/entitlements.d.ts +9 -12
- package/dist/http/schemas/entitlements.js +11 -5
- package/dist/http/schemas/entitlements.js.map +1 -1
- package/dist/http/schemas/events.d.ts +527 -0
- package/dist/http/schemas/events.js +64 -0
- package/dist/http/schemas/events.js.map +1 -0
- package/dist/http/schemas/files.d.ts +15 -5
- package/dist/http/schemas/files.js +10 -2
- package/dist/http/schemas/files.js.map +1 -1
- package/dist/http/schemas/index.d.ts +14 -12
- package/dist/http/schemas/index.js +14 -12
- package/dist/http/schemas/invites.d.ts +3 -47
- package/dist/http/schemas/invites.js +4 -25
- package/dist/http/schemas/invites.js.map +1 -1
- package/dist/http/schemas/member.d.ts +94 -7
- package/dist/http/schemas/member.js +7 -3
- package/dist/http/schemas/member.js.map +1 -1
- package/dist/http/schemas/members.d.ts +329 -65
- package/dist/http/schemas/members.js +67 -25
- package/dist/http/schemas/members.js.map +1 -1
- package/dist/http/schemas/notifications.d.ts +502 -175
- package/dist/http/schemas/notifications.js +148 -28
- package/dist/http/schemas/notifications.js.map +1 -1
- package/dist/http/schemas/platform.d.ts +37 -0
- package/dist/http/schemas/platform.js +19 -0
- package/dist/http/schemas/platform.js.map +1 -0
- package/dist/http/schemas/posts.d.ts +92 -38
- package/dist/http/schemas/posts.js +4 -4
- package/dist/http/schemas/posts.js.map +1 -1
- package/dist/http/schemas/profile.d.ts +1 -1
- package/dist/http/schemas/shared.d.ts +10 -13
- package/dist/http/schemas/shared.js +3 -11
- package/dist/http/schemas/shared.js.map +1 -1
- package/dist/http/schemas/students.d.ts +22 -0
- package/dist/http/schemas/students.js +3 -1
- package/dist/http/schemas/students.js.map +1 -1
- package/dist/http/scope.d.ts +9 -12
- package/dist/http/scope.js +30 -35
- package/dist/http/scope.js.map +1 -1
- package/dist/http/server.js +9 -0
- package/dist/http/server.js.map +1 -1
- package/dist/http/shutdown.d.ts +11 -0
- package/dist/http/shutdown.js +40 -0
- package/dist/http/shutdown.js.map +1 -0
- package/dist/http/sse.d.ts +19 -0
- package/dist/http/sse.js +104 -0
- package/dist/http/sse.js.map +1 -0
- package/dist/http/test-caller.d.ts +13 -0
- package/dist/http/test-caller.js +14 -0
- package/dist/http/test-caller.js.map +1 -0
- package/dist/http/well-known.js +2 -2
- package/dist/http/well-known.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/packages/server/package.js +1 -1
- package/package.json +7 -6
- package/dist/http/routes/member/actor.d.ts +0 -14
- package/dist/http/routes/member/actor.js +0 -11
- package/dist/http/routes/member/actor.js.map +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { refusals } from "../../schemas/shared.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveActor } from "../../scope.js";
|
|
3
|
+
import { ActivityComments, Comment, CommentIdParam, CommentReport, CommentView, DiscussionActivityParam, EditComment, PostComment, Reactions, ReportComment, ResolveCommentFlags, SetReaction } from "../../schemas/discussion.js";
|
|
3
4
|
import "../../schemas/index.js";
|
|
4
|
-
import { actorOf } from "./actor.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region src/http/routes/member/comments.ts
|
|
7
7
|
async function memberCommentsRoutes(app, container) {
|
|
@@ -16,7 +16,7 @@ async function memberCommentsRoutes(app, container) {
|
|
|
16
16
|
operationId: "listActivityComments",
|
|
17
17
|
tags,
|
|
18
18
|
summary: "Read an activity's comments",
|
|
19
|
-
description: "For
|
|
19
|
+
description: "For anyone who can open the course: through a grant, or with `courses:read` on it. Returns the activity's resolved comment settings with the comments; removed comments keep their place without their body.",
|
|
20
20
|
params: DiscussionActivityParam,
|
|
21
21
|
response: {
|
|
22
22
|
200: ActivityComments,
|
|
@@ -24,8 +24,8 @@ async function memberCommentsRoutes(app, container) {
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
handler: async (req) => {
|
|
27
|
-
const
|
|
28
|
-
return discussion.activityComments(req.
|
|
27
|
+
const actor = await resolveActor(container, req);
|
|
28
|
+
return discussion.activityComments(req.params.activityId, actor);
|
|
29
29
|
}
|
|
30
30
|
});
|
|
31
31
|
r.route({
|
|
@@ -36,7 +36,7 @@ async function memberCommentsRoutes(app, container) {
|
|
|
36
36
|
operationId: "postComment",
|
|
37
37
|
tags,
|
|
38
38
|
summary: "Post a comment or reply on an activity",
|
|
39
|
-
description: "For
|
|
39
|
+
description: "For anyone who can open the course, while discussion is on for the activity (403 otherwise). Lands pending when the course requires review and you do not hold `courses:moderate` on it. Replies nest one level and need threading on.",
|
|
40
40
|
params: DiscussionActivityParam,
|
|
41
41
|
body: PostComment,
|
|
42
42
|
response: {
|
|
@@ -45,13 +45,12 @@ async function memberCommentsRoutes(app, container) {
|
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
handler: async (req, reply) => {
|
|
48
|
-
const
|
|
49
|
-
const comment = await discussion.postComment(
|
|
50
|
-
actor: orgUser,
|
|
48
|
+
const actor = await resolveActor(container, req);
|
|
49
|
+
const comment = await discussion.postComment({
|
|
51
50
|
activityId: req.params.activityId,
|
|
52
51
|
parentId: req.body.parentId,
|
|
53
52
|
body: req.body.body
|
|
54
|
-
});
|
|
53
|
+
}, actor);
|
|
55
54
|
return reply.code(201).send(comment);
|
|
56
55
|
}
|
|
57
56
|
});
|
|
@@ -63,7 +62,7 @@ async function memberCommentsRoutes(app, container) {
|
|
|
63
62
|
operationId: "editComment",
|
|
64
63
|
tags,
|
|
65
64
|
summary: "Revise your own comment",
|
|
66
|
-
description: "Author only; not even
|
|
65
|
+
description: "Author only; not even a moderator may revise it. Discussion must be open where the comment is, and a removed comment cannot be revised.",
|
|
67
66
|
params: CommentIdParam,
|
|
68
67
|
body: EditComment,
|
|
69
68
|
response: {
|
|
@@ -72,8 +71,8 @@ async function memberCommentsRoutes(app, container) {
|
|
|
72
71
|
}
|
|
73
72
|
},
|
|
74
73
|
handler: async (req) => {
|
|
75
|
-
const
|
|
76
|
-
return discussion.edit(req.
|
|
74
|
+
const actor = await resolveActor(container, req);
|
|
75
|
+
return discussion.edit(req.params.commentId, req.body.body, actor);
|
|
77
76
|
}
|
|
78
77
|
});
|
|
79
78
|
r.route({
|
|
@@ -84,7 +83,7 @@ async function memberCommentsRoutes(app, container) {
|
|
|
84
83
|
operationId: "deleteComment",
|
|
85
84
|
tags,
|
|
86
85
|
summary: "Delete your comment, or one you moderate",
|
|
87
|
-
description: "
|
|
86
|
+
description: "The author can delete their own comment; anyone else needs `communities:moderate` on the community for a post comment, or `courses:moderate` on the course for a lesson comment. The comment is marked removed and keeps its place in the thread. Deleting a removed comment returns it unchanged.",
|
|
88
87
|
params: CommentIdParam,
|
|
89
88
|
response: {
|
|
90
89
|
200: CommentView,
|
|
@@ -92,8 +91,8 @@ async function memberCommentsRoutes(app, container) {
|
|
|
92
91
|
}
|
|
93
92
|
},
|
|
94
93
|
handler: async (req) => {
|
|
95
|
-
const
|
|
96
|
-
return discussion.remove(req.
|
|
94
|
+
const actor = await resolveActor(container, req);
|
|
95
|
+
return discussion.remove(req.params.commentId, "any", actor);
|
|
97
96
|
}
|
|
98
97
|
});
|
|
99
98
|
r.route({
|
|
@@ -113,8 +112,8 @@ async function memberCommentsRoutes(app, container) {
|
|
|
113
112
|
}
|
|
114
113
|
},
|
|
115
114
|
handler: async (req) => {
|
|
116
|
-
const
|
|
117
|
-
return discussion.setReaction(req.
|
|
115
|
+
const actor = await resolveActor(container, req);
|
|
116
|
+
return discussion.setReaction(req.params.commentId, req.body.emoji, actor);
|
|
118
117
|
}
|
|
119
118
|
});
|
|
120
119
|
r.route({
|
|
@@ -133,8 +132,8 @@ async function memberCommentsRoutes(app, container) {
|
|
|
133
132
|
}
|
|
134
133
|
},
|
|
135
134
|
handler: async (req) => {
|
|
136
|
-
const
|
|
137
|
-
return discussion.setReaction(req.
|
|
135
|
+
const actor = await resolveActor(container, req);
|
|
136
|
+
return discussion.setReaction(req.params.commentId, null, actor);
|
|
138
137
|
}
|
|
139
138
|
});
|
|
140
139
|
r.route({
|
|
@@ -154,19 +153,19 @@ async function memberCommentsRoutes(app, container) {
|
|
|
154
153
|
}
|
|
155
154
|
},
|
|
156
155
|
handler: async (req) => {
|
|
157
|
-
const
|
|
158
|
-
return discussion.reportComment(req.
|
|
156
|
+
const actor = await resolveActor(container, req);
|
|
157
|
+
return discussion.reportComment(req.params.commentId, req.body.reason, actor);
|
|
159
158
|
}
|
|
160
159
|
});
|
|
161
160
|
r.route({
|
|
162
161
|
method: "POST",
|
|
163
|
-
url: "/api/member/comments/:commentId/flags/
|
|
162
|
+
url: "/api/member/comments/:commentId/flags/resolve",
|
|
164
163
|
preHandler: app.requireOrgSession,
|
|
165
164
|
schema: {
|
|
166
165
|
operationId: "resolveCommentFlags",
|
|
167
166
|
tags,
|
|
168
167
|
summary: "Dismiss the open flags on a comment in a space you moderate",
|
|
169
|
-
description: "
|
|
168
|
+
description: "Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. Closes every open flag and leaves the comment up.",
|
|
170
169
|
params: CommentIdParam,
|
|
171
170
|
body: ResolveCommentFlags,
|
|
172
171
|
response: {
|
|
@@ -175,11 +174,51 @@ async function memberCommentsRoutes(app, container) {
|
|
|
175
174
|
}
|
|
176
175
|
},
|
|
177
176
|
handler: async (req, reply) => {
|
|
178
|
-
const
|
|
179
|
-
await discussion.resolveReports(req.
|
|
177
|
+
const actor = await resolveActor(container, req);
|
|
178
|
+
await discussion.resolveReports(req.params.commentId, "any", actor);
|
|
180
179
|
return reply.code(204).send();
|
|
181
180
|
}
|
|
182
181
|
});
|
|
182
|
+
r.route({
|
|
183
|
+
method: "POST",
|
|
184
|
+
url: "/api/member/comments/:commentId/restore",
|
|
185
|
+
preHandler: app.requireOrgSession,
|
|
186
|
+
schema: {
|
|
187
|
+
operationId: "restoreMemberComment",
|
|
188
|
+
tags,
|
|
189
|
+
summary: "Restore a removed comment where you moderate",
|
|
190
|
+
description: "Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. Publishes the comment again; a comment that is not removed is returned unchanged.",
|
|
191
|
+
params: CommentIdParam,
|
|
192
|
+
response: {
|
|
193
|
+
200: CommentView,
|
|
194
|
+
...refusals(403, 404)
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
handler: async (req) => {
|
|
198
|
+
const actor = await resolveActor(container, req);
|
|
199
|
+
return discussion.restore(req.params.commentId, "any", actor);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
r.route({
|
|
203
|
+
method: "POST",
|
|
204
|
+
url: "/api/member/comments/:commentId/approve",
|
|
205
|
+
preHandler: app.requireOrgSession,
|
|
206
|
+
schema: {
|
|
207
|
+
operationId: "approveMemberComment",
|
|
208
|
+
tags,
|
|
209
|
+
summary: "Publish a comment awaiting review where you moderate",
|
|
210
|
+
description: "Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. 403 when the comment is not awaiting review.",
|
|
211
|
+
params: CommentIdParam,
|
|
212
|
+
response: {
|
|
213
|
+
200: Comment,
|
|
214
|
+
...refusals(403, 404)
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
handler: async (req) => {
|
|
218
|
+
const actor = await resolveActor(container, req);
|
|
219
|
+
return discussion.approve(req.params.commentId, actor);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
183
222
|
}
|
|
184
223
|
//#endregion
|
|
185
224
|
export { memberCommentsRoutes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comments.js","names":[],"sources":["../../../../src/http/routes/member/comments.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n ActivityComments,\n CommentIdParam,\n CommentReport,\n CommentView,\n DiscussionActivityParam,\n EditComment,\n PostComment,\n Reactions,\n ReportComment,\n ResolveCommentFlags,\n SetReaction,\n refusals,\n} from '../../schemas/index.js';\nimport type { Container } from '../../../app/container.js';\nimport { actorOf } from './actor.js';\n\nexport async function memberCommentsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const discussion = container.discussion;\n const tags = ['Discussion'];\n\n r.route({\n method: 'GET',\n url: '/api/member/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listActivityComments',\n tags,\n summary: \"Read an activity's comments\",\n description:\n \"For a student who can open the course, and staff. Returns the activity's resolved comment settings with the comments; removed comments keep their place without their body.\",\n params: DiscussionActivityParam,\n response: { 200: ActivityComments, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.activityComments(req.orgId, req.params.activityId, orgUser);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'postComment',\n tags,\n summary: 'Post a comment or reply on an activity',\n description:\n 'For a student who can open the course, and staff, while discussion is on for the activity (403 otherwise). Lands pending when the course requires review and you are not staff. Replies nest one level and need threading on.',\n params: DiscussionActivityParam,\n body: PostComment,\n response: { 201: CommentView, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n const comment = await discussion.postComment(req.orgId, {\n actor: orgUser,\n activityId: req.params.activityId,\n parentId: req.body.parentId,\n body: req.body.body,\n });\n return reply.code(201).send(comment);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'editComment',\n tags,\n summary: 'Revise your own comment',\n description:\n 'Author only; not even staff may revise it. Discussion must be open where the comment is, and a removed comment cannot be revised.',\n params: CommentIdParam,\n body: EditComment,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.edit(req.orgId, req.params.commentId, orgUser, req.body.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteComment',\n tags,\n summary: 'Delete your comment, or one you moderate',\n description:\n 'For the author, staff on an activity comment, or a moderator on a post comment. The comment is marked removed and keeps its place in the thread. Deleting a removed comment returns it unchanged.',\n params: CommentIdParam,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.remove(req.orgId, req.params.commentId, orgUser);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setCommentReaction',\n tags,\n summary: 'Set your reaction on a comment',\n description:\n 'For anyone who can read the comment, while reactions are on where it is (403 otherwise). Replaces any reaction you already had and returns the new counts.',\n params: CommentIdParam,\n body: SetReaction,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.setReaction(req.orgId, req.params.commentId, orgUser, req.body.emoji);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearCommentReaction',\n tags,\n summary: 'Withdraw your reaction from a comment',\n description: 'For anyone who can read the comment. Returns the new counts.',\n params: CommentIdParam,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.setReaction(req.orgId, req.params.commentId, orgUser, null);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/reports',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reportComment',\n tags,\n summary: 'Flag a comment for the moderators',\n description:\n 'For anyone who can read the comment. Flagging it again while your flag is open returns that flag.',\n params: CommentIdParam,\n body: ReportComment,\n response: { 200: CommentReport, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n return discussion.reportComment(req.orgId, req.params.commentId, orgUser, req.body.reason);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/flags/resolution',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resolveCommentFlags',\n tags,\n summary: 'Dismiss the open flags on a comment in a space you moderate',\n description:\n 'For a moderator of a post comment, or staff for an activity comment. Closes every open flag and leaves the comment up.',\n params: CommentIdParam,\n body: ResolveCommentFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const orgUser = await actorOf(container, req.orgId, req.userId);\n await discussion.resolveReports(req.orgId, req.params.commentId, orgUser);\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"comments.js","names":[],"sources":["../../../../src/http/routes/member/comments.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport {\n ActivityComments,\n Comment,\n CommentIdParam,\n CommentReport,\n CommentView,\n DiscussionActivityParam,\n EditComment,\n PostComment,\n Reactions,\n ReportComment,\n ResolveCommentFlags,\n SetReaction,\n refusals,\n} from '../../schemas/index.js';\nimport type { Container } from '../../../app/container.js';\nimport { resolveActor } from '../../scope.js';\n\nexport async function memberCommentsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const discussion = container.discussion;\n const tags = ['Discussion'];\n\n r.route({\n method: 'GET',\n url: '/api/member/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listActivityComments',\n tags,\n summary: \"Read an activity's comments\",\n description:\n \"For anyone who can open the course: through a grant, or with `courses:read` on it. Returns the activity's resolved comment settings with the comments; removed comments keep their place without their body.\",\n params: DiscussionActivityParam,\n response: { 200: ActivityComments, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.activityComments(req.params.activityId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/activities/:activityId/comments',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'postComment',\n tags,\n summary: 'Post a comment or reply on an activity',\n description:\n 'For anyone who can open the course, while discussion is on for the activity (403 otherwise). Lands pending when the course requires review and you do not hold `courses:moderate` on it. Replies nest one level and need threading on.',\n params: DiscussionActivityParam,\n body: PostComment,\n response: { 201: CommentView, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const comment = await discussion.postComment(\n { activityId: req.params.activityId, parentId: req.body.parentId, body: req.body.body },\n actor,\n );\n return reply.code(201).send(comment);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'editComment',\n tags,\n summary: 'Revise your own comment',\n description:\n 'Author only; not even a moderator may revise it. Discussion must be open where the comment is, and a removed comment cannot be revised.',\n params: CommentIdParam,\n body: EditComment,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.edit(req.params.commentId, req.body.body, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/comments/:commentId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteComment',\n tags,\n summary: 'Delete your comment, or one you moderate',\n description:\n 'The author can delete their own comment; anyone else needs `communities:moderate` on the community for a post comment, or `courses:moderate` on the course for a lesson comment. The comment is marked removed and keeps its place in the thread. Deleting a removed comment returns it unchanged.',\n params: CommentIdParam,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.remove(req.params.commentId, 'any', actor);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setCommentReaction',\n tags,\n summary: 'Set your reaction on a comment',\n description:\n 'For anyone who can read the comment, while reactions are on where it is (403 otherwise). Replaces any reaction you already had and returns the new counts.',\n params: CommentIdParam,\n body: SetReaction,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.setReaction(req.params.commentId, req.body.emoji, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/comments/:commentId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearCommentReaction',\n tags,\n summary: 'Withdraw your reaction from a comment',\n description: 'For anyone who can read the comment. Returns the new counts.',\n params: CommentIdParam,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.setReaction(req.params.commentId, null, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/reports',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'reportComment',\n tags,\n summary: 'Flag a comment for the moderators',\n description:\n 'For anyone who can read the comment. Flagging it again while your flag is open returns that flag.',\n params: CommentIdParam,\n body: ReportComment,\n response: { 200: CommentReport, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.reportComment(req.params.commentId, req.body.reason, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/flags/resolve',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resolveCommentFlags',\n tags,\n summary: 'Dismiss the open flags on a comment in a space you moderate',\n description:\n 'Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. Closes every open flag and leaves the comment up.',\n params: CommentIdParam,\n body: ResolveCommentFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await discussion.resolveReports(req.params.commentId, 'any', actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/restore',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'restoreMemberComment',\n tags,\n summary: 'Restore a removed comment where you moderate',\n description:\n 'Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. Publishes the comment again; a comment that is not removed is returned unchanged.',\n params: CommentIdParam,\n response: { 200: CommentView, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.restore(req.params.commentId, 'any', actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/comments/:commentId/approve',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approveMemberComment',\n tags,\n summary: 'Publish a comment awaiting review where you moderate',\n description:\n 'Needs `courses:moderate` on the course for an activity comment, or `communities:moderate` on the community for a post comment. 403 when the comment is not awaiting review.',\n params: CommentIdParam,\n response: { 200: Comment, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return discussion.approve(req.params.commentId, actor);\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { PageQuery, refusals } from "../../schemas/shared.js";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveActor } from "../../scope.js";
|
|
3
|
+
import { SetMute } from "../../schemas/discussion.js";
|
|
4
|
+
import { CommunityHome, CommunityHomeSpace, CommunitySpaceParam, DirectoryPage, DirectoryQuery, SpaceIdParam, SpaceMember, SpaceMemberParam, SpaceMembersPage } from "../../schemas/community.js";
|
|
3
5
|
import { CommunityParam, OrgUserBody } from "../../schemas/chats.js";
|
|
4
6
|
import "../../schemas/index.js";
|
|
5
|
-
import {
|
|
6
|
-
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
7
|
+
import { z } from "zod";
|
|
7
8
|
//#region src/http/routes/member/community.ts
|
|
8
9
|
async function memberCommunityRoutes(app, container) {
|
|
9
10
|
const r = app.withTypeProvider();
|
|
@@ -16,7 +17,7 @@ async function memberCommunityRoutes(app, container) {
|
|
|
16
17
|
operationId: "getMemberCommunityHome",
|
|
17
18
|
tags,
|
|
18
19
|
summary: "The spaces a member can see in a community",
|
|
19
|
-
description: "For a member of the community. Lists its spaces in order. Spaces the viewer can open come back in full; spaces they can see but not open come back locked, with only their name, count, rule and the viewer's standing. A by-invite space the viewer is not invited to or a member of is left out. 404 when the viewer is not a member.",
|
|
20
|
+
description: "For a member of the community, through a grant or `communities:read` on it. Lists its spaces in order. Spaces the viewer can open come back in full; spaces they can see but not open come back locked, with only their name, count, rule and the viewer's standing. A by-invite space the viewer is not invited to or a member of is left out, unless the viewer opens every space. `viewer` carries the viewer's permissions on the community, whether they open every space, and their team flag and role name. 404 when the viewer is not a member.",
|
|
20
21
|
params: CommunityParam,
|
|
21
22
|
response: {
|
|
22
23
|
200: CommunityHome,
|
|
@@ -24,10 +25,8 @@ async function memberCommunityRoutes(app, container) {
|
|
|
24
25
|
}
|
|
25
26
|
},
|
|
26
27
|
handler: async (req) => {
|
|
27
|
-
const actor = await
|
|
28
|
-
|
|
29
|
-
if (!home) throw new NotFoundError("Community", req.params.communityId);
|
|
30
|
-
return home;
|
|
28
|
+
const actor = await resolveActor(container, req);
|
|
29
|
+
return container.reporting.member.communityHome(req.params.communityId, actor);
|
|
31
30
|
}
|
|
32
31
|
});
|
|
33
32
|
r.route({
|
|
@@ -46,10 +45,8 @@ async function memberCommunityRoutes(app, container) {
|
|
|
46
45
|
}
|
|
47
46
|
},
|
|
48
47
|
handler: async (req) => {
|
|
49
|
-
const actor = await
|
|
50
|
-
|
|
51
|
-
if (!space) throw new NotFoundError("Space", req.params.spaceId);
|
|
52
|
-
return space;
|
|
48
|
+
const actor = await resolveActor(container, req);
|
|
49
|
+
return container.reporting.member.communitySpace(req.params.communityId, req.params.spaceId, actor);
|
|
53
50
|
}
|
|
54
51
|
});
|
|
55
52
|
r.route({
|
|
@@ -60,7 +57,7 @@ async function memberCommunityRoutes(app, container) {
|
|
|
60
57
|
operationId: "getMemberCommunityDirectory",
|
|
61
58
|
tags,
|
|
62
59
|
summary: "People in a community",
|
|
63
|
-
description: "For a member of the community: everyone who holds it, with their public profile. 404 when the viewer is not a member.",
|
|
60
|
+
description: "For a member of the community: everyone who holds it or holds `communities:read` on it, with their public profile, whether they are team here and under which role name, and whether they open every space. 404 when the viewer is not a member.",
|
|
64
61
|
params: CommunityParam,
|
|
65
62
|
querystring: DirectoryQuery,
|
|
66
63
|
response: {
|
|
@@ -69,10 +66,8 @@ async function memberCommunityRoutes(app, container) {
|
|
|
69
66
|
}
|
|
70
67
|
},
|
|
71
68
|
handler: async (req) => {
|
|
72
|
-
const actor = await
|
|
73
|
-
|
|
74
|
-
if (!page) throw new NotFoundError("Community", req.params.communityId);
|
|
75
|
-
return page;
|
|
69
|
+
const actor = await resolveActor(container, req);
|
|
70
|
+
return container.reporting.member.directory(req.params.communityId, req.query, actor);
|
|
76
71
|
}
|
|
77
72
|
});
|
|
78
73
|
r.route({
|
|
@@ -83,7 +78,7 @@ async function memberCommunityRoutes(app, container) {
|
|
|
83
78
|
operationId: "listSpaceMembers",
|
|
84
79
|
tags,
|
|
85
80
|
summary: "List the members of a space",
|
|
86
|
-
description: "For anyone who can open the space. Members see the active members;
|
|
81
|
+
description: "For anyone who can open the space. Members see the active members; holders of `communities:moderate` on the community also see open requests and invitations. Who opens every space holds no place, and is left out.",
|
|
87
82
|
params: CommunitySpaceParam,
|
|
88
83
|
querystring: PageQuery,
|
|
89
84
|
response: {
|
|
@@ -92,8 +87,8 @@ async function memberCommunityRoutes(app, container) {
|
|
|
92
87
|
}
|
|
93
88
|
},
|
|
94
89
|
handler: async (req) => {
|
|
95
|
-
const actor = await
|
|
96
|
-
return container.entitlements.listSpaceMembers(req.
|
|
90
|
+
const actor = await resolveActor(container, req);
|
|
91
|
+
return container.entitlements.listSpaceMembers(req.params.communityId, req.params.spaceId, req.query, actor);
|
|
97
92
|
}
|
|
98
93
|
});
|
|
99
94
|
r.route({
|
|
@@ -104,7 +99,7 @@ async function memberCommunityRoutes(app, container) {
|
|
|
104
99
|
operationId: "requestSpaceMembership",
|
|
105
100
|
tags,
|
|
106
101
|
summary: "Ask to join a space",
|
|
107
|
-
description: "For a member of the community who
|
|
102
|
+
description: "For a member of the community who does not open every space. Opens a request on a `by_request` space for a moderator to approve. 409 when the space is not `by_request` or you are already placed in it.",
|
|
108
103
|
params: CommunitySpaceParam,
|
|
109
104
|
response: {
|
|
110
105
|
201: SpaceMember,
|
|
@@ -112,8 +107,8 @@ async function memberCommunityRoutes(app, container) {
|
|
|
112
107
|
}
|
|
113
108
|
},
|
|
114
109
|
handler: async (req, reply) => {
|
|
115
|
-
const actor = await
|
|
116
|
-
const member = await container.entitlements.requestJoin(req.
|
|
110
|
+
const actor = await resolveActor(container, req);
|
|
111
|
+
const member = await container.entitlements.requestJoin(req.params.communityId, req.params.spaceId, actor);
|
|
117
112
|
return reply.code(201).send(member);
|
|
118
113
|
}
|
|
119
114
|
});
|
|
@@ -125,7 +120,7 @@ async function memberCommunityRoutes(app, container) {
|
|
|
125
120
|
operationId: "inviteSpaceMember",
|
|
126
121
|
tags,
|
|
127
122
|
summary: "Invite a person into a space you moderate",
|
|
128
|
-
description:
|
|
123
|
+
description: `Requires \`communities:moderate\`. On the community, as its member. Invites another member into a \`by_invite\` space; they join when they accept. 403 when the person opens every space or is not a member of the community; 409 when the space is not invite-only or the person is already placed.`,
|
|
129
124
|
params: CommunitySpaceParam,
|
|
130
125
|
body: OrgUserBody,
|
|
131
126
|
response: {
|
|
@@ -134,8 +129,8 @@ async function memberCommunityRoutes(app, container) {
|
|
|
134
129
|
}
|
|
135
130
|
},
|
|
136
131
|
handler: async (req, reply) => {
|
|
137
|
-
const actor = await
|
|
138
|
-
const member = await container.entitlements.invite(req.
|
|
132
|
+
const actor = await resolveActor(container, req);
|
|
133
|
+
const member = await container.entitlements.invite(req.params.communityId, req.params.spaceId, req.body.orgUserId, actor);
|
|
139
134
|
return reply.code(201).send(member);
|
|
140
135
|
}
|
|
141
136
|
});
|
|
@@ -147,7 +142,7 @@ async function memberCommunityRoutes(app, container) {
|
|
|
147
142
|
operationId: "addSpaceMember",
|
|
148
143
|
tags,
|
|
149
144
|
summary: "Add a person to a space you moderate",
|
|
150
|
-
description:
|
|
145
|
+
description: `Requires \`communities:moderate\`. On the community, as its member. Places another member straight into the space, with no request or invitation. 403 when the person opens every space; 409 when the space is open to all members, the person is already placed, or the space is full.`,
|
|
151
146
|
params: CommunitySpaceParam,
|
|
152
147
|
body: OrgUserBody,
|
|
153
148
|
response: {
|
|
@@ -156,20 +151,20 @@ async function memberCommunityRoutes(app, container) {
|
|
|
156
151
|
}
|
|
157
152
|
},
|
|
158
153
|
handler: async (req, reply) => {
|
|
159
|
-
const actor = await
|
|
160
|
-
const member = await container.entitlements.addSpaceMember(req.
|
|
154
|
+
const actor = await resolveActor(container, req);
|
|
155
|
+
const member = await container.entitlements.addSpaceMember(req.params.communityId, req.params.spaceId, req.body.orgUserId, actor);
|
|
161
156
|
return reply.code(201).send(member);
|
|
162
157
|
}
|
|
163
158
|
});
|
|
164
159
|
r.route({
|
|
165
160
|
method: "POST",
|
|
166
|
-
url: "/api/member/communities/:communityId/spaces/:spaceId/
|
|
161
|
+
url: "/api/member/communities/:communityId/spaces/:spaceId/accept",
|
|
167
162
|
preHandler: app.requireOrgSession,
|
|
168
163
|
schema: {
|
|
169
164
|
operationId: "acceptSpaceInvite",
|
|
170
165
|
tags,
|
|
171
166
|
summary: "Accept your invitation to a space",
|
|
172
|
-
description: "For a member of the community who
|
|
167
|
+
description: "For a member of the community who does not open every space. Makes you an active member. 409 when you have no open invitation, are already a member, or the space is full.",
|
|
173
168
|
params: CommunitySpaceParam,
|
|
174
169
|
response: {
|
|
175
170
|
200: SpaceMember,
|
|
@@ -177,19 +172,19 @@ async function memberCommunityRoutes(app, container) {
|
|
|
177
172
|
}
|
|
178
173
|
},
|
|
179
174
|
handler: async (req) => {
|
|
180
|
-
const actor = await
|
|
181
|
-
return container.entitlements.accept(req.
|
|
175
|
+
const actor = await resolveActor(container, req);
|
|
176
|
+
return container.entitlements.accept(req.params.communityId, req.params.spaceId, actor);
|
|
182
177
|
}
|
|
183
178
|
});
|
|
184
179
|
r.route({
|
|
185
180
|
method: "POST",
|
|
186
|
-
url: "/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/
|
|
181
|
+
url: "/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/approve",
|
|
187
182
|
preHandler: app.requireOrgSession,
|
|
188
183
|
schema: {
|
|
189
184
|
operationId: "approveSpaceRequest",
|
|
190
185
|
tags,
|
|
191
186
|
summary: "Approve a person's request to join a space you moderate",
|
|
192
|
-
description:
|
|
187
|
+
description: `Requires \`communities:moderate\`. On the community, as its member. 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.`,
|
|
193
188
|
params: SpaceMemberParam,
|
|
194
189
|
response: {
|
|
195
190
|
200: SpaceMember,
|
|
@@ -197,8 +192,8 @@ async function memberCommunityRoutes(app, container) {
|
|
|
197
192
|
}
|
|
198
193
|
},
|
|
199
194
|
handler: async (req) => {
|
|
200
|
-
const actor = await
|
|
201
|
-
return container.entitlements.approve(req.
|
|
195
|
+
const actor = await resolveActor(container, req);
|
|
196
|
+
return container.entitlements.approve(req.params.communityId, req.params.spaceId, req.params.orgUserId, actor);
|
|
202
197
|
}
|
|
203
198
|
});
|
|
204
199
|
r.route({
|
|
@@ -209,7 +204,7 @@ async function memberCommunityRoutes(app, container) {
|
|
|
209
204
|
operationId: "declineSpaceRequest",
|
|
210
205
|
tags,
|
|
211
206
|
summary: "Decline a person's request to join a space you moderate",
|
|
212
|
-
description:
|
|
207
|
+
description: `Requires \`communities:moderate\`. On the community, as its member. Closes the open request. 404 when the person has no open request; 409 when they are already a member.`,
|
|
213
208
|
params: SpaceMemberParam,
|
|
214
209
|
response: {
|
|
215
210
|
200: SpaceMember,
|
|
@@ -217,19 +212,19 @@ async function memberCommunityRoutes(app, container) {
|
|
|
217
212
|
}
|
|
218
213
|
},
|
|
219
214
|
handler: async (req) => {
|
|
220
|
-
const actor = await
|
|
221
|
-
return container.entitlements.decline(req.
|
|
215
|
+
const actor = await resolveActor(container, req);
|
|
216
|
+
return container.entitlements.decline(req.params.communityId, req.params.spaceId, req.params.orgUserId, actor);
|
|
222
217
|
}
|
|
223
218
|
});
|
|
224
219
|
r.route({
|
|
225
220
|
method: "POST",
|
|
226
|
-
url: "/api/member/communities/:communityId/spaces/:spaceId/
|
|
221
|
+
url: "/api/member/communities/:communityId/spaces/:spaceId/refuse",
|
|
227
222
|
preHandler: app.requireOrgSession,
|
|
228
223
|
schema: {
|
|
229
224
|
operationId: "refuseSpaceInvite",
|
|
230
225
|
tags,
|
|
231
226
|
summary: "Refuse your invitation to a space",
|
|
232
|
-
description: "For a member of the community who
|
|
227
|
+
description: "For a member of the community who does not open every space. 409 when you have no open invitation or are already a member.",
|
|
233
228
|
params: CommunitySpaceParam,
|
|
234
229
|
response: {
|
|
235
230
|
200: SpaceMember,
|
|
@@ -237,8 +232,8 @@ async function memberCommunityRoutes(app, container) {
|
|
|
237
232
|
}
|
|
238
233
|
},
|
|
239
234
|
handler: async (req) => {
|
|
240
|
-
const actor = await
|
|
241
|
-
return container.entitlements.refuse(req.
|
|
235
|
+
const actor = await resolveActor(container, req);
|
|
236
|
+
return container.entitlements.refuse(req.params.communityId, req.params.spaceId, actor);
|
|
242
237
|
}
|
|
243
238
|
});
|
|
244
239
|
r.route({
|
|
@@ -249,7 +244,7 @@ async function memberCommunityRoutes(app, container) {
|
|
|
249
244
|
operationId: "leaveSpace",
|
|
250
245
|
tags,
|
|
251
246
|
summary: "Leave a space",
|
|
252
|
-
description: "For a member of the community who
|
|
247
|
+
description: "For a member of the community who does not open every space. 409 when you are not a member of the space.",
|
|
253
248
|
params: CommunitySpaceParam,
|
|
254
249
|
response: {
|
|
255
250
|
200: SpaceMember,
|
|
@@ -257,19 +252,19 @@ async function memberCommunityRoutes(app, container) {
|
|
|
257
252
|
}
|
|
258
253
|
},
|
|
259
254
|
handler: async (req) => {
|
|
260
|
-
const actor = await
|
|
261
|
-
return container.entitlements.leave(req.
|
|
255
|
+
const actor = await resolveActor(container, req);
|
|
256
|
+
return container.entitlements.leave(req.params.communityId, req.params.spaceId, actor);
|
|
262
257
|
}
|
|
263
258
|
});
|
|
264
259
|
r.route({
|
|
265
260
|
method: "POST",
|
|
266
|
-
url: "/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/
|
|
261
|
+
url: "/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/remove",
|
|
267
262
|
preHandler: app.requireOrgSession,
|
|
268
263
|
schema: {
|
|
269
264
|
operationId: "removeSpaceMember",
|
|
270
265
|
tags,
|
|
271
266
|
summary: "Remove a person from a space you moderate",
|
|
272
|
-
description:
|
|
267
|
+
description: `Requires \`communities:moderate\`. On the community, as its member. 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.`,
|
|
273
268
|
params: SpaceMemberParam,
|
|
274
269
|
response: {
|
|
275
270
|
200: SpaceMember,
|
|
@@ -277,8 +272,30 @@ async function memberCommunityRoutes(app, container) {
|
|
|
277
272
|
}
|
|
278
273
|
},
|
|
279
274
|
handler: async (req) => {
|
|
280
|
-
const actor = await
|
|
281
|
-
return container.entitlements.remove(req.
|
|
275
|
+
const actor = await resolveActor(container, req);
|
|
276
|
+
return container.entitlements.remove(req.params.communityId, req.params.spaceId, req.params.orgUserId, actor);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
r.route({
|
|
280
|
+
method: "PUT",
|
|
281
|
+
url: "/api/member/spaces/:spaceId/subscription",
|
|
282
|
+
preHandler: app.requireOrgSession,
|
|
283
|
+
schema: {
|
|
284
|
+
operationId: "setSpaceSubscription",
|
|
285
|
+
tags,
|
|
286
|
+
summary: "Mute a space, or clear your mute",
|
|
287
|
+
description: "For anyone who can open the space. `muted` stops its activity notifying you, until `mutedUntil` when given; mentions, actions on you and moderation work still notify. `none` clears the mute.",
|
|
288
|
+
params: SpaceIdParam,
|
|
289
|
+
body: SetMute,
|
|
290
|
+
response: {
|
|
291
|
+
204: z.void(),
|
|
292
|
+
...refusals(403, 404)
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
handler: async (req, reply) => {
|
|
296
|
+
const actor = await resolveActor(container, req);
|
|
297
|
+
await container.subscriptions.setSpace(req.params.spaceId, req.body, actor);
|
|
298
|
+
return reply.code(204).send();
|
|
282
299
|
}
|
|
283
300
|
});
|
|
284
301
|
}
|