@headless-lms/server 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/community-mail.d.ts +6 -8
- package/dist/app/community-mail.js +13 -17
- package/dist/app/community-mail.js.map +1 -1
- package/dist/app/container.d.ts +27 -10
- package/dist/app/container.js +178 -22
- package/dist/app/container.js.map +1 -1
- package/dist/app/notification-channels.d.ts +47 -0
- package/dist/app/notification-channels.js +174 -0
- package/dist/app/notification-channels.js.map +1 -0
- package/dist/app/notification-reads.d.ts +23 -0
- package/dist/app/notification-reads.js +95 -0
- package/dist/app/notification-reads.js.map +1 -0
- package/dist/http/errors.d.ts +1 -0
- package/dist/http/errors.js +16 -1
- package/dist/http/errors.js.map +1 -1
- package/dist/http/mcp/tools/communities.js +1 -1
- package/dist/http/mcp/tools/communities.js.map +1 -1
- package/dist/http/plugins/error-handler.js +3 -1
- package/dist/http/plugins/error-handler.js.map +1 -1
- package/dist/http/routes/automations.js +3 -3
- package/dist/http/routes/automations.js.map +1 -1
- package/dist/http/routes/communities.js +67 -7
- package/dist/http/routes/communities.js.map +1 -1
- package/dist/http/routes/member/actor.d.ts +1 -1
- package/dist/http/routes/member/chats.js +9 -30
- package/dist/http/routes/member/chats.js.map +1 -1
- package/dist/http/routes/member/community.js +25 -1
- package/dist/http/routes/member/community.js.map +1 -1
- package/dist/http/routes/member/devices.d.ts +7 -0
- package/dist/http/routes/member/devices.js +98 -0
- package/dist/http/routes/member/devices.js.map +1 -0
- package/dist/http/routes/member/index.js +4 -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 +108 -0
- package/dist/http/routes/member/notification-settings.js.map +1 -0
- package/dist/http/routes/member/notifications.js +66 -42
- package/dist/http/routes/member/notifications.js.map +1 -1
- package/dist/http/routes/member/posts.js +33 -33
- package/dist/http/routes/member/posts.js.map +1 -1
- package/dist/http/routes/organizations.js +122 -1
- package/dist/http/routes/organizations.js.map +1 -1
- package/dist/http/schemas/assets.d.ts +6 -6
- package/dist/http/schemas/automations.d.ts +0 -4
- package/dist/http/schemas/chats.d.ts +34 -11
- package/dist/http/schemas/chats.js +4 -6
- package/dist/http/schemas/chats.js.map +1 -1
- package/dist/http/schemas/community.d.ts +77 -17
- package/dist/http/schemas/community.js +6 -3
- package/dist/http/schemas/community.js.map +1 -1
- package/dist/http/schemas/discussion.d.ts +23 -1
- package/dist/http/schemas/discussion.js +14 -2
- package/dist/http/schemas/discussion.js.map +1 -1
- package/dist/http/schemas/index.d.ts +6 -6
- package/dist/http/schemas/index.js +6 -6
- package/dist/http/schemas/integrations.d.ts +4 -4
- package/dist/http/schemas/members.d.ts +5 -5
- package/dist/http/schemas/notifications.d.ts +498 -175
- package/dist/http/schemas/notifications.js +148 -28
- package/dist/http/schemas/notifications.js.map +1 -1
- package/dist/http/schemas/posts.d.ts +37 -3
- package/dist/http/schemas/profile.d.ts +2 -2
- package/dist/http/schemas/shared.d.ts +10 -3
- package/dist/http/schemas/shared.js +3 -2
- package/dist/http/schemas/shared.js.map +1 -1
- package/dist/http/schemas/students.d.ts +3 -3
- package/dist/http/scope.d.ts +2 -1
- package/dist/http/scope.js +7 -1
- package/dist/http/scope.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js.map +1 -1
- package/dist/packages/server/package.js +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chats.js","names":[],"sources":["../../../../src/http/routes/member/chats.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../../app/container.js';\nimport { actorOf } from './actor.js';\nimport {\n BlockParam,\n Chat,\n ChatIdParam,\n ChatsPage,\n CommunityParam,\n EditMessage,\n FlagReason,\n PageQuery,\n MarkRead,\n MessageFlag,\n MessageIdParam,\n MessagesPage,\n MessageView,\n MuteChat,\n OrgUserBody,\n Reactions,\n ResolveMessageFlags,\n SendMessage,\n SetReaction,\n StartDirectChat,\n StartGroupChat,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberChatRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const chats = container.chats;\n const tags = ['Chats'];\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/chats',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberChats',\n tags,\n summary: 'List your chats in a community',\n description:\n 'For a member of the community. Every chat you are in, with your unread count and its last message.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: ChatsPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.listChats(req.orgId, actor, req.params.communityId, req.query);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/direct',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'startDirectChat',\n tags,\n summary: 'Start a direct chat, or return the one that already exists',\n description:\n 'For a member of the community, within its direct-message settings (403 otherwise). With the community set so a first message is a request, a new chat starts `pending` until the other person accepts it. 409 when you message yourself, send attachments without a body, or start a request without a message.',\n body: StartDirectChat,\n response: { 200: Chat, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.startDirect(req.orgId, actor, req.body);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/group',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'startGroupChat',\n tags,\n summary: 'Start a group chat',\n description:\n \"For a member of the community while group chats are on (403 otherwise). Everyone named must hold the community and be someone you may message. 409 when fewer than two others are named or the group would exceed the community's group size.\",\n body: StartGroupChat,\n response: { 201: Chat, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const chat = await chats.startGroup(req.orgId, actor, req.body);\n return reply.code(201).send(chat);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/participants',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addChatParticipant',\n tags,\n summary: 'Add a person to a group chat you are in',\n description:\n 'For a participant of a group chat. 409 when the chat is direct, the person is already in it, or it is full.',\n params: ChatIdParam,\n body: OrgUserBody,\n response: { 200: Chat, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.addParticipant(req.orgId, actor, req.params.chatId, req.body.orgUserId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/leave',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'leaveChat',\n tags,\n summary: 'Leave a chat',\n description: 'For a participant. The chat stays for the others.',\n params: ChatIdParam,\n response: { 200: Chat, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.leave(req.orgId, actor, req.params.chatId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/acceptance',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'acceptChat',\n tags,\n summary: 'Accept a message request',\n description:\n 'For the person the request was sent to. The chat opens for both sides. 409 when the chat is not a pending request.',\n params: ChatIdParam,\n response: { 200: Chat, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.accept(req.orgId, actor, req.params.chatId);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/chats/:chatId/messages',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listChatMessages',\n tags,\n summary: 'Read the messages in a chat, newest first',\n description: 'For a participant of the chat.',\n params: ChatIdParam,\n querystring: PageQuery,\n response: { 200: MessagesPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.messages(req.orgId, actor, req.params.chatId, req.query);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/messages',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'sendChatMessage',\n tags,\n summary: 'Send a message',\n description:\n \"For a participant while the chat is open and not locked, and nobody in it has blocked the other (403 otherwise). A request's sender cannot send more until it is accepted.\",\n params: ChatIdParam,\n body: SendMessage,\n response: { 201: MessageView, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const message = await chats.send(req.orgId, actor, req.params.chatId, req.body);\n return reply.code(201).send(message);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/messages/:messageId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'editChatMessage',\n tags,\n summary: 'Edit your own message',\n description:\n 'Sender only, while the chat is open and not locked. 409 when the message is deleted.',\n params: MessageIdParam,\n body: EditMessage,\n response: { 200: MessageView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.editMessage(req.orgId, actor, req.params.messageId, req.body.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/messages/:messageId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteChatMessage',\n tags,\n summary: 'Delete your message, or one in a community you moderate',\n description:\n 'For the sender, or a moderator of the community. The message stays in the history, marked deleted. 409 when it is already deleted.',\n params: MessageIdParam,\n response: { 200: MessageView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.deleteMessage(req.orgId, actor, req.params.messageId);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/messages/:messageId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setMessageReaction',\n tags,\n summary: 'Set your reaction on a message',\n description:\n 'For a participant of the chat. Replaces any reaction you already had and returns the new counts.',\n params: MessageIdParam,\n body: SetReaction,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.setReaction(req.orgId, actor, req.params.messageId, req.body.emoji);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/messages/:messageId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearMessageReaction',\n tags,\n summary: 'Withdraw your reaction from a message',\n description: 'For a participant of the chat. Returns the new counts.',\n params: MessageIdParam,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.setReaction(req.orgId, actor, req.params.messageId, null);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/messages/:messageId/flags',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'flagMessage',\n tags,\n summary: 'Flag a message for the moderators',\n description:\n 'For a participant of the chat. Flagging it again while your flag is open returns that flag.',\n params: MessageIdParam,\n body: FlagReason,\n response: { 201: MessageFlag, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const flag = await chats.flagMessage(req.orgId, actor, req.params.messageId, req.body.reason);\n return reply.code(201).send(flag);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/messages/:messageId/flags/resolution',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resolveMessageFlags',\n tags,\n summary: 'Dismiss the open flags on a message in a community you moderate',\n description:\n 'For a moderator who is a member of the community. Closes every open flag and leaves the message up.',\n params: MessageIdParam,\n body: ResolveMessageFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await chats.resolveMessageFlags(req.orgId, actor, req.params.messageId, req.body.resolution);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/read',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'markChatRead',\n tags,\n summary: 'Mark a chat read up to a message',\n description: 'For a participant. The message must be in the chat (404 otherwise).',\n params: ChatIdParam,\n body: MarkRead,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await chats.markRead(req.orgId, actor, req.params.chatId, req.body.messageId);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/mute',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'muteChat',\n tags,\n summary: 'Mute a chat until a time, or until unmuted',\n description: 'For a participant. Without `until` the chat stays muted until you unmute it.',\n params: ChatIdParam,\n body: MuteChat,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await chats.mute(req.orgId, actor, req.params.chatId, req.body.until);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/unmute',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unmuteChat',\n tags,\n summary: 'Unmute a chat',\n description: 'For a participant.',\n params: ChatIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await chats.unmute(req.orgId, actor, req.params.chatId);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/lock',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'lockChat',\n tags,\n summary: 'Lock a chat in a community you moderate',\n description:\n 'For a moderator who is a member of the community. 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: ChatIdParam,\n response: { 200: Chat, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.lock(req.orgId, actor, req.params.chatId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/unlock',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unlockChat',\n tags,\n summary: 'Unlock a chat in a community you moderate',\n description:\n 'For a moderator who is a member of the community. Unlocking an unlocked chat returns it unchanged.',\n params: ChatIdParam,\n response: { 200: Chat, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.unlock(req.orgId, actor, req.params.chatId);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/communities/:communityId/blocks/:orgUserId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'blockMember',\n tags,\n summary: 'Block a person in a community',\n description:\n 'For a member of the community. Neither of you can message the other there; a direct chat between you closes. Blocking twice is harmless. 409 when you block yourself.',\n params: BlockParam,\n response: { 204: z.void(), ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await chats.block(req.orgId, actor, req.params.communityId, req.params.orgUserId);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/communities/:communityId/blocks/:orgUserId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unblockMember',\n tags,\n summary: 'Unblock a person in a community',\n description:\n 'For a member of the community. A direct chat closed by the block reopens unless a moderator locked it. 409 when you unblock yourself.',\n params: BlockParam,\n response: { 204: z.void(), ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await chats.unblock(req.orgId, actor, req.params.communityId, req.params.orgUserId);\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chats.js","names":[],"sources":["../../../../src/http/routes/member/chats.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../../app/container.js';\nimport { actorOf } from './actor.js';\nimport {\n BlockParam,\n Chat,\n ChatIdParam,\n ChatsPage,\n CommunityParam,\n EditMessage,\n FlagReason,\n PageQuery,\n MarkRead,\n MessageFlag,\n MessageIdParam,\n MessagesPage,\n MessageView,\n OrgUserBody,\n Reactions,\n ResolveMessageFlags,\n SendMessage,\n SetMute,\n SetReaction,\n StartDirectChat,\n StartGroupChat,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberChatRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const chats = container.chats;\n const tags = ['Chats'];\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/chats',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberChats',\n tags,\n summary: 'List your chats in a community',\n description:\n 'For a member of the community. Every chat you are in, with your unread count and its last message.',\n params: CommunityParam,\n querystring: PageQuery,\n response: { 200: ChatsPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.listChats(req.orgId, actor, req.params.communityId, req.query);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/direct',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'startDirectChat',\n tags,\n summary: 'Start a direct chat, or return the one that already exists',\n description:\n 'For a member of the community, within its direct-message settings (403 otherwise). With the community set so a first message is a request, a new chat starts `pending` until the other person accepts it. 409 when you message yourself, send attachments without a body, or start a request without a message.',\n body: StartDirectChat,\n response: { 200: Chat, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.startDirect(req.orgId, actor, req.body);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/group',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'startGroupChat',\n tags,\n summary: 'Start a group chat',\n description:\n \"For a member of the community while group chats are on (403 otherwise). Everyone named must hold the community and be someone you may message. 409 when fewer than two others are named or the group would exceed the community's group size.\",\n body: StartGroupChat,\n response: { 201: Chat, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const chat = await chats.startGroup(req.orgId, actor, req.body);\n return reply.code(201).send(chat);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/participants',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addChatParticipant',\n tags,\n summary: 'Add a person to a group chat you are in',\n description:\n 'For a participant of a group chat. 409 when the chat is direct, the person is already in it, or it is full.',\n params: ChatIdParam,\n body: OrgUserBody,\n response: { 200: Chat, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.addParticipant(req.orgId, actor, req.params.chatId, req.body.orgUserId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/leave',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'leaveChat',\n tags,\n summary: 'Leave a chat',\n description: 'For a participant. The chat stays for the others.',\n params: ChatIdParam,\n response: { 200: Chat, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.leave(req.orgId, actor, req.params.chatId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/acceptance',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'acceptChat',\n tags,\n summary: 'Accept a message request',\n description:\n 'For the person the request was sent to. The chat opens for both sides. 409 when the chat is not a pending request.',\n params: ChatIdParam,\n response: { 200: Chat, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.accept(req.orgId, actor, req.params.chatId);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/chats/:chatId/messages',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listChatMessages',\n tags,\n summary: 'Read the messages in a chat, newest first',\n description: 'For a participant of the chat.',\n params: ChatIdParam,\n querystring: PageQuery,\n response: { 200: MessagesPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.messages(req.orgId, actor, req.params.chatId, req.query);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/messages',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'sendChatMessage',\n tags,\n summary: 'Send a message',\n description:\n \"For a participant while the chat is open and not locked, and nobody in it has blocked the other (403 otherwise). A request's sender cannot send more until it is accepted.\",\n params: ChatIdParam,\n body: SendMessage,\n response: { 201: MessageView, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const message = await chats.send(req.orgId, actor, req.params.chatId, req.body);\n return reply.code(201).send(message);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/messages/:messageId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'editChatMessage',\n tags,\n summary: 'Edit your own message',\n description:\n 'Sender only, while the chat is open and not locked. 409 when the message is deleted.',\n params: MessageIdParam,\n body: EditMessage,\n response: { 200: MessageView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.editMessage(req.orgId, actor, req.params.messageId, req.body.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/messages/:messageId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteChatMessage',\n tags,\n summary: 'Delete your message, or one in a community you moderate',\n description:\n 'For the sender, or a moderator of the community. The message stays in the history, marked deleted. 409 when it is already deleted.',\n params: MessageIdParam,\n response: { 200: MessageView, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.deleteMessage(req.orgId, actor, req.params.messageId);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/messages/:messageId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setMessageReaction',\n tags,\n summary: 'Set your reaction on a message',\n description:\n 'For a participant of the chat. Replaces any reaction you already had and returns the new counts.',\n params: MessageIdParam,\n body: SetReaction,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.setReaction(req.orgId, actor, req.params.messageId, req.body.emoji);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/messages/:messageId/reaction',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'clearMessageReaction',\n tags,\n summary: 'Withdraw your reaction from a message',\n description: 'For a participant of the chat. Returns the new counts.',\n params: MessageIdParam,\n response: { 200: Reactions, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.setReaction(req.orgId, actor, req.params.messageId, null);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/messages/:messageId/flags',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'flagMessage',\n tags,\n summary: 'Flag a message for the moderators',\n description:\n 'For a participant of the chat. Flagging it again while your flag is open returns that flag.',\n params: MessageIdParam,\n body: FlagReason,\n response: { 201: MessageFlag, ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const flag = await chats.flagMessage(req.orgId, actor, req.params.messageId, req.body.reason);\n return reply.code(201).send(flag);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/messages/:messageId/flags/resolution',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'resolveMessageFlags',\n tags,\n summary: 'Dismiss the open flags on a message in a community you moderate',\n description:\n 'For a moderator who is a member of the community. Closes every open flag and leaves the message up.',\n params: MessageIdParam,\n body: ResolveMessageFlags,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await chats.resolveMessageFlags(req.orgId, actor, req.params.messageId, req.body.resolution);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/read',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'markChatRead',\n tags,\n summary: 'Mark a chat read up to a message',\n description: 'For a participant. The message must be in the chat (404 otherwise).',\n params: ChatIdParam,\n body: MarkRead,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await chats.markRead(req.orgId, actor, req.params.chatId, req.body.messageId);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/chats/:chatId/subscription',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setChatSubscription',\n tags,\n summary: 'Mute a chat, or clear your mute',\n description:\n 'For a participant. `muted` stops its messages notifying you, until `mutedUntil` when given; a mention still notifies you. `none` clears the mute.',\n params: ChatIdParam,\n body: SetMute,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await container.subscriptions.setChat(req.orgId, actor, req.params.chatId, req.body);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/lock',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'lockChat',\n tags,\n summary: 'Lock a chat in a community you moderate',\n description:\n 'For a moderator who is a member of the community. 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: ChatIdParam,\n response: { 200: Chat, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.lock(req.orgId, actor, req.params.chatId);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/chats/:chatId/unlock',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unlockChat',\n tags,\n summary: 'Unlock a chat in a community you moderate',\n description:\n 'For a moderator who is a member of the community. Unlocking an unlocked chat returns it unchanged.',\n params: ChatIdParam,\n response: { 200: Chat, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return chats.unlock(req.orgId, actor, req.params.chatId);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/communities/:communityId/blocks/:orgUserId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'blockMember',\n tags,\n summary: 'Block a person in a community',\n description:\n 'For a member of the community. Neither of you can message the other there; a direct chat between you closes. Blocking twice is harmless. 409 when you block yourself.',\n params: BlockParam,\n response: { 204: z.void(), ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await chats.block(req.orgId, actor, req.params.communityId, req.params.orgUserId);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/communities/:communityId/blocks/:orgUserId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'unblockMember',\n tags,\n summary: 'Unblock a person in a community',\n description:\n 'For a member of the community. A direct chat closed by the block reopens unless a moderator locked it. 409 when you unblock yourself.',\n params: BlockParam,\n response: { 204: z.void(), ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await chats.unblock(req.orgId, actor, req.params.communityId, req.params.orgUserId);\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { PageQuery, refusals } from "../../schemas/shared.js";
|
|
2
|
-
import {
|
|
2
|
+
import { SetMute } from "../../schemas/discussion.js";
|
|
3
|
+
import { CommunityHome, CommunityHomeSpace, CommunitySpaceParam, DirectoryPage, DirectoryQuery, SpaceIdParam, SpaceMember, SpaceMemberParam, SpaceMembersPage } from "../../schemas/community.js";
|
|
3
4
|
import { CommunityParam, OrgUserBody } from "../../schemas/chats.js";
|
|
4
5
|
import "../../schemas/index.js";
|
|
5
6
|
import { actorOf } from "./actor.js";
|
|
7
|
+
import { z } from "zod";
|
|
6
8
|
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
7
9
|
//#region src/http/routes/member/community.ts
|
|
8
10
|
async function memberCommunityRoutes(app, container) {
|
|
@@ -281,6 +283,28 @@ async function memberCommunityRoutes(app, container) {
|
|
|
281
283
|
return container.entitlements.remove(req.orgId, actor, req.params.communityId, req.params.spaceId, req.params.orgUserId);
|
|
282
284
|
}
|
|
283
285
|
});
|
|
286
|
+
r.route({
|
|
287
|
+
method: "PUT",
|
|
288
|
+
url: "/api/member/spaces/:spaceId/subscription",
|
|
289
|
+
preHandler: app.requireOrgSession,
|
|
290
|
+
schema: {
|
|
291
|
+
operationId: "setSpaceSubscription",
|
|
292
|
+
tags,
|
|
293
|
+
summary: "Mute a space, or clear your mute",
|
|
294
|
+
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.",
|
|
295
|
+
params: SpaceIdParam,
|
|
296
|
+
body: SetMute,
|
|
297
|
+
response: {
|
|
298
|
+
204: z.void(),
|
|
299
|
+
...refusals(403, 404)
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
handler: async (req, reply) => {
|
|
303
|
+
const actor = await actorOf(container, req.orgId, req.userId);
|
|
304
|
+
await container.subscriptions.setSpace(req.orgId, actor, req.params.spaceId, req.body);
|
|
305
|
+
return reply.code(204).send();
|
|
306
|
+
}
|
|
307
|
+
});
|
|
284
308
|
}
|
|
285
309
|
//#endregion
|
|
286
310
|
export { memberCommunityRoutes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"community.js","names":[],"sources":["../../../../src/http/routes/member/community.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport type { Container } from '../../../app/container.js';\nimport { NotFoundError } from '@headless-lms/core/shared/errors';\nimport { actorOf } from './actor.js';\nimport {\n CommunityHome,\n CommunityHomeSpace,\n CommunityParam,\n CommunitySpaceParam,\n DirectoryPage,\n DirectoryQuery,\n OrgUserBody,\n PageQuery,\n SpaceMember,\n SpaceMemberParam,\n SpaceMembersPage,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberCommunityRoutes(\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/member/communities/:communityId/home',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityHome',\n tags,\n summary: 'The spaces a member can see in a community',\n description:\n \"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.\",\n params: CommunityParam,\n response: { 200: CommunityHome, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const home = await container.reporting.member.communityHome(\n req.orgId,\n actor,\n req.params.communityId,\n );\n if (!home) {\n throw new NotFoundError('Community', req.params.communityId);\n }\n return home;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberSpace',\n tags,\n summary: 'One space as a member sees it',\n description:\n 'For a member of the community: the space with its topics, links, settings and where the viewer stands. A space the viewer cannot open comes back locked, without its content. 404 when the viewer is not a member, the space is not in the community, or it is a by-invite space the viewer is not invited to or a member of.',\n params: CommunitySpaceParam,\n response: { 200: CommunityHomeSpace, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const space = await container.reporting.member.communitySpace(\n req.orgId,\n actor,\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: 'GET',\n url: '/api/member/communities/:communityId/directory',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityDirectory',\n tags,\n summary: 'People in a community',\n description:\n 'For a member of the community: everyone who holds it, with their public profile. 404 when the viewer is not a member.',\n params: CommunityParam,\n querystring: DirectoryQuery,\n response: { 200: DirectoryPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const page = await container.reporting.member.directory(\n req.orgId,\n actor,\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/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listSpaceMembers',\n tags,\n summary: 'List the members of a space',\n description:\n 'For anyone who can open the space. Members see the active members; moderators also see open requests and invitations.',\n params: CommunitySpaceParam,\n querystring: PageQuery,\n response: { 200: SpaceMembersPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.listSpaceMembers(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.query,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/requests',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'requestSpaceMembership',\n tags,\n summary: 'Ask to join a space',\n description:\n 'For a member of the community who is not staff. 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.',\n params: CommunitySpaceParam,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.requestJoin(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/invites',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'inviteSpaceMember',\n tags,\n summary: 'Invite a person into a space you moderate',\n description:\n 'For a moderator who is a member of the community. Invites another member 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 actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.invite(\n req.orgId,\n actor,\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/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addSpaceMember',\n tags,\n summary: 'Add a person to a space you moderate',\n description:\n 'For a moderator who is a member of the community. Places another member 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 actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.addSpaceMember(\n req.orgId,\n actor,\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/member/communities/:communityId/spaces/:spaceId/acceptance',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'acceptSpaceInvite',\n tags,\n summary: 'Accept your invitation to a space',\n description:\n 'For a member of the community who is not staff. Makes you an active member. 409 when you have no open invitation, are already a member, or the space is full.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.accept(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/approval',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approveSpaceRequest',\n tags,\n summary: \"Approve a person's request to join a space you moderate\",\n description:\n 'For a moderator who is a member of the community. 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 actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.approve(\n req.orgId,\n actor,\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/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/decline',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'declineSpaceRequest',\n tags,\n summary: \"Decline a person's request to join a space you moderate\",\n description:\n 'For a moderator who is a member of the community. 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 actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.decline(\n req.orgId,\n actor,\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/member/communities/:communityId/spaces/:spaceId/refusal',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'refuseSpaceInvite',\n tags,\n summary: 'Refuse your invitation to a space',\n description:\n 'For a member of the community who is not staff. 409 when you have no open invitation or are already a member.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.refuse(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/departure',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'leaveSpace',\n tags,\n summary: 'Leave a space',\n description:\n 'For a member of the community who is not staff. 409 when you are not a member of the space.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.leave(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/removal',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeSpaceMember',\n tags,\n summary: 'Remove a person from a space you moderate',\n description:\n 'For a moderator who is a member of the community. 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 actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.remove(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"community.js","names":[],"sources":["../../../../src/http/routes/member/community.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 { actorOf } from './actor.js';\nimport {\n CommunityHome,\n CommunityHomeSpace,\n CommunityParam,\n CommunitySpaceParam,\n DirectoryPage,\n DirectoryQuery,\n OrgUserBody,\n PageQuery,\n SetMute,\n SpaceIdParam,\n SpaceMember,\n SpaceMemberParam,\n SpaceMembersPage,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberCommunityRoutes(\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/member/communities/:communityId/home',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityHome',\n tags,\n summary: 'The spaces a member can see in a community',\n description:\n \"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.\",\n params: CommunityParam,\n response: { 200: CommunityHome, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const home = await container.reporting.member.communityHome(\n req.orgId,\n actor,\n req.params.communityId,\n );\n if (!home) {\n throw new NotFoundError('Community', req.params.communityId);\n }\n return home;\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/spaces/:spaceId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberSpace',\n tags,\n summary: 'One space as a member sees it',\n description:\n 'For a member of the community: the space with its topics, links, settings and where the viewer stands. A space the viewer cannot open comes back locked, without its content. 404 when the viewer is not a member, the space is not in the community, or it is a by-invite space the viewer is not invited to or a member of.',\n params: CommunitySpaceParam,\n response: { 200: CommunityHomeSpace, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const space = await container.reporting.member.communitySpace(\n req.orgId,\n actor,\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: 'GET',\n url: '/api/member/communities/:communityId/directory',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityDirectory',\n tags,\n summary: 'People in a community',\n description:\n 'For a member of the community: everyone who holds it, with their public profile. 404 when the viewer is not a member.',\n params: CommunityParam,\n querystring: DirectoryQuery,\n response: { 200: DirectoryPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const page = await container.reporting.member.directory(\n req.orgId,\n actor,\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/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listSpaceMembers',\n tags,\n summary: 'List the members of a space',\n description:\n 'For anyone who can open the space. Members see the active members; moderators also see open requests and invitations.',\n params: CommunitySpaceParam,\n querystring: PageQuery,\n response: { 200: SpaceMembersPage, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.listSpaceMembers(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.query,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/requests',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'requestSpaceMembership',\n tags,\n summary: 'Ask to join a space',\n description:\n 'For a member of the community who is not staff. 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.',\n params: CommunitySpaceParam,\n response: { 201: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.requestJoin(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n return reply.code(201).send(member);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/invites',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'inviteSpaceMember',\n tags,\n summary: 'Invite a person into a space you moderate',\n description:\n 'For a moderator who is a member of the community. Invites another member 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 actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.invite(\n req.orgId,\n actor,\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/member/communities/:communityId/spaces/:spaceId/members',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'addSpaceMember',\n tags,\n summary: 'Add a person to a space you moderate',\n description:\n 'For a moderator who is a member of the community. Places another member 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 actor = await actorOf(container, req.orgId, req.userId);\n const member = await container.entitlements.addSpaceMember(\n req.orgId,\n actor,\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/member/communities/:communityId/spaces/:spaceId/acceptance',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'acceptSpaceInvite',\n tags,\n summary: 'Accept your invitation to a space',\n description:\n 'For a member of the community who is not staff. Makes you an active member. 409 when you have no open invitation, are already a member, or the space is full.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.accept(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/approval',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'approveSpaceRequest',\n tags,\n summary: \"Approve a person's request to join a space you moderate\",\n description:\n 'For a moderator who is a member of the community. 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 actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.approve(\n req.orgId,\n actor,\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/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/decline',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'declineSpaceRequest',\n tags,\n summary: \"Decline a person's request to join a space you moderate\",\n description:\n 'For a moderator who is a member of the community. 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 actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.decline(\n req.orgId,\n actor,\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/member/communities/:communityId/spaces/:spaceId/refusal',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'refuseSpaceInvite',\n tags,\n summary: 'Refuse your invitation to a space',\n description:\n 'For a member of the community who is not staff. 409 when you have no open invitation or are already a member.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.refuse(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/departure',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'leaveSpace',\n tags,\n summary: 'Leave a space',\n description:\n 'For a member of the community who is not staff. 409 when you are not a member of the space.',\n params: CommunitySpaceParam,\n response: { 200: SpaceMember, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.leave(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n );\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/communities/:communityId/spaces/:spaceId/members/:orgUserId/removal',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeSpaceMember',\n tags,\n summary: 'Remove a person from a space you moderate',\n description:\n 'For a moderator who is a member of the community. 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 actor = await actorOf(container, req.orgId, req.userId);\n return container.entitlements.remove(\n req.orgId,\n actor,\n req.params.communityId,\n req.params.spaceId,\n req.params.orgUserId,\n );\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/member/spaces/:spaceId/subscription',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setSpaceSubscription',\n tags,\n summary: 'Mute a space, or clear your mute',\n description:\n '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.',\n params: SpaceIdParam,\n body: SetMute,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await container.subscriptions.setSpace(req.orgId, actor, req.params.spaceId, req.body);\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Container } from "../../../app/container.js";
|
|
2
|
+
import { FastifyInstance } from "fastify";
|
|
3
|
+
//#region src/http/routes/member/devices.d.ts
|
|
4
|
+
declare function memberDeviceRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { memberDeviceRoutes };
|
|
7
|
+
//# sourceMappingURL=devices.d.ts.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { refusals } from "../../schemas/shared.js";
|
|
2
|
+
import { Device, DeviceIdParam, DeviceList, PushConfigView, RegisterDevice } from "../../schemas/notifications.js";
|
|
3
|
+
import "../../schemas/index.js";
|
|
4
|
+
import { actorOf } from "./actor.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region src/http/routes/member/devices.ts
|
|
7
|
+
async function memberDeviceRoutes(app, container) {
|
|
8
|
+
const r = app.withTypeProvider();
|
|
9
|
+
const tags = ["Notifications"];
|
|
10
|
+
async function memberOf(orgId, userId) {
|
|
11
|
+
const orgUser = await actorOf(container, orgId, userId);
|
|
12
|
+
return {
|
|
13
|
+
id: orgUser.id,
|
|
14
|
+
role: orgUser.role,
|
|
15
|
+
userId
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
r.route({
|
|
19
|
+
method: "GET",
|
|
20
|
+
url: "/api/member/devices",
|
|
21
|
+
preHandler: app.requireOrgSession,
|
|
22
|
+
schema: {
|
|
23
|
+
operationId: "listMemberDevices",
|
|
24
|
+
tags,
|
|
25
|
+
summary: "List the devices you registered for push notifications",
|
|
26
|
+
description: "For anyone signed in to the organization. Each app install or browser that registered, most recently seen first. A device not seen for 30 days receives no push until it registers again.",
|
|
27
|
+
response: {
|
|
28
|
+
200: DeviceList,
|
|
29
|
+
...refusals(403)
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
handler: async (req) => {
|
|
33
|
+
return container.devices.list(req.orgId, await memberOf(req.orgId, req.userId));
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
r.route({
|
|
37
|
+
method: "POST",
|
|
38
|
+
url: "/api/member/devices",
|
|
39
|
+
preHandler: app.requireOrgSession,
|
|
40
|
+
schema: {
|
|
41
|
+
operationId: "registerMemberDevice",
|
|
42
|
+
tags,
|
|
43
|
+
summary: "Register this device for push notifications, or refresh it",
|
|
44
|
+
description: "For anyone signed in to the organization. Call it after sign-in and on every launch. A new token adds a device, a known token is refreshed, and a token another person registered moves to you. A browser sends its push subscription endpoint as `token` with its `keys`; an iOS app sends its bundle id as `appId` and its APNs `environment`. 422 when the provider is not configured, an `apns` `appId` is not one of the configured bundle ids, a `webpush` device has no `keys`, or `keys` or `environment` come with a provider they do not apply to.",
|
|
45
|
+
body: RegisterDevice,
|
|
46
|
+
response: {
|
|
47
|
+
200: Device,
|
|
48
|
+
...refusals(400, 403, 422)
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
handler: async (req) => {
|
|
52
|
+
return container.devices.register(req.orgId, await memberOf(req.orgId, req.userId), req.body);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
r.route({
|
|
56
|
+
method: "DELETE",
|
|
57
|
+
url: "/api/member/devices/:deviceId",
|
|
58
|
+
preHandler: app.requireOrgSession,
|
|
59
|
+
schema: {
|
|
60
|
+
operationId: "removeMemberDevice",
|
|
61
|
+
tags,
|
|
62
|
+
summary: "Remove one of your devices",
|
|
63
|
+
description: "For the person who registered the device. Call it before signing out, so the next person on the device receives none of your pushes. 404 when the device is not yours.",
|
|
64
|
+
params: DeviceIdParam,
|
|
65
|
+
response: {
|
|
66
|
+
204: z.void(),
|
|
67
|
+
...refusals(403, 404)
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
handler: async (req, reply) => {
|
|
71
|
+
await container.devices.remove(req.orgId, await memberOf(req.orgId, req.userId), req.params.deviceId);
|
|
72
|
+
return reply.code(204).send();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
r.route({
|
|
76
|
+
method: "GET",
|
|
77
|
+
url: "/api/member/push-config",
|
|
78
|
+
preHandler: app.requireOrgSession,
|
|
79
|
+
schema: {
|
|
80
|
+
operationId: "getMemberPushConfig",
|
|
81
|
+
tags,
|
|
82
|
+
summary: "Read what a browser needs to subscribe to push notifications",
|
|
83
|
+
description: "For anyone signed in to the organization. `vapidPublicKey` is the `applicationServerKey` for `pushManager.subscribe`, or null when Web Push is not configured.",
|
|
84
|
+
response: {
|
|
85
|
+
200: PushConfigView,
|
|
86
|
+
...refusals(403)
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
handler: async (req) => {
|
|
90
|
+
await actorOf(container, req.orgId, req.userId);
|
|
91
|
+
return container.devices.pushConfig();
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { memberDeviceRoutes };
|
|
97
|
+
|
|
98
|
+
//# sourceMappingURL=devices.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devices.js","names":[],"sources":["../../../../src/http/routes/member/devices.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../../app/container.js';\nimport { actorOf } from './actor.js';\nimport {\n Device,\n DeviceIdParam,\n DeviceList,\n PushConfigView,\n RegisterDevice,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberDeviceRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Notifications'];\n\n async function memberOf(orgId: string, userId: string) {\n const orgUser = await actorOf(container, orgId, userId);\n return { id: orgUser.id, role: orgUser.role, userId };\n }\n\n r.route({\n method: 'GET',\n url: '/api/member/devices',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listMemberDevices',\n tags,\n summary: 'List the devices you registered for push notifications',\n description:\n 'For anyone signed in to the organization. Each app install or browser that registered, most recently seen first. A device not seen for 30 days receives no push until it registers again.',\n response: { 200: DeviceList, ...refusals(403) },\n },\n handler: async (req) => {\n return container.devices.list(req.orgId, await memberOf(req.orgId, req.userId));\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/member/devices',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'registerMemberDevice',\n tags,\n summary: 'Register this device for push notifications, or refresh it',\n description:\n 'For anyone signed in to the organization. Call it after sign-in and on every launch. A new token adds a device, a known token is refreshed, and a token another person registered moves to you. A browser sends its push subscription endpoint as `token` with its `keys`; an iOS app sends its bundle id as `appId` and its APNs `environment`. 422 when the provider is not configured, an `apns` `appId` is not one of the configured bundle ids, a `webpush` device has no `keys`, or `keys` or `environment` come with a provider they do not apply to.',\n body: RegisterDevice,\n response: { 200: Device, ...refusals(400, 403, 422) },\n },\n handler: async (req) => {\n return container.devices.register(req.orgId, await memberOf(req.orgId, req.userId), req.body);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/member/devices/:deviceId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'removeMemberDevice',\n tags,\n summary: 'Remove one of your devices',\n description:\n 'For the person who registered the device. Call it before signing out, so the next person on the device receives none of your pushes. 404 when the device is not yours.',\n params: DeviceIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n await container.devices.remove(\n req.orgId,\n await memberOf(req.orgId, req.userId),\n req.params.deviceId,\n );\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/push-config',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberPushConfig',\n tags,\n summary: 'Read what a browser needs to subscribe to push notifications',\n description:\n 'For anyone signed in to the organization. `vapidPublicKey` is the `applicationServerKey` for `pushManager.subscribe`, or null when Web Push is not configured.',\n response: { 200: PushConfigView, ...refusals(403) },\n },\n handler: async (req) => {\n await actorOf(container, req.orgId, req.userId);\n return container.devices.pushConfig();\n },\n });\n}\n"],"mappings":";;;;;;AAcA,eAAsB,mBACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,OAAO,CAAC,eAAe;CAE7B,eAAe,SAAS,OAAe,QAAgB;EACrD,MAAM,UAAU,MAAM,QAAQ,WAAW,OAAO,MAAM;EACtD,OAAO;GAAE,IAAI,QAAQ;GAAI,MAAM,QAAQ;GAAM;EAAO;CACtD;CAEA,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,UAAU;IAAE,KAAK;IAAY,GAAG,SAAS,GAAG;GAAE;EAChD;EACA,SAAS,OAAO,QAAQ;GACtB,OAAO,UAAU,QAAQ,KAAK,IAAI,OAAO,MAAM,SAAS,IAAI,OAAO,IAAI,MAAM,CAAC;EAChF;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,MAAM;GACN,UAAU;IAAE,KAAK;IAAQ,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACtD;EACA,SAAS,OAAO,QAAQ;GACtB,OAAO,UAAU,QAAQ,SAAS,IAAI,OAAO,MAAM,SAAS,IAAI,OAAO,IAAI,MAAM,GAAG,IAAI,IAAI;EAC9F;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK,EAAE,KAAK;IAAG,GAAG,SAAS,KAAK,GAAG;GAAE;EACnD;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,UAAU,QAAQ,OACtB,IAAI,OACJ,MAAM,SAAS,IAAI,OAAO,IAAI,MAAM,GACpC,IAAI,OAAO,QACb;GACA,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,UAAU;IAAE,KAAK;IAAgB,GAAG,SAAS,GAAG;GAAE;EACpD;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9C,OAAO,UAAU,QAAQ,WAAW;EACtC;CACF,CAAC;AACH"}
|
|
@@ -6,6 +6,8 @@ import { memberCoursesRoutes } from "./courses.js";
|
|
|
6
6
|
import { memberDownloadsRoutes } from "./downloads.js";
|
|
7
7
|
import { memberFileRoutes } from "./files.js";
|
|
8
8
|
import { memberNotificationRoutes } from "./notifications.js";
|
|
9
|
+
import { memberNotificationSettingsRoutes } from "./notification-settings.js";
|
|
10
|
+
import { memberDeviceRoutes } from "./devices.js";
|
|
9
11
|
import { memberOrgRoutes } from "./org.js";
|
|
10
12
|
import { memberPostRoutes } from "./posts.js";
|
|
11
13
|
import { memberProfileRoutes } from "./profile.js";
|
|
@@ -24,6 +26,8 @@ async function memberRoutes(app, container) {
|
|
|
24
26
|
await memberDownloadsRoutes(app, container);
|
|
25
27
|
await memberFileRoutes(app, container);
|
|
26
28
|
await memberNotificationRoutes(app, container);
|
|
29
|
+
await memberNotificationSettingsRoutes(app, container);
|
|
30
|
+
await memberDeviceRoutes(app, container);
|
|
27
31
|
}
|
|
28
32
|
//#endregion
|
|
29
33
|
export { memberRoutes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/http/routes/member/index.ts"],"sourcesContent":["// Student HTTP API under `/api/member`. Session-guarded like every\n// back-office route, but scoped by the session's `org_users` row —\n// the reads gate on the person's enrollments, not on staff role. One file per\n// resource; everything under `/api/member` lives in this directory and nowhere\n// else. The course, download, and progress reads call reporting/member.\nimport type { FastifyInstance } from 'fastify';\nimport type { Container } from '../../../app/container.js';\nimport { memberAssetsRoutes } from './assets.js';\nimport { memberChatRoutes } from './chats.js';\nimport { memberCommentsRoutes } from './comments.js';\nimport { memberCommunityRoutes } from './community.js';\nimport { memberCoursesRoutes } from './courses.js';\nimport { memberDownloadsRoutes } from './downloads.js';\nimport { memberFileRoutes } from './files.js';\nimport { memberNotificationRoutes } from './notifications.js';\nimport { memberOrgRoutes } from './org.js';\nimport { memberPostRoutes } from './posts.js';\nimport { memberProfileRoutes } from './profile.js';\nimport { memberProgressRoutes } from './progress.js';\n\nexport async function memberRoutes(app: FastifyInstance, container: Container): Promise<void> {\n await memberOrgRoutes(app, container);\n await memberProfileRoutes(app, container);\n await memberCommunityRoutes(app, container);\n await memberPostRoutes(app, container);\n await memberChatRoutes(app, container);\n await memberCoursesRoutes(app, container);\n await memberProgressRoutes(app, container);\n await memberCommentsRoutes(app, container);\n await memberAssetsRoutes(app, container);\n await memberDownloadsRoutes(app, container);\n await memberFileRoutes(app, container);\n await memberNotificationRoutes(app, container);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/http/routes/member/index.ts"],"sourcesContent":["// Student HTTP API under `/api/member`. Session-guarded like every\n// back-office route, but scoped by the session's `org_users` row —\n// the reads gate on the person's enrollments, not on staff role. One file per\n// resource; everything under `/api/member` lives in this directory and nowhere\n// else. The course, download, and progress reads call reporting/member.\nimport type { FastifyInstance } from 'fastify';\nimport type { Container } from '../../../app/container.js';\nimport { memberAssetsRoutes } from './assets.js';\nimport { memberChatRoutes } from './chats.js';\nimport { memberCommentsRoutes } from './comments.js';\nimport { memberCommunityRoutes } from './community.js';\nimport { memberCoursesRoutes } from './courses.js';\nimport { memberDownloadsRoutes } from './downloads.js';\nimport { memberFileRoutes } from './files.js';\nimport { memberNotificationRoutes } from './notifications.js';\nimport { memberNotificationSettingsRoutes } from './notification-settings.js';\nimport { memberDeviceRoutes } from './devices.js';\nimport { memberOrgRoutes } from './org.js';\nimport { memberPostRoutes } from './posts.js';\nimport { memberProfileRoutes } from './profile.js';\nimport { memberProgressRoutes } from './progress.js';\n\nexport async function memberRoutes(app: FastifyInstance, container: Container): Promise<void> {\n await memberOrgRoutes(app, container);\n await memberProfileRoutes(app, container);\n await memberCommunityRoutes(app, container);\n await memberPostRoutes(app, container);\n await memberChatRoutes(app, container);\n await memberCoursesRoutes(app, container);\n await memberProgressRoutes(app, container);\n await memberCommentsRoutes(app, container);\n await memberAssetsRoutes(app, container);\n await memberDownloadsRoutes(app, container);\n await memberFileRoutes(app, container);\n await memberNotificationRoutes(app, container);\n await memberNotificationSettingsRoutes(app, container);\n await memberDeviceRoutes(app, container);\n}\n"],"mappings":";;;;;;;;;;;;;;;AAsBA,eAAsB,aAAa,KAAsB,WAAqC;CAC5F,MAAM,gBAAgB,KAAK,SAAS;CACpC,MAAM,oBAAoB,KAAK,SAAS;CACxC,MAAM,sBAAsB,KAAK,SAAS;CAC1C,MAAM,iBAAiB,KAAK,SAAS;CACrC,MAAM,iBAAiB,KAAK,SAAS;CACrC,MAAM,oBAAoB,KAAK,SAAS;CACxC,MAAM,qBAAqB,KAAK,SAAS;CACzC,MAAM,qBAAqB,KAAK,SAAS;CACzC,MAAM,mBAAmB,KAAK,SAAS;CACvC,MAAM,sBAAsB,KAAK,SAAS;CAC1C,MAAM,iBAAiB,KAAK,SAAS;CACrC,MAAM,yBAAyB,KAAK,SAAS;CAC7C,MAAM,iCAAiC,KAAK,SAAS;CACrD,MAAM,mBAAmB,KAAK,SAAS;AACzC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Container } from "../../../app/container.js";
|
|
2
|
+
import { FastifyInstance } from "fastify";
|
|
3
|
+
//#region src/http/routes/member/notification-settings.d.ts
|
|
4
|
+
declare function memberNotificationSettingsRoutes(app: FastifyInstance, container: Container): Promise<void>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { memberNotificationSettingsRoutes };
|
|
7
|
+
//# sourceMappingURL=notification-settings.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { refusals } from "../../schemas/shared.js";
|
|
2
|
+
import { CommunityParam } from "../../schemas/chats.js";
|
|
3
|
+
import { CommunityNotificationSettings, OrgNotificationSettings, PatchNotificationSettings } from "../../schemas/notifications.js";
|
|
4
|
+
import "../../schemas/index.js";
|
|
5
|
+
import { actorOf } from "./actor.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { NotFoundError } from "@headless-lms/core/shared/errors";
|
|
8
|
+
//#region src/http/routes/member/notification-settings.ts
|
|
9
|
+
async function memberNotificationSettingsRoutes(app, container) {
|
|
10
|
+
const r = app.withTypeProvider();
|
|
11
|
+
const tags = ["Notifications"];
|
|
12
|
+
r.route({
|
|
13
|
+
method: "GET",
|
|
14
|
+
url: "/api/member/communities/:communityId/settings/notifications",
|
|
15
|
+
preHandler: app.requireOrgSession,
|
|
16
|
+
schema: {
|
|
17
|
+
operationId: "getMemberCommunityNotificationSettings",
|
|
18
|
+
tags,
|
|
19
|
+
summary: "Read how you are told about each notification category in a community",
|
|
20
|
+
description: "For anyone who can open the community, staff included. One entry per category of the list in use, each with the channels it offers on this deployment, whether each is on for you and why (`locked`, `space`, `community`, `spaceDefault` or `default`), and `default`, what the channel would be without your own setting at that level. A per-space category also lists every space you can open, with its name. `digest` is the digest schedule of the community, null when it sends none. 404 when you cannot open the community.",
|
|
21
|
+
params: CommunityParam,
|
|
22
|
+
response: {
|
|
23
|
+
200: CommunityNotificationSettings,
|
|
24
|
+
...refusals(403, 404)
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
handler: async (req) => {
|
|
28
|
+
const actor = await actorOf(container, req.orgId, req.userId);
|
|
29
|
+
const view = await container.reporting.member.communityNotificationSettings(req.orgId, actor, req.userId, req.params.communityId);
|
|
30
|
+
if (!view) throw new NotFoundError("Community", req.params.communityId);
|
|
31
|
+
return view;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
r.route({
|
|
35
|
+
method: "PATCH",
|
|
36
|
+
url: "/api/member/communities/:communityId/settings/notifications",
|
|
37
|
+
preHandler: app.requireOrgSession,
|
|
38
|
+
schema: {
|
|
39
|
+
operationId: "setMemberCommunityNotificationSettings",
|
|
40
|
+
tags,
|
|
41
|
+
summary: "Switch notification categories on or off per channel in a community",
|
|
42
|
+
description: "For anyone who can open the community, staff included. Each setting switches one category on one channel, for all spaces or, for a per-space category, for one space; `enabled: null` removes your choice so the default applies again. Settings left out keep their values. 404 when you cannot open the community; 422 when a category does not exist, does not offer the channel, the channel is locked, or `spaceId` is given for a category that is not per space.",
|
|
43
|
+
params: CommunityParam,
|
|
44
|
+
body: PatchNotificationSettings,
|
|
45
|
+
response: {
|
|
46
|
+
204: z.void(),
|
|
47
|
+
...refusals(400, 403, 404, 422)
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
handler: async (req, reply) => {
|
|
51
|
+
const actor = await actorOf(container, req.orgId, req.userId);
|
|
52
|
+
await container.notificationSettings.set(req.orgId, {
|
|
53
|
+
id: actor.id,
|
|
54
|
+
role: actor.role,
|
|
55
|
+
userId: req.userId
|
|
56
|
+
}, { communityId: req.params.communityId }, req.body.settings);
|
|
57
|
+
return reply.code(204).send();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
r.route({
|
|
61
|
+
method: "GET",
|
|
62
|
+
url: "/api/member/settings/notifications",
|
|
63
|
+
preHandler: app.requireOrgSession,
|
|
64
|
+
schema: {
|
|
65
|
+
operationId: "getMemberNotificationSettings",
|
|
66
|
+
tags,
|
|
67
|
+
summary: "Read how you are told about notifications outside any community",
|
|
68
|
+
description: "For anyone signed in to the organization. The organization's categories, such as access and course content, each with the channels it offers, whether each is on for you, and `default`, what it would be without your own setting.",
|
|
69
|
+
response: {
|
|
70
|
+
200: OrgNotificationSettings,
|
|
71
|
+
...refusals(403)
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
handler: async (req) => {
|
|
75
|
+
await actorOf(container, req.orgId, req.userId);
|
|
76
|
+
return container.reporting.member.orgNotificationSettings(req.orgId, req.userId);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
r.route({
|
|
80
|
+
method: "PATCH",
|
|
81
|
+
url: "/api/member/settings/notifications",
|
|
82
|
+
preHandler: app.requireOrgSession,
|
|
83
|
+
schema: {
|
|
84
|
+
operationId: "setMemberNotificationSettings",
|
|
85
|
+
tags,
|
|
86
|
+
summary: "Switch notification categories outside any community on or off per channel",
|
|
87
|
+
description: "For anyone signed in to the organization. Works like the community settings, against the organization's categories; `enabled: null` removes your choice. 422 when a category does not exist, does not offer the channel, the channel is locked, or `spaceId` is given.",
|
|
88
|
+
body: PatchNotificationSettings,
|
|
89
|
+
response: {
|
|
90
|
+
204: z.void(),
|
|
91
|
+
...refusals(400, 403, 422)
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
handler: async (req, reply) => {
|
|
95
|
+
const actor = await actorOf(container, req.orgId, req.userId);
|
|
96
|
+
await container.notificationSettings.set(req.orgId, {
|
|
97
|
+
id: actor.id,
|
|
98
|
+
role: actor.role,
|
|
99
|
+
userId: req.userId
|
|
100
|
+
}, "org", req.body.settings);
|
|
101
|
+
return reply.code(204).send();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { memberNotificationSettingsRoutes };
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=notification-settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notification-settings.js","names":[],"sources":["../../../../src/http/routes/member/notification-settings.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 { actorOf } from './actor.js';\nimport {\n CommunityParam,\n CommunityNotificationSettings,\n OrgNotificationSettings,\n PatchNotificationSettings,\n refusals,\n} from '../../schemas/index.js';\n\nexport async function memberNotificationSettingsRoutes(\n app: FastifyInstance,\n container: Container,\n): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Notifications'];\n\n r.route({\n method: 'GET',\n url: '/api/member/communities/:communityId/settings/notifications',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberCommunityNotificationSettings',\n tags,\n summary: 'Read how you are told about each notification category in a community',\n description:\n 'For anyone who can open the community, staff included. One entry per category of the list in use, each with the channels it offers on this deployment, whether each is on for you and why (`locked`, `space`, `community`, `spaceDefault` or `default`), and `default`, what the channel would be without your own setting at that level. A per-space category also lists every space you can open, with its name. `digest` is the digest schedule of the community, null when it sends none. 404 when you cannot open the community.',\n params: CommunityParam,\n response: { 200: CommunityNotificationSettings, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n const view = await container.reporting.member.communityNotificationSettings(\n req.orgId,\n actor,\n req.userId,\n req.params.communityId,\n );\n if (!view) {\n throw new NotFoundError('Community', req.params.communityId);\n }\n return view;\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/communities/:communityId/settings/notifications',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setMemberCommunityNotificationSettings',\n tags,\n summary: 'Switch notification categories on or off per channel in a community',\n description:\n 'For anyone who can open the community, staff included. Each setting switches one category on one channel, for all spaces or, for a per-space category, for one space; `enabled: null` removes your choice so the default applies again. Settings left out keep their values. 404 when you cannot open the community; 422 when a category does not exist, does not offer the channel, the channel is locked, or `spaceId` is given for a category that is not per space.',\n params: CommunityParam,\n body: PatchNotificationSettings,\n response: { 204: z.void(), ...refusals(400, 403, 404, 422) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await container.notificationSettings.set(\n req.orgId,\n { id: actor.id, role: actor.role, userId: req.userId },\n { communityId: req.params.communityId },\n req.body.settings,\n );\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/member/settings/notifications',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMemberNotificationSettings',\n tags,\n summary: 'Read how you are told about notifications outside any community',\n description:\n \"For anyone signed in to the organization. The organization's categories, such as access and course content, each with the channels it offers, whether each is on for you, and `default`, what it would be without your own setting.\",\n response: { 200: OrgNotificationSettings, ...refusals(403) },\n },\n handler: async (req) => {\n await actorOf(container, req.orgId, req.userId);\n return container.reporting.member.orgNotificationSettings(req.orgId, req.userId);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/member/settings/notifications',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'setMemberNotificationSettings',\n tags,\n summary: 'Switch notification categories outside any community on or off per channel',\n description:\n \"For anyone signed in to the organization. Works like the community settings, against the organization's categories; `enabled: null` removes your choice. 422 when a category does not exist, does not offer the channel, the channel is locked, or `spaceId` is given.\",\n body: PatchNotificationSettings,\n response: { 204: z.void(), ...refusals(400, 403, 422) },\n },\n handler: async (req, reply) => {\n const actor = await actorOf(container, req.orgId, req.userId);\n await container.notificationSettings.set(\n req.orgId,\n { id: actor.id, role: actor.role, userId: req.userId },\n 'org',\n req.body.settings,\n );\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":";;;;;;;;AAcA,eAAsB,iCACpB,KACA,WACe;CACf,MAAM,IAAI,IAAI,iBAAkC;CAChD,MAAM,OAAO,CAAC,eAAe;CAE7B,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,UAAU;IAAE,KAAK;IAA+B,GAAG,SAAS,KAAK,GAAG;GAAE;EACxE;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC5D,MAAM,OAAO,MAAM,UAAU,UAAU,OAAO,8BAC5C,IAAI,OACJ,OACA,IAAI,QACJ,IAAI,OAAO,WACb;GACA,IAAI,CAAC,MACH,MAAM,IAAI,cAAc,aAAa,IAAI,OAAO,WAAW;GAE7D,OAAO;EACT;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,QAAQ;GACR,MAAM;GACN,UAAU;IAAE,KAAK,EAAE,KAAK;IAAG,GAAG,SAAS,KAAK,KAAK,KAAK,GAAG;GAAE;EAC7D;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC5D,MAAM,UAAU,qBAAqB,IACnC,IAAI,OACJ;IAAE,IAAI,MAAM;IAAI,MAAM,MAAM;IAAM,QAAQ,IAAI;GAAO,GACrD,EAAE,aAAa,IAAI,OAAO,YAAY,GACtC,IAAI,KAAK,QACX;GACA,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,UAAU;IAAE,KAAK;IAAyB,GAAG,SAAS,GAAG;GAAE;EAC7D;EACA,SAAS,OAAO,QAAQ;GACtB,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC9C,OAAO,UAAU,UAAU,OAAO,wBAAwB,IAAI,OAAO,IAAI,MAAM;EACjF;CACF,CAAC;CAED,EAAE,MAAM;EACN,QAAQ;EACR,KAAK;EACL,YAAY,IAAI;EAChB,QAAQ;GACN,aAAa;GACb;GACA,SAAS;GACT,aACE;GACF,MAAM;GACN,UAAU;IAAE,KAAK,EAAE,KAAK;IAAG,GAAG,SAAS,KAAK,KAAK,GAAG;GAAE;EACxD;EACA,SAAS,OAAO,KAAK,UAAU;GAC7B,MAAM,QAAQ,MAAM,QAAQ,WAAW,IAAI,OAAO,IAAI,MAAM;GAC5D,MAAM,UAAU,qBAAqB,IACnC,IAAI,OACJ;IAAE,IAAI,MAAM;IAAI,MAAM,MAAM;IAAM,QAAQ,IAAI;GAAO,GACrD,OACA,IAAI,KAAK,QACX;GACA,OAAO,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;EAC9B;CACF,CAAC;AACH"}
|