@headless-lms/core 0.6.0 → 0.8.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/assets/file-urls.d.ts +5 -19
- package/dist/assets/file-urls.js +2 -23
- package/dist/assets/file-urls.js.map +1 -1
- package/dist/assets/index.d.ts +3 -3
- package/dist/assets/ports.d.ts +9 -10
- package/dist/assets/service.d.ts +10 -13
- package/dist/assets/service.js +41 -62
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/index.d.ts +4 -4
- package/dist/automations/notifications/delivery.d.ts +3 -3
- package/dist/automations/notifications/delivery.js +1 -1
- package/dist/automations/notifications/delivery.js.map +1 -1
- package/dist/automations/ports.d.ts +2 -2
- package/dist/automations/service.d.ts +1 -8
- package/dist/automations/service.js +0 -8
- package/dist/automations/service.js.map +1 -1
- package/dist/content/service.d.ts +4 -1
- package/dist/content/service.js +52 -29
- package/dist/content/service.js.map +1 -1
- package/dist/discussion/chats.d.ts +1 -2
- package/dist/discussion/chats.js +8 -17
- package/dist/discussion/chats.js.map +1 -1
- package/dist/discussion/ports.d.ts +0 -6
- package/dist/discussion/ports.js.map +1 -1
- package/dist/discussion/posts.d.ts +1 -4
- package/dist/discussion/posts.js +8 -21
- package/dist/discussion/posts.js.map +1 -1
- package/dist/entitlements/events.d.ts +33 -33
- package/dist/identity/events.d.ts +2 -2
- package/dist/integrations/events.d.ts +3 -3
- package/dist/organizations/events.d.ts +2 -2
- package/dist/reporting/member/ports.d.ts +1 -10
- package/dist/reporting/member/service.d.ts +2 -11
- package/dist/reporting/member/service.js +12 -62
- package/dist/reporting/member/service.js.map +1 -1
- package/dist/types/automations.d.ts +5 -2
- package/dist/types/index.d.ts +2 -2
- package/dist/types/ports.d.ts +4 -2
- package/dist/types/ports.js.map +1 -1
- package/dist/types/schemas/content.d.ts +2 -2
- package/dist/types/schemas/discussion.d.ts +14 -14
- package/dist/types/schemas/entitlements.d.ts +8 -8
- package/dist/types/schemas/notifications.d.ts +3 -3
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chats.js","names":[],"sources":["../../src/discussion/chats.ts"],"sourcesContent":["import { genId } from '../shared/id.js';\nimport {\n ConflictError,\n ForbiddenError,\n InvalidInputError,\n NotFoundError,\n UnavailableError,\n} from '../shared/errors.js';\nimport type { Logger, PubSub } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport type { DmSettings } from '../content/index.js';\nimport type { AssetFiles, FileUrls, MemberImageReader } from '../assets/index.js';\nimport type {\n Chat,\n ChatLiveEvent,\n ChatUnreadSummary,\n ChatUser,\n LiveMessage,\n Message,\n MessageFlag,\n MessageFlagResolution,\n MessageView,\n Page,\n ReactionCounts,\n ReactionEmoji,\n} from '../types/index.js';\nimport type { Actor } from '../shared/actor.js';\nimport type { NewDiscussionEvent } from './events.js';\nimport { discussionEvents } from './events.js';\nimport { authorName, authorView } from './authors.js';\nimport { activeSubscription } from './subscriptions.js';\nimport { keepAllowed, mentionTokens, mentionViews } from './mentions.js';\nimport { isTeam, teamBadge } from './rules.js';\nimport { ChatHub, TYPING, publishChange, type ChatHubOptions } from './live.js';\nimport type { CommentAuthor } from './model.js';\nimport type {\n AuthorPermissionReader,\n AuthorProfileReader,\n ChatInboxRow,\n ChatListItem,\n ChatListQuery,\n ChatLiveReads,\n ChatMemberStates,\n ChatRepository,\n ChatService,\n ChatUnread,\n ChatUnitOfWork,\n LiveChat,\n MentionsRepository,\n MessageQueue,\n MessageQueueEntry,\n MessageReactions,\n SendMessageInput,\n StartDirectInput,\n StartGroupInput,\n CommunityAccess,\n SpaceReader,\n SubscriptionsRepository,\n} from './ports.js';\nimport { MODERATION_QUEUE_LIMIT } from './ports.js';\n\nconst TYPING_FOR_MS = 5000;\nconst TYPING_REPEAT_MS = 2000;\n\nexport type ChatServiceParams = {\n repo: ChatRepository;\n access: CommunityAccess;\n content: Pick<SpaceReader, 'getDmSettings'>;\n authors: AuthorPermissionReader;\n profiles: AuthorProfileReader;\n subscriptions: SubscriptionsRepository;\n mentions: MentionsRepository;\n images: MemberImageReader & Pick<AssetFiles, 'signFile'>;\n fileUrls: FileUrls;\n uow: ChatUnitOfWork;\n pubsub?: PubSub;\n live?: ChatHubOptions;\n logger?: Logger;\n};\n\nfunction eventMessage(message: Message): Omit<Message, 'clientId'> {\n const { clientId: _clientId, ...rest } = message;\n return rest;\n}\n\ntype Teams = { asked: ReadonlySet<string>; team: Map<string, boolean> };\n\nexport class ChatServiceImpl implements ChatService, ChatLiveReads {\n /** This process's live chat hub; null without a PubSub. */\n readonly hub: ChatHub | null;\n private readonly repo: ChatRepository;\n private readonly access: CommunityAccess;\n private readonly content: Pick<SpaceReader, 'getDmSettings'>;\n private readonly authors: AuthorPermissionReader;\n private readonly profiles: AuthorProfileReader;\n private readonly subscriptions: SubscriptionsRepository;\n private readonly mentions: MentionsRepository;\n private readonly images: MemberImageReader & Pick<AssetFiles, 'signFile'>;\n private readonly fileUrls: FileUrls;\n private readonly uow: ChatUnitOfWork;\n private readonly pubsub: PubSub | null;\n private readonly logger: Logger;\n private readonly typed = new Map<string, number>();\n\n constructor(params: ChatServiceParams) {\n this.repo = params.repo;\n this.access = params.access;\n this.content = params.content;\n this.authors = params.authors;\n this.profiles = params.profiles;\n this.subscriptions = params.subscriptions;\n this.mentions = params.mentions;\n this.images = params.images;\n this.fileUrls = params.fileUrls;\n this.uow = params.uow;\n this.pubsub = params.pubsub ?? null;\n this.logger = params.logger ?? noopLogger;\n this.hub = this.pubsub\n ? new ChatHub({ pubsub: this.pubsub, reads: this, logger: this.logger, ...params.live })\n : null;\n }\n\n async startDirect(input: StartDirectInput, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n if (actor.id === input.orgUserId) {\n throw new InvalidInputError('You cannot message yourself');\n }\n if (input.body === null && input.assetIds.length > 0) {\n throw new InvalidInputError('An attachment needs a message');\n }\n const settings = await this.requireSettings(orgId, input.communityId);\n await this.requireActor(actor, input.communityId);\n const targetTeam = await this.requirePerson(orgId, input.communityId, input.orgUserId);\n const actorTeam = isTeam(actor.permissions, input.communityId);\n this.assertMayMessage(settings, actorTeam, targetTeam);\n const first: SendMessageInput | null =\n input.body === null ? null : { body: input.body, replyToId: null, assetIds: input.assetIds };\n await this.requireOwnedImages(orgId, actor.id, input.assetIds);\n const teams: Teams = {\n asked: new Set([input.orgUserId]),\n team: new Map([[input.orgUserId, targetTeam]]),\n };\n const saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n await chats.lockDirectPair(orgId, input.communityId, actor.id, input.orgUserId);\n const existing = await chats.findDirect(orgId, input.communityId, actor.id, input.orgUserId);\n if (existing) {\n const users = await chats.participants(orgId, existing.id);\n const rejoin = users.some((user) => user.orgUserId === actor.id && user.leftAt !== null);\n const present = users.map((user) =>\n user.orgUserId === actor.id ? { ...user, leftAt: null } : user,\n );\n if (first) {\n await this.assertMaySend(chats, orgId, actor, existing, present, settings, first, teams);\n }\n if (rejoin) {\n await chats.join(\n orgId,\n this.memberRow(orgId, existing.communityId, existing.id, actor.id, new Date()),\n );\n }\n const written = first\n ? await this.writeMessage(chats, mentions, orgId, actor, existing, present, first)\n : null;\n const chat = written?.chat ?? existing;\n const events: NewDiscussionEvent[] = [];\n if (rejoin || written?.opened) {\n events.push(this.updatedEvent(orgId, actor, chat, present));\n }\n if (written) {\n events.push(\n this.sentEvent(orgId, actor, written.chat, written.message, written.mentioned),\n );\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n return { chat, created: false };\n }\n if (await chats.isBlocked(orgId, input.communityId, actor.id, input.orgUserId)) {\n throw new ForbiddenError('You cannot message this person');\n }\n const request = settings.firstMessageIsRequest && !actorTeam && !targetTeam;\n if (request && !first) {\n throw new InvalidInputError('A message request needs a message');\n }\n const now = new Date();\n const chat = await chats.insertChat(orgId, {\n orgId,\n id: genId('chat'),\n communityId: input.communityId,\n kind: 'direct',\n status: request ? 'pending' : 'open',\n createdBy: actor.id,\n lockedAt: null,\n lockedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n const users = [actor.id, input.orgUserId].map((orgUserId) =>\n this.memberRow(orgId, chat.communityId, chat.id, orgUserId, now),\n );\n for (const user of users) {\n await chats.join(orgId, user);\n }\n const events: NewDiscussionEvent[] = [this.createdEvent(orgId, actor, chat, users)];\n if (first) {\n const written = await this.writeMessage(chats, mentions, orgId, actor, chat, users, first);\n events.push(this.sentEvent(orgId, actor, written.chat, written.message, written.mentioned));\n }\n await outbox.append(events);\n return { chat, created: true };\n });\n if (saved.created) {\n this.logger.info('direct chat started', {\n orgId,\n communityId: input.communityId,\n chatId: saved.chat.id,\n status: saved.chat.status,\n actorId: actor.id,\n });\n } else {\n this.logger.debug('direct chat already exists', {\n orgId,\n communityId: input.communityId,\n chatId: saved.chat.id,\n });\n }\n return saved.chat;\n }\n\n async startGroup(input: StartGroupInput, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const settings = await this.requireSettings(orgId, input.communityId);\n this.assertGroup(settings);\n await this.requireActor(actor, input.communityId);\n const others = [...new Set(input.orgUserIds.filter((id) => id !== actor.id))].sort();\n if (others.length < 2) {\n throw new InvalidInputError('A group chat needs three people');\n }\n if (others.length + 1 > settings.groupSize) {\n throw new ConflictError('This group is full');\n }\n const actorTeam = isTeam(actor.permissions, input.communityId);\n for (const orgUserId of others) {\n const targetTeam = await this.requirePerson(orgId, input.communityId, orgUserId);\n this.assertMayMessage(settings, actorTeam, targetTeam);\n }\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n if (await chats.anyBlockedAmong(orgId, input.communityId, [actor.id, ...others])) {\n throw new ForbiddenError('You cannot message this person');\n }\n const now = new Date();\n const chat = await chats.insertChat(orgId, {\n orgId,\n id: genId('chat'),\n communityId: input.communityId,\n kind: 'group',\n status: 'open',\n createdBy: actor.id,\n lockedAt: null,\n lockedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n const ids = [actor.id, ...others];\n const users = ids.map((orgUserId) =>\n this.memberRow(orgId, chat.communityId, chat.id, orgUserId, now),\n );\n for (const user of users) {\n await chats.join(orgId, user);\n }\n const events: NewDiscussionEvent[] = [\n this.createdEvent(orgId, actor, chat, users),\n ...others.map((orgUserId) => this.addedEvent(orgId, actor, chat, orgUserId)),\n ];\n await outbox.append(events);\n return chat;\n });\n this.logger.info('group chat started', {\n orgId,\n communityId: input.communityId,\n chatId: saved.id,\n actorId: actor.id,\n });\n return saved;\n }\n\n async addParticipant(chatId: string, orgUserId: string, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.kind !== 'group') {\n throw new ConflictError('This chat is not a group');\n }\n const settings = await this.requireSettings(orgId, chat.communityId);\n this.assertGroup(settings);\n const targetTeam = await this.requirePerson(orgId, chat.communityId, orgUserId);\n this.assertMayMessage(settings, isTeam(actor.permissions, chat.communityId), targetTeam);\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n const users = await chats.participants(orgId, chatId);\n this.assertActive(users, actor.id);\n const existing = users.find((user) => user.orgUserId === orgUserId);\n if (existing && existing.leftAt === null) {\n throw new ConflictError('That person is already in this chat');\n }\n const active = users.filter((user) => user.leftAt === null);\n if (active.length + 1 > settings.groupSize) {\n throw new ConflictError('This group is full');\n }\n const blocked = await chats.blockedWith(\n orgId,\n current.communityId,\n orgUserId,\n active.map((user) => user.orgUserId),\n );\n if (blocked.length > 0) {\n throw new ForbiddenError('You cannot message this person');\n }\n const now = new Date();\n await chats.join(\n orgId,\n this.memberRow(orgId, current.communityId, current.id, orgUserId, now),\n );\n await outbox.append([this.addedEvent(orgId, actor, current, orgUserId)]);\n return current;\n });\n this.logger.info('chat participant added', { orgId, chatId, orgUserId, actorId: actor.id });\n return saved;\n }\n\n async leave(chatId: string, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n const users = await chats.participants(orgId, chatId);\n this.assertActive(users, actor.id);\n const now = new Date();\n await chats.markLeft(orgId, chatId, actor.id, now);\n const remaining = users.map((user) =>\n user.orgUserId === actor.id ? { ...user, leftAt: now } : user,\n );\n await outbox.append([this.updatedEvent(orgId, actor, current, remaining)]);\n return current;\n });\n this.logger.info('chat left', { orgId, chatId, actorId: actor.id });\n return saved;\n }\n\n async accept(chatId: string, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.createdBy === actor.id) {\n throw new ForbiddenError('You cannot accept this request');\n }\n if (chat.kind !== 'direct' || chat.status !== 'pending') {\n throw new ConflictError('This chat is not a request');\n }\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n if (current.kind !== 'direct' || current.status !== 'pending') {\n throw new ConflictError('This chat is not a request');\n }\n const next = await this.mustUpdate(chats, orgId, chatId, { status: 'open' });\n const users = await chats.participants(orgId, chatId);\n await outbox.append([this.updatedEvent(orgId, actor, next, users)]);\n return next;\n });\n this.logger.info('chat accepted', { orgId, chatId, actorId: actor.id });\n return saved;\n }\n\n async send(chatId: string, input: SendMessageInput, actor: Actor): Promise<MessageView> {\n const orgId = actor.orgId;\n const preview = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, preview.communityId);\n const settings = await this.requireSettings(orgId, preview.communityId);\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n const clientId = input.clientId;\n const before = await this.repo.participants(orgId, chatId);\n const teams = await this.teamsOf(\n orgId,\n preview.communityId,\n before.map((user) => user.orgUserId).filter((orgUserId) => orgUserId !== actor.id),\n );\n await this.requireOwnedImages(orgId, actor.id, input.assetIds);\n let saved: { message: Message; repeat: boolean };\n try {\n saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n const chat = await this.mustChat(chats, orgId, chatId);\n if (clientId) {\n const stored = await chats.findByClientId(orgId, chatId, actor.id, clientId);\n if (stored) {\n return { message: stored, repeat: true };\n }\n }\n const users = await chats.participants(orgId, chatId);\n await this.assertMaySend(chats, orgId, actor, chat, users, settings, input, teams);\n const written = await this.writeMessage(chats, mentions, orgId, actor, chat, users, input);\n const events: NewDiscussionEvent[] = [];\n if (written.opened) {\n events.push(this.updatedEvent(orgId, actor, written.chat, users));\n }\n events.push(this.sentEvent(orgId, actor, written.chat, written.message, written.mentioned));\n await outbox.append(events);\n return { message: written.message, repeat: false };\n });\n } catch (error) {\n const stored =\n clientId && error instanceof ConflictError\n ? await this.repo.findByClientId(orgId, chatId, actor.id, clientId)\n : null;\n if (!stored) {\n throw error;\n }\n saved = { message: stored, repeat: true };\n }\n if (saved.repeat) {\n this.logger.debug('message send repeated', {\n orgId,\n chatId,\n messageId: saved.message.id,\n });\n } else {\n this.logger.info('message sent', {\n orgId,\n chatId,\n messageId: saved.message.id,\n actorId: actor.id,\n });\n }\n return this.viewOf(actor, saved.message);\n }\n\n async editMessage(messageId: string, body: string, actor: Actor): Promise<MessageView> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireWritableChat(actor, message.chatId);\n if (message.orgUserId !== actor.id) {\n throw new ForbiddenError('You can change only your own message');\n }\n if (message.deletedAt) {\n throw new ConflictError('This message was deleted');\n }\n if (message.body === body) {\n return this.viewOf(actor, message);\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n const next = await chats.updateMessage(orgId, messageId, { body, editedAt: now });\n if (!next) {\n throw new NotFoundError('Message', messageId);\n }\n const chat = await this.mustChat(chats, orgId, next.chatId);\n const users = await chats.participants(orgId, next.chatId);\n const candidates = this.mentionable(body, users);\n const added = await mentions.sync(orgId, 'message', messageId, candidates, now);\n await outbox.append([\n discussionEvents.messageUpdated.make({\n orgId,\n data: { ...eventMessage(next), mentionedOrgUserIds: keepAllowed(candidates, added) },\n metadata: { ...this.meta(actor), chatStatus: chat.status },\n }),\n ]);\n return next;\n });\n this.logger.info('message edited', { orgId, messageId, actorId: actor.id });\n return this.viewOf(actor, saved);\n }\n\n async deleteMessage(messageId: string, actor: Actor): Promise<MessageView> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(actor, message.communityId);\n if (!actor.permissions.has('communities:moderate', message.communityId)) {\n if (message.orgUserId !== actor.id) {\n this.logger.warn('message delete refused — not the sender or a moderator', {\n orgId,\n messageId,\n actorId: actor.id,\n });\n throw new ForbiddenError('You cannot delete this message');\n }\n await this.requireWritableChat(actor, message.chatId);\n }\n if (message.deletedAt) {\n throw new ConflictError('This message was deleted');\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n const next = await chats.updateMessage(orgId, messageId, { deletedAt: now });\n if (!next) {\n throw new NotFoundError('Message', messageId);\n }\n await mentions.removeItem(orgId, 'message', messageId);\n await chats.resolveFlags(orgId, messageId, {\n resolvedAt: now,\n resolvedBy: actor.id,\n resolution: 'deleted',\n });\n await outbox.append([\n discussionEvents.messageDeleted.make({\n orgId,\n data: eventMessage(next),\n metadata: this.meta(actor),\n }),\n ]);\n return next;\n });\n this.logger.info('message deleted', { orgId, messageId, actorId: actor.id });\n return this.viewOf(actor, saved);\n }\n\n async setReaction(\n messageId: string,\n emoji: ReactionEmoji | null,\n actor: Actor,\n ): Promise<MessageReactions> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(actor, message.communityId);\n await this.requireActive(this.repo, orgId, message.chatId, actor.id);\n await this.uow.run(async ({ chats, outbox }) => {\n const change = await chats.setReaction(\n orgId,\n message.communityId,\n messageId,\n actor.id,\n emoji,\n );\n if (!change.changed) {\n return;\n }\n const where = {\n messageId,\n chatId: message.chatId,\n communityId: message.communityId,\n orgUserId: actor.id,\n };\n const events: NewDiscussionEvent[] = [];\n if (change.inserted && emoji !== null) {\n events.push(\n discussionEvents.messageReactionAdded.make({\n orgId,\n data: { ...where, reaction: emoji },\n metadata: this.meta(actor),\n }),\n );\n }\n events.push(\n discussionEvents.messageReactionsChanged.make({\n orgId,\n data: { ...where, reaction: emoji },\n metadata: this.meta(actor),\n }),\n );\n await outbox.append(events);\n });\n this.logger.debug('message reaction set', { orgId, messageId });\n const counts = await this.repo.reactionsOf(orgId, [messageId], actor.id);\n return counts[messageId] ?? { reactions: {} };\n }\n\n async markRead(chatId: string, messageId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const message = await this.mustMessage(this.repo, orgId, messageId);\n if (message.chatId !== chatId) {\n throw new NotFoundError('Message', messageId);\n }\n const moved = await this.uow.run(async ({ chats, outbox }) => {\n if (!(await chats.setRead(orgId, chatId, actor.id, messageId))) {\n return false;\n }\n await outbox.append([\n discussionEvents.chatRead.make({\n orgId,\n data: {\n chatId,\n communityId: chat.communityId,\n orgUserId: actor.id,\n lastReadMessageId: messageId,\n },\n metadata: this.meta(actor),\n }),\n ]);\n return true;\n });\n this.logger.debug(moved ? 'chat marked read' : 'chat read mark kept', {\n orgId,\n chatId,\n messageId,\n });\n }\n\n async block(communityId: string, orgUserId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n if (actor.id === orgUserId) {\n throw new InvalidInputError('You cannot block yourself');\n }\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n await this.requirePerson(orgId, communityId, orgUserId);\n await this.uow.run(async ({ chats }) => {\n await chats.lockDirectPair(orgId, communityId, actor.id, orgUserId);\n await chats.block(orgId, communityId, actor.id, orgUserId);\n });\n this.logger.info('user blocked', { orgId, communityId, actorId: actor.id });\n }\n\n async unblock(communityId: string, orgUserId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n if (actor.id === orgUserId) {\n throw new InvalidInputError('You cannot block yourself');\n }\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n await this.uow.run(async ({ chats }) => {\n await chats.lockDirectPair(orgId, communityId, actor.id, orgUserId);\n await chats.unblock(orgId, communityId, actor.id, orgUserId);\n });\n this.logger.info('user unblocked', { orgId, communityId, actorId: actor.id });\n }\n\n async flagMessage(messageId: string, reason: string, actor: Actor): Promise<MessageFlag> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(actor, message.communityId);\n await this.requireActive(this.repo, orgId, message.chatId, actor.id);\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const flag = await chats.insertFlag(orgId, {\n orgId,\n id: genId('messageFlag'),\n communityId: message.communityId,\n messageId,\n orgUserId: actor.id,\n reason,\n resolvedAt: null,\n resolution: null,\n resolvedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n if (!flag) {\n const existing = await chats.findFlag(orgId, messageId, actor.id);\n if (!existing) {\n throw new NotFoundError('Message', messageId);\n }\n return existing;\n }\n await outbox.append([\n discussionEvents.messageReported.make({\n orgId,\n data: flag,\n metadata: this.meta(actor),\n }),\n ]);\n return flag;\n });\n this.logger.info('message flagged', { orgId, messageId, actorId: actor.id });\n return saved;\n }\n\n async resolveMessageFlags(\n messageId: string,\n resolution: Extract<MessageFlagResolution, 'dismissed'>,\n communityId: string | null,\n actor: Actor,\n ): Promise<void> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n if (communityId !== null && message.communityId !== communityId) {\n throw new NotFoundError('Message', messageId);\n }\n this.requireModerator(actor, message.communityId, 'resolve message flags');\n await this.requireActor(actor, message.communityId);\n await this.repo.resolveFlags(orgId, messageId, {\n resolvedAt: new Date(),\n resolvedBy: actor.id,\n resolution,\n });\n this.logger.info('message flags resolved', {\n orgId,\n communityId: message.communityId,\n messageId,\n resolution,\n actorId: actor.id,\n });\n }\n\n async lock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat> {\n return this.setLock(actor, chatId, true, communityId);\n }\n\n async unlock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat> {\n return this.setLock(actor, chatId, false, communityId);\n }\n\n async listChats(\n communityId: string,\n query: ChatListQuery,\n actor: Actor,\n ): Promise<Page<ChatListItem>> {\n const orgId = actor.orgId;\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n const page = await this.repo.listForUser(orgId, communityId, actor.id, query);\n const items = await this.listItems(actor, communityId, page.rows);\n this.logger.debug('chats listed', { orgId, communityId, count: items.length });\n return { ...page, rows: items };\n }\n\n async chat(chatId: string, actor: Actor): Promise<ChatListItem> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const row = await this.repo.inboxRow(orgId, chatId, actor.id);\n if (!row) {\n throw new NotFoundError('Chat', chatId);\n }\n const [item] = await this.listItems(actor, chat.communityId, [row]);\n this.logger.debug('chat read', { orgId, chatId });\n return item!;\n }\n\n async directChat(communityId: string, orgUserId: string, actor: Actor): Promise<ChatListItem> {\n const orgId = actor.orgId;\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n const chat =\n orgUserId === actor.id\n ? null\n : await this.repo.findDirect(orgId, communityId, actor.id, orgUserId);\n const row = chat ? await this.repo.inboxRow(orgId, chat.id, actor.id) : null;\n if (!row) {\n throw new NotFoundError('Chat', orgUserId);\n }\n const [item] = await this.listItems(actor, communityId, [row]);\n this.logger.debug('direct chat read', { orgId, communityId, chatId: row.chat.id });\n return item!;\n }\n\n async unreadSummary(communityId: string, actor: Actor): Promise<ChatUnreadSummary> {\n const orgId = actor.orgId;\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n return this.repo.unreadSummary(orgId, communityId, actor.id, new Date());\n }\n\n async typing(chatId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.lockedAt || chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n if (!this.pubsub) {\n return;\n }\n const now = Date.now();\n const key = `${orgId}:${chatId}:${actor.id}`;\n const last = this.typed.get(key);\n if (last !== undefined && now - last < TYPING_REPEAT_MS) {\n return;\n }\n this.typed.set(key, now);\n for (const [seen, at] of this.typed) {\n if (now - at >= TYPING_REPEAT_MS) {\n this.typed.delete(seen);\n }\n }\n await publishChange(\n this.pubsub,\n {\n event: TYPING,\n orgId,\n communityId: chat.communityId,\n chatId,\n orgUserId: actor.id,\n until: new Date(now + TYPING_FOR_MS).toISOString(),\n },\n this.logger,\n );\n }\n\n async watch(communityId: string, actor: Actor): Promise<AsyncIterable<ChatLiveEvent>> {\n if (!this.hub) {\n throw new UnavailableError('Live chat is not available');\n }\n await this.requireSettings(actor.orgId, communityId);\n await this.requireActor(actor, communityId);\n return this.hub.open({ communityId, actor });\n }\n\n async messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]> {\n const messages = await this.repo.messagesByIds(orgId, [...new Set(messageIds)]);\n const views = await this.withSenders(orgId, null, messages);\n return views.map(({ viewerReaction: _viewerReaction, ...view }) => view);\n }\n\n activeParticipants(orgId: string, chatId: string): Promise<string[]> {\n return this.repo.activeParticipants(orgId, chatId);\n }\n\n async messageReactions(orgId: string, messageId: string): Promise<ReactionCounts> {\n const counts = await this.repo.reactionsOf(orgId, [messageId], '');\n return counts[messageId]?.reactions ?? {};\n }\n\n async liveChat(orgId: string, chatId: string): Promise<LiveChat | null> {\n const chat = await this.repo.findChat(orgId, chatId);\n if (!chat) {\n return null;\n }\n const participantIds = await this.repo.activeParticipants(orgId, chatId);\n const profiles = await this.participantsOf(orgId, chat.communityId, participantIds);\n return {\n chat,\n participantIds,\n participants: participantIds.flatMap((id) => profiles.get(id) ?? []),\n };\n }\n\n memberStates(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<ChatMemberStates[]> {\n return this.repo.memberStates(orgId, communityId, orgUserIds, new Date());\n }\n\n async messages(\n chatId: string,\n query: { page: number; pageSize: number },\n actor: Actor,\n ): Promise<Page<MessageView>> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const page = await this.repo.listMessages(orgId, chatId, query);\n this.logger.debug('chat messages read', { orgId, chatId });\n return {\n ...page,\n rows: await this.withSenders(orgId, actor, page.rows),\n };\n }\n\n async attachmentFile(\n chatId: string,\n messageId: string,\n assetId: string,\n actor: Actor,\n ): Promise<string> {\n const orgId = actor.orgId;\n const chat = await this.repo.findChat(orgId, chatId);\n const message = await this.repo.findMessage(orgId, messageId);\n if (!chat || !message || message.chatId !== chatId) {\n throw new NotFoundError('Message attachment', assetId);\n }\n const attached = (await this.repo.attachmentsOf(orgId, [messageId]))[messageId] ?? [];\n if (!attached.includes(assetId)) {\n throw new NotFoundError('Message attachment', assetId);\n }\n if (!(await this.access.isCommunityMember(chat.communityId, actor))) {\n throw new NotFoundError('Message attachment', assetId);\n }\n const moderates = actor.permissions.has('communities:moderate', chat.communityId);\n const users = await this.repo.participants(orgId, chatId);\n const inChat = users.some((user) => user.orgUserId === actor.id && user.leftAt === null);\n const readable = inChat\n ? message.deletedAt === null || moderates\n : moderates &&\n (await this.repo.listOpenFlags(orgId, chat.communityId)).some(\n (flag) => flag.messageId === messageId,\n );\n if (!readable) {\n throw new NotFoundError('Message attachment', assetId);\n }\n const url = await this.images.signFile(orgId, assetId);\n if (!url) {\n throw new NotFoundError('Message attachment', assetId);\n }\n return url;\n }\n\n async messageQueue(communityId: string, actor: Actor): Promise<MessageQueue> {\n const orgId = actor.orgId;\n this.requireModerator(actor, communityId, 'message queue');\n await this.requireActor(actor, communityId);\n await this.requireSettings(orgId, communityId);\n const flags = await this.repo.listOpenFlags(orgId, communityId, MODERATION_QUEUE_LIMIT);\n const messages = await this.repo.messagesByIds(orgId, [\n ...new Set(flags.map((flag) => flag.messageId)),\n ]);\n const summaries = await this.repo.chatSummaries(orgId, [\n ...new Set(messages.map((message) => message.chatId)),\n ]);\n const byChat = new Map(summaries.map((summary) => [summary.chat.id, summary]));\n const views = await this.withSenders(orgId, actor, messages);\n const entries: MessageQueueEntry[] = views.flatMap((message) => {\n const summary = byChat.get(message.chatId);\n if (!summary) {\n return [];\n }\n return [\n {\n message,\n flags: flags.filter((flag) => flag.messageId === message.id),\n chat: summary.chat,\n participantIds: summary.participantIds,\n },\n ];\n });\n this.logger.debug('message queue read', { orgId, communityId, count: entries.length });\n return { entries };\n }\n\n async chatSummary(\n orgId: string,\n chatId: string,\n ): Promise<{ chat: Chat; participants: ChatUser[] } | null> {\n const chat = await this.repo.findChat(orgId, chatId);\n if (!chat) {\n return null;\n }\n const participants = await this.repo.participants(orgId, chatId);\n return { chat, participants };\n }\n\n async titlesFor(chatIds: string[], actor: Actor): Promise<Record<string, string>> {\n const orgId = actor.orgId;\n if (chatIds.length === 0) {\n return {};\n }\n const summaries = (await this.repo.chatSummaries(orgId, [...new Set(chatIds)])).filter(\n (summary) => summary.participantIds.includes(actor.id),\n );\n const others = [\n ...new Set(\n summaries.flatMap((summary) => summary.participantIds.filter((id) => id !== actor.id)),\n ),\n ];\n const records = others.length > 0 ? await this.profiles.authorsOf(orgId, others) : {};\n const nameOf = (id: string): string => {\n const record = records[id];\n return (record && authorName(record)) || 'A member';\n };\n return Object.fromEntries(\n summaries.flatMap((summary) => {\n const names = summary.participantIds.filter((id) => id !== actor.id).map(nameOf);\n if (names.length > 0) {\n return [[summary.chat.id, names.join(', ')]];\n }\n return summary.chat.kind === 'group' ? [[summary.chat.id, 'Group chat']] : [];\n }),\n );\n }\n\n unreadInChat(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread> {\n return this.repo.unreadFor(orgId, chatId, orgUserId);\n }\n\n findMessage(orgId: string, messageId: string): Promise<Message | null> {\n return this.repo.findMessage(orgId, messageId);\n }\n\n async latestMessageBody(orgId: string, chatId: string): Promise<string | null> {\n const page = await this.repo.listMessages(orgId, chatId, { page: 1, pageSize: 1 });\n return page.rows[0]?.body ?? null;\n }\n\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean> {\n return this.repo.isBlocked(orgId, communityId, a, b);\n }\n\n private async setLock(\n actor: Actor,\n chatId: string,\n locked: boolean,\n communityId: string | null,\n ): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n if (communityId !== null && chat.communityId !== communityId) {\n throw new NotFoundError('Chat', chatId);\n }\n this.requireModerator(actor, chat.communityId, locked ? 'lock chat' : 'unlock chat');\n await this.requireActor(actor, chat.communityId);\n if (Boolean(chat.lockedAt) === locked) {\n return chat;\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const next = await this.mustUpdate(chats, orgId, chatId, {\n lockedAt: locked ? now : null,\n lockedBy: locked ? actor.id : null,\n });\n if (locked) {\n await chats.resolveOpenFlagsForChat(orgId, chatId, {\n resolvedAt: now,\n resolvedBy: actor.id,\n resolution: 'locked',\n });\n }\n const users = await chats.participants(orgId, chatId);\n await outbox.append([this.updatedEvent(orgId, actor, next, users)]);\n return next;\n });\n this.logger.info(locked ? 'chat locked' : 'chat unlocked', {\n orgId,\n chatId,\n actorId: actor.id,\n });\n return saved;\n }\n\n private requireModerator(actor: Actor, communityId: string, action: string): void {\n if (!actor.permissions.has('communities:moderate', communityId)) {\n this.logger.warn('chat moderation refused — not a moderator', {\n orgId: actor.orgId,\n communityId,\n action,\n actorId: actor.id,\n });\n throw new ForbiddenError('You need communities:moderate');\n }\n }\n\n private async requireSettings(orgId: string, communityId: string): Promise<DmSettings> {\n const settings = await this.content.getDmSettings(communityId, this.authors.systemActor(orgId));\n if (!settings) {\n throw new NotFoundError('Community', communityId);\n }\n return settings;\n }\n\n private async requireActor(actor: Actor, communityId: string): Promise<void> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new ForbiddenError('You are not a member of this community');\n }\n }\n\n /** Whether the person is team in the community; throws when they are not a\n * member of it. */\n private async requirePerson(\n orgId: string,\n communityId: string,\n orgUserId: string,\n ): Promise<boolean> {\n const teams = await this.membersTeam(orgId, communityId, [orgUserId]);\n const team = teams.get(orgUserId);\n if (team === undefined) {\n throw new ForbiddenError('That person is not a member of this community');\n }\n return team;\n }\n\n /** Each member of the community among the given people, with whether they\n * are team there. People who are not members are left out. */\n private async membersTeam(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<Map<string, boolean>> {\n const members = await this.access.communityMembersAmong(orgId, communityId, orgUserIds);\n if (members.length === 0) {\n return new Map();\n }\n const permissions = await this.authors.permissionsOfMany(orgId, members);\n return new Map(\n members.map((orgUserId) => {\n const held = permissions.get(orgUserId);\n return [orgUserId, held !== undefined && isTeam(held, communityId)];\n }),\n );\n }\n\n private async teamsOf(orgId: string, communityId: string, orgUserIds: string[]): Promise<Teams> {\n return {\n asked: new Set(orgUserIds),\n team: await this.membersTeam(orgId, communityId, orgUserIds),\n };\n }\n\n private async assertMaySend(\n chats: ChatRepository,\n orgId: string,\n actor: Actor,\n chat: Chat,\n users: ChatUser[],\n settings: DmSettings,\n input: SendMessageInput,\n teams: Teams,\n ): Promise<void> {\n this.assertActive(users, actor.id);\n if (chat.lockedAt) {\n throw new ForbiddenError('This chat is locked');\n }\n if (chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n if (this.pendingCreator(chat, actor)) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n const others = users\n .filter(\n (other) =>\n other.orgUserId !== actor.id && (chat.kind === 'direct' || other.leftAt === null),\n )\n .map((other) => other.orgUserId);\n if (others.some((other) => !teams.asked.has(other))) {\n throw new ConflictError('This chat changed while you were sending; send again');\n }\n if (others.length > 0) {\n const recipients =\n chat.kind === 'group' ? others.filter((other) => teams.team.has(other)) : others;\n const actorTeam = isTeam(actor.permissions, chat.communityId);\n for (const other of recipients) {\n const team = teams.team.get(other);\n if (team === undefined || !this.mayMessage(settings, actorTeam, team)) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n if (recipients.length > 0) {\n const blocked = await chats.blockedWith(orgId, chat.communityId, actor.id, recipients);\n if (blocked.length > 0) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n }\n if (input.replyToId) {\n const reply = await chats.findMessage(orgId, input.replyToId);\n if (!reply || reply.chatId !== chat.id) {\n throw new NotFoundError('Message', input.replyToId);\n }\n }\n }\n\n private mentionable(body: string, users: ChatUser[]): string[] {\n return keepAllowed(\n mentionTokens(body),\n users.filter((user) => user.leftAt === null).map((user) => user.orgUserId),\n );\n }\n\n private async writeMessage(\n chats: ChatRepository,\n mentions: MentionsRepository,\n orgId: string,\n actor: Actor,\n chat: Chat,\n users: ChatUser[],\n input: SendMessageInput,\n ): Promise<{ chat: Chat; message: Message; opened: boolean; mentioned: string[] }> {\n const opened =\n chat.kind === 'direct' && chat.status === 'pending' && chat.createdBy !== actor.id;\n const current = opened\n ? await this.mustUpdate(chats, orgId, chat.id, { status: 'open' })\n : chat;\n const message = await chats.insertMessage(\n orgId,\n this.newMessage(orgId, current, actor, input, new Date()),\n );\n if (input.assetIds.length > 0) {\n await chats.attach(orgId, current.communityId, message.id, input.assetIds);\n }\n const mentioned = this.mentionable(input.body, users);\n await mentions.sync(orgId, 'message', message.id, mentioned, message.createdAt);\n return { chat: current, message, opened, mentioned };\n }\n\n private assertMayMessage(settings: DmSettings, actorTeam: boolean, targetTeam: boolean): void {\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n if (!this.mayMessage(settings, actorTeam, targetTeam)) {\n throw new ForbiddenError('You cannot message this person');\n }\n }\n\n private assertGroup(settings: DmSettings): void {\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n if (!settings.groupChats) {\n throw new ForbiddenError('Group chats are off');\n }\n }\n\n private mayMessage(settings: DmSettings, actorTeam: boolean, targetTeam: boolean): boolean {\n if (actorTeam) {\n return settings.teamToAnyone;\n }\n if (targetTeam) {\n return settings.memberToTeam;\n }\n return settings.memberToMember;\n }\n\n private async requireActive(\n repo: ChatRepository,\n orgId: string,\n chatId: string,\n orgUserId: string,\n ): Promise<void> {\n const users = await repo.participants(orgId, chatId);\n this.assertActive(users, orgUserId);\n }\n\n private assertActive(users: ChatUser[], orgUserId: string): void {\n if (!users.some((user) => user.orgUserId === orgUserId && user.leftAt === null)) {\n throw new ForbiddenError('You are not in this chat');\n }\n }\n\n private async requireOwnedImages(\n orgId: string,\n orgUserId: string,\n assetIds: string[],\n ): Promise<void> {\n for (const assetId of assetIds) {\n if (!(await this.images.ownsImage(orgId, assetId, orgUserId))) {\n this.logger.debug('attachment refused', { orgId, orgUserId, assetId });\n throw new NotFoundError('Asset', assetId);\n }\n }\n }\n\n private async requireWritableChat(actor: Actor, chatId: string): Promise<void> {\n const chat = await this.mustChat(this.repo, actor.orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, actor.orgId, chat.id, actor.id);\n if (chat.lockedAt || chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n\n private pendingCreator(chat: Chat, actor: Actor): boolean {\n return chat.kind === 'direct' && chat.status === 'pending' && chat.createdBy === actor.id;\n }\n\n private async mustChat(repo: ChatRepository, orgId: string, chatId: string): Promise<Chat> {\n const chat = await repo.findChat(orgId, chatId);\n if (!chat) {\n throw new NotFoundError('Chat', chatId);\n }\n return chat;\n }\n\n private async mustMessage(\n repo: ChatRepository,\n orgId: string,\n messageId: string,\n ): Promise<Message> {\n const message = await repo.findMessage(orgId, messageId);\n if (!message) {\n throw new NotFoundError('Message', messageId);\n }\n return message;\n }\n\n private async mustUpdate(\n chats: ChatRepository,\n orgId: string,\n chatId: string,\n patch: Partial<Pick<Chat, 'status' | 'lockedAt' | 'lockedBy'>>,\n ): Promise<Chat> {\n const next = await chats.updateChat(orgId, chatId, patch);\n if (!next) {\n throw new NotFoundError('Chat', chatId);\n }\n return next;\n }\n\n private memberRow(\n orgId: string,\n communityId: string,\n chatId: string,\n orgUserId: string,\n now: Date,\n ): ChatUser {\n return {\n orgId,\n communityId,\n chatId,\n orgUserId,\n joinedAt: now,\n leftAt: null,\n lastReadMessageId: null,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n private newMessage(\n orgId: string,\n chat: Chat,\n actor: Actor,\n input: SendMessageInput,\n now: Date,\n ): Message {\n return {\n orgId,\n id: genId('message'),\n communityId: chat.communityId,\n chatId: chat.id,\n orgUserId: actor.id,\n body: input.body,\n replyToId: input.replyToId,\n clientId: input.clientId ?? null,\n editedAt: null,\n deletedAt: null,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n private activeIds(users: ChatUser[]): string[] {\n return users\n .filter((user) => user.leftAt === null)\n .map((user) => user.orgUserId)\n .sort();\n }\n\n private chatData(chat: Chat, users: ChatUser[]) {\n return { ...chat, participantIds: this.activeIds(users) };\n }\n\n private meta(actor: Actor) {\n return { actorId: actor.id };\n }\n\n private createdEvent(orgId: string, actor: Actor, chat: Chat, users: ChatUser[]) {\n return discussionEvents.chatCreated.make({\n orgId,\n data: this.chatData(chat, users),\n metadata: this.meta(actor),\n });\n }\n\n private updatedEvent(orgId: string, actor: Actor, chat: Chat, users: ChatUser[]) {\n return discussionEvents.chatUpdated.make({\n orgId,\n data: this.chatData(chat, users),\n metadata: this.meta(actor),\n });\n }\n\n private addedEvent(orgId: string, actor: Actor, chat: Chat, orgUserId: string) {\n return discussionEvents.chatParticipantAdded.make({\n orgId,\n data: {\n chatId: chat.id,\n communityId: chat.communityId,\n orgUserId,\n addedBy: actor.id,\n },\n metadata: this.meta(actor),\n });\n }\n\n private sentEvent(\n orgId: string,\n actor: Actor,\n chat: Chat,\n message: Message,\n mentionedOrgUserIds: string[],\n ) {\n return discussionEvents.messageSent.make({\n orgId,\n data: { ...eventMessage(message), mentionedOrgUserIds },\n metadata: { ...this.meta(actor), chatStatus: chat.status },\n });\n }\n\n private async viewOf(actor: Actor, message: Message): Promise<MessageView> {\n const [view] = await this.withSenders(actor.orgId, actor, [message]);\n return view!;\n }\n\n private async withSenders(\n orgId: string,\n viewer: Actor | null,\n messages: Message[],\n ): Promise<MessageView[]> {\n if (messages.length === 0) {\n return [];\n }\n const messageIds = messages.map((message) => message.id);\n const senderIds = [...new Set(messages.map((message) => message.orgUserId))];\n const [records, permissions, attached, reactions, mentioned] = await Promise.all([\n this.profiles.authorsOf(orgId, senderIds),\n this.authors.permissionsOfMany(orgId, senderIds),\n this.repo.attachmentsOf(orgId, messageIds),\n this.repo.reactionsOf(orgId, messageIds, viewer?.id ?? ''),\n mentionViews(this.mentions, this.profiles, orgId, 'message', messageIds),\n ]);\n const hidden = (message: Message) =>\n message.deletedAt !== null &&\n !viewer?.permissions.has('communities:moderate', message.communityId);\n const shown = (message: Message) => (hidden(message) ? [] : (attached[message.id] ?? []));\n return messages.map((message) => {\n const record = records[message.orgUserId];\n if (!record) {\n throw new NotFoundError('Member', message.orgUserId);\n }\n const counts = reactions[message.id];\n return {\n ...(hidden(message) ? { ...message, body: '' } : message),\n sender: authorView(\n orgId,\n record,\n this.fileUrls,\n teamBadge(permissions.get(message.orgUserId), message.communityId),\n ),\n attachments: shown(message).map((assetId) => ({\n assetId,\n url: this.fileUrls.messageAttachment(message.chatId, message.id, assetId),\n })),\n reactions: counts?.reactions ?? {},\n viewerReaction: counts?.viewerReaction ?? null,\n mentions: hidden(message) ? [] : (mentioned[message.id] ?? []),\n };\n });\n }\n\n private async listItems(\n actor: Actor,\n communityId: string,\n rows: ChatInboxRow[],\n ): Promise<ChatListItem[]> {\n const orgId = actor.orgId;\n const seesDeleted = actor.permissions.has('communities:moderate', communityId);\n const now = new Date();\n const [subscribed, profiles] = await Promise.all([\n this.subscriptions.forViewer(\n orgId,\n actor.id,\n 'chat',\n rows.map((item) => item.chat.id),\n ),\n this.participantsOf(\n orgId,\n communityId,\n rows.flatMap((item) => item.participantIds),\n ),\n ]);\n const subscriptionOf = new Map(subscribed.map((row) => [row.itemId, row]));\n return rows.map((item) => ({\n ...item,\n participants: item.participantIds.flatMap((id) => profiles.get(id) ?? []),\n subscription: activeSubscription(subscriptionOf.get(item.chat.id), now),\n lastMessage:\n item.lastMessage && item.lastMessage.deletedAt !== null && !seesDeleted\n ? { ...item.lastMessage, body: '' }\n : item.lastMessage,\n }));\n }\n\n private async participantsOf(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<Map<string, CommentAuthor>> {\n const ids = [...new Set(orgUserIds)];\n if (ids.length === 0) {\n return new Map();\n }\n const [records, permissions] = await Promise.all([\n this.profiles.authorsOf(orgId, ids),\n this.authors.permissionsOfMany(orgId, ids),\n ]);\n return new Map(\n ids.flatMap((id) => {\n const record = records[id];\n return record\n ? [\n [\n id,\n authorView(\n orgId,\n record,\n this.fileUrls,\n teamBadge(permissions.get(id), communityId),\n ),\n ] as const,\n ]\n : [];\n }),\n );\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chats.js","names":[],"sources":["../../src/discussion/chats.ts"],"sourcesContent":["import { genId } from '../shared/id.js';\nimport {\n ConflictError,\n ForbiddenError,\n InvalidInputError,\n NotFoundError,\n UnavailableError,\n} from '../shared/errors.js';\nimport type { Logger, PubSub } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport type { DmSettings } from '../content/index.js';\nimport type { AssetFiles, FileUrls, MemberImageReader } from '../assets/index.js';\nimport type {\n Chat,\n ChatLiveEvent,\n ChatUnreadSummary,\n ChatUser,\n LiveMessage,\n Message,\n MessageFlag,\n MessageFlagResolution,\n MessageView,\n Page,\n ReactionCounts,\n ReactionEmoji,\n} from '../types/index.js';\nimport type { Actor } from '../shared/actor.js';\nimport type { NewDiscussionEvent } from './events.js';\nimport { discussionEvents } from './events.js';\nimport { authorName, authorView } from './authors.js';\nimport { activeSubscription } from './subscriptions.js';\nimport { keepAllowed, mentionTokens, mentionViews } from './mentions.js';\nimport { isTeam, teamBadge } from './rules.js';\nimport { ChatHub, TYPING, publishChange, type ChatHubOptions } from './live.js';\nimport type { CommentAuthor } from './model.js';\nimport type {\n AuthorPermissionReader,\n AuthorProfileReader,\n ChatInboxRow,\n ChatListItem,\n ChatListQuery,\n ChatLiveReads,\n ChatMemberStates,\n ChatRepository,\n ChatService,\n ChatUnread,\n ChatUnitOfWork,\n LiveChat,\n MentionsRepository,\n MessageQueue,\n MessageQueueEntry,\n MessageReactions,\n SendMessageInput,\n StartDirectInput,\n StartGroupInput,\n CommunityAccess,\n SpaceReader,\n SubscriptionsRepository,\n} from './ports.js';\nimport { MODERATION_QUEUE_LIMIT } from './ports.js';\n\nconst TYPING_FOR_MS = 5000;\nconst TYPING_REPEAT_MS = 2000;\n\nexport type ChatServiceParams = {\n repo: ChatRepository;\n access: CommunityAccess;\n content: Pick<SpaceReader, 'getDmSettings'>;\n authors: AuthorPermissionReader;\n profiles: AuthorProfileReader;\n subscriptions: SubscriptionsRepository;\n mentions: MentionsRepository;\n images: MemberImageReader & Pick<AssetFiles, 'fileLinks'>;\n fileUrls: FileUrls;\n uow: ChatUnitOfWork;\n pubsub?: PubSub;\n live?: ChatHubOptions;\n logger?: Logger;\n};\n\nfunction eventMessage(message: Message): Omit<Message, 'clientId'> {\n const { clientId: _clientId, ...rest } = message;\n return rest;\n}\n\ntype Teams = { asked: ReadonlySet<string>; team: Map<string, boolean> };\n\nexport class ChatServiceImpl implements ChatService, ChatLiveReads {\n /** This process's live chat hub; null without a PubSub. */\n readonly hub: ChatHub | null;\n private readonly repo: ChatRepository;\n private readonly access: CommunityAccess;\n private readonly content: Pick<SpaceReader, 'getDmSettings'>;\n private readonly authors: AuthorPermissionReader;\n private readonly profiles: AuthorProfileReader;\n private readonly subscriptions: SubscriptionsRepository;\n private readonly mentions: MentionsRepository;\n private readonly images: MemberImageReader & Pick<AssetFiles, 'fileLinks'>;\n private readonly fileUrls: FileUrls;\n private readonly uow: ChatUnitOfWork;\n private readonly pubsub: PubSub | null;\n private readonly logger: Logger;\n private readonly typed = new Map<string, number>();\n\n constructor(params: ChatServiceParams) {\n this.repo = params.repo;\n this.access = params.access;\n this.content = params.content;\n this.authors = params.authors;\n this.profiles = params.profiles;\n this.subscriptions = params.subscriptions;\n this.mentions = params.mentions;\n this.images = params.images;\n this.fileUrls = params.fileUrls;\n this.uow = params.uow;\n this.pubsub = params.pubsub ?? null;\n this.logger = params.logger ?? noopLogger;\n this.hub = this.pubsub\n ? new ChatHub({ pubsub: this.pubsub, reads: this, logger: this.logger, ...params.live })\n : null;\n }\n\n async startDirect(input: StartDirectInput, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n if (actor.id === input.orgUserId) {\n throw new InvalidInputError('You cannot message yourself');\n }\n if (input.body === null && input.assetIds.length > 0) {\n throw new InvalidInputError('An attachment needs a message');\n }\n const settings = await this.requireSettings(orgId, input.communityId);\n await this.requireActor(actor, input.communityId);\n const targetTeam = await this.requirePerson(orgId, input.communityId, input.orgUserId);\n const actorTeam = isTeam(actor.permissions, input.communityId);\n this.assertMayMessage(settings, actorTeam, targetTeam);\n const first: SendMessageInput | null =\n input.body === null ? null : { body: input.body, replyToId: null, assetIds: input.assetIds };\n await this.requireOwnedImages(orgId, actor.id, input.assetIds);\n const teams: Teams = {\n asked: new Set([input.orgUserId]),\n team: new Map([[input.orgUserId, targetTeam]]),\n };\n const saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n await chats.lockDirectPair(orgId, input.communityId, actor.id, input.orgUserId);\n const existing = await chats.findDirect(orgId, input.communityId, actor.id, input.orgUserId);\n if (existing) {\n const users = await chats.participants(orgId, existing.id);\n const rejoin = users.some((user) => user.orgUserId === actor.id && user.leftAt !== null);\n const present = users.map((user) =>\n user.orgUserId === actor.id ? { ...user, leftAt: null } : user,\n );\n if (first) {\n await this.assertMaySend(chats, orgId, actor, existing, present, settings, first, teams);\n }\n if (rejoin) {\n await chats.join(\n orgId,\n this.memberRow(orgId, existing.communityId, existing.id, actor.id, new Date()),\n );\n }\n const written = first\n ? await this.writeMessage(chats, mentions, orgId, actor, existing, present, first)\n : null;\n const chat = written?.chat ?? existing;\n const events: NewDiscussionEvent[] = [];\n if (rejoin || written?.opened) {\n events.push(this.updatedEvent(orgId, actor, chat, present));\n }\n if (written) {\n events.push(\n this.sentEvent(orgId, actor, written.chat, written.message, written.mentioned),\n );\n }\n if (events.length > 0) {\n await outbox.append(events);\n }\n return { chat, created: false };\n }\n if (await chats.isBlocked(orgId, input.communityId, actor.id, input.orgUserId)) {\n throw new ForbiddenError('You cannot message this person');\n }\n const request = settings.firstMessageIsRequest && !actorTeam && !targetTeam;\n if (request && !first) {\n throw new InvalidInputError('A message request needs a message');\n }\n const now = new Date();\n const chat = await chats.insertChat(orgId, {\n orgId,\n id: genId('chat'),\n communityId: input.communityId,\n kind: 'direct',\n status: request ? 'pending' : 'open',\n createdBy: actor.id,\n lockedAt: null,\n lockedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n const users = [actor.id, input.orgUserId].map((orgUserId) =>\n this.memberRow(orgId, chat.communityId, chat.id, orgUserId, now),\n );\n for (const user of users) {\n await chats.join(orgId, user);\n }\n const events: NewDiscussionEvent[] = [this.createdEvent(orgId, actor, chat, users)];\n if (first) {\n const written = await this.writeMessage(chats, mentions, orgId, actor, chat, users, first);\n events.push(this.sentEvent(orgId, actor, written.chat, written.message, written.mentioned));\n }\n await outbox.append(events);\n return { chat, created: true };\n });\n if (saved.created) {\n this.logger.info('direct chat started', {\n orgId,\n communityId: input.communityId,\n chatId: saved.chat.id,\n status: saved.chat.status,\n actorId: actor.id,\n });\n } else {\n this.logger.debug('direct chat already exists', {\n orgId,\n communityId: input.communityId,\n chatId: saved.chat.id,\n });\n }\n return saved.chat;\n }\n\n async startGroup(input: StartGroupInput, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const settings = await this.requireSettings(orgId, input.communityId);\n this.assertGroup(settings);\n await this.requireActor(actor, input.communityId);\n const others = [...new Set(input.orgUserIds.filter((id) => id !== actor.id))].sort();\n if (others.length < 2) {\n throw new InvalidInputError('A group chat needs three people');\n }\n if (others.length + 1 > settings.groupSize) {\n throw new ConflictError('This group is full');\n }\n const actorTeam = isTeam(actor.permissions, input.communityId);\n for (const orgUserId of others) {\n const targetTeam = await this.requirePerson(orgId, input.communityId, orgUserId);\n this.assertMayMessage(settings, actorTeam, targetTeam);\n }\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n if (await chats.anyBlockedAmong(orgId, input.communityId, [actor.id, ...others])) {\n throw new ForbiddenError('You cannot message this person');\n }\n const now = new Date();\n const chat = await chats.insertChat(orgId, {\n orgId,\n id: genId('chat'),\n communityId: input.communityId,\n kind: 'group',\n status: 'open',\n createdBy: actor.id,\n lockedAt: null,\n lockedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n const ids = [actor.id, ...others];\n const users = ids.map((orgUserId) =>\n this.memberRow(orgId, chat.communityId, chat.id, orgUserId, now),\n );\n for (const user of users) {\n await chats.join(orgId, user);\n }\n const events: NewDiscussionEvent[] = [\n this.createdEvent(orgId, actor, chat, users),\n ...others.map((orgUserId) => this.addedEvent(orgId, actor, chat, orgUserId)),\n ];\n await outbox.append(events);\n return chat;\n });\n this.logger.info('group chat started', {\n orgId,\n communityId: input.communityId,\n chatId: saved.id,\n actorId: actor.id,\n });\n return saved;\n }\n\n async addParticipant(chatId: string, orgUserId: string, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.kind !== 'group') {\n throw new ConflictError('This chat is not a group');\n }\n const settings = await this.requireSettings(orgId, chat.communityId);\n this.assertGroup(settings);\n const targetTeam = await this.requirePerson(orgId, chat.communityId, orgUserId);\n this.assertMayMessage(settings, isTeam(actor.permissions, chat.communityId), targetTeam);\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n const users = await chats.participants(orgId, chatId);\n this.assertActive(users, actor.id);\n const existing = users.find((user) => user.orgUserId === orgUserId);\n if (existing && existing.leftAt === null) {\n throw new ConflictError('That person is already in this chat');\n }\n const active = users.filter((user) => user.leftAt === null);\n if (active.length + 1 > settings.groupSize) {\n throw new ConflictError('This group is full');\n }\n const blocked = await chats.blockedWith(\n orgId,\n current.communityId,\n orgUserId,\n active.map((user) => user.orgUserId),\n );\n if (blocked.length > 0) {\n throw new ForbiddenError('You cannot message this person');\n }\n const now = new Date();\n await chats.join(\n orgId,\n this.memberRow(orgId, current.communityId, current.id, orgUserId, now),\n );\n await outbox.append([this.addedEvent(orgId, actor, current, orgUserId)]);\n return current;\n });\n this.logger.info('chat participant added', { orgId, chatId, orgUserId, actorId: actor.id });\n return saved;\n }\n\n async leave(chatId: string, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n const users = await chats.participants(orgId, chatId);\n this.assertActive(users, actor.id);\n const now = new Date();\n await chats.markLeft(orgId, chatId, actor.id, now);\n const remaining = users.map((user) =>\n user.orgUserId === actor.id ? { ...user, leftAt: now } : user,\n );\n await outbox.append([this.updatedEvent(orgId, actor, current, remaining)]);\n return current;\n });\n this.logger.info('chat left', { orgId, chatId, actorId: actor.id });\n return saved;\n }\n\n async accept(chatId: string, actor: Actor): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.createdBy === actor.id) {\n throw new ForbiddenError('You cannot accept this request');\n }\n if (chat.kind !== 'direct' || chat.status !== 'pending') {\n throw new ConflictError('This chat is not a request');\n }\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const current = await this.mustChat(chats, orgId, chatId);\n if (current.kind !== 'direct' || current.status !== 'pending') {\n throw new ConflictError('This chat is not a request');\n }\n const next = await this.mustUpdate(chats, orgId, chatId, { status: 'open' });\n const users = await chats.participants(orgId, chatId);\n await outbox.append([this.updatedEvent(orgId, actor, next, users)]);\n return next;\n });\n this.logger.info('chat accepted', { orgId, chatId, actorId: actor.id });\n return saved;\n }\n\n async send(chatId: string, input: SendMessageInput, actor: Actor): Promise<MessageView> {\n const orgId = actor.orgId;\n const preview = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, preview.communityId);\n const settings = await this.requireSettings(orgId, preview.communityId);\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n const clientId = input.clientId;\n const before = await this.repo.participants(orgId, chatId);\n const teams = await this.teamsOf(\n orgId,\n preview.communityId,\n before.map((user) => user.orgUserId).filter((orgUserId) => orgUserId !== actor.id),\n );\n await this.requireOwnedImages(orgId, actor.id, input.assetIds);\n let saved: { message: Message; repeat: boolean };\n try {\n saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n const chat = await this.mustChat(chats, orgId, chatId);\n if (clientId) {\n const stored = await chats.findByClientId(orgId, chatId, actor.id, clientId);\n if (stored) {\n return { message: stored, repeat: true };\n }\n }\n const users = await chats.participants(orgId, chatId);\n await this.assertMaySend(chats, orgId, actor, chat, users, settings, input, teams);\n const written = await this.writeMessage(chats, mentions, orgId, actor, chat, users, input);\n const events: NewDiscussionEvent[] = [];\n if (written.opened) {\n events.push(this.updatedEvent(orgId, actor, written.chat, users));\n }\n events.push(this.sentEvent(orgId, actor, written.chat, written.message, written.mentioned));\n await outbox.append(events);\n return { message: written.message, repeat: false };\n });\n } catch (error) {\n const stored =\n clientId && error instanceof ConflictError\n ? await this.repo.findByClientId(orgId, chatId, actor.id, clientId)\n : null;\n if (!stored) {\n throw error;\n }\n saved = { message: stored, repeat: true };\n }\n if (saved.repeat) {\n this.logger.debug('message send repeated', {\n orgId,\n chatId,\n messageId: saved.message.id,\n });\n } else {\n this.logger.info('message sent', {\n orgId,\n chatId,\n messageId: saved.message.id,\n actorId: actor.id,\n });\n }\n return this.viewOf(actor, saved.message);\n }\n\n async editMessage(messageId: string, body: string, actor: Actor): Promise<MessageView> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireWritableChat(actor, message.chatId);\n if (message.orgUserId !== actor.id) {\n throw new ForbiddenError('You can change only your own message');\n }\n if (message.deletedAt) {\n throw new ConflictError('This message was deleted');\n }\n if (message.body === body) {\n return this.viewOf(actor, message);\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n const next = await chats.updateMessage(orgId, messageId, { body, editedAt: now });\n if (!next) {\n throw new NotFoundError('Message', messageId);\n }\n const chat = await this.mustChat(chats, orgId, next.chatId);\n const users = await chats.participants(orgId, next.chatId);\n const candidates = this.mentionable(body, users);\n const added = await mentions.sync(orgId, 'message', messageId, candidates, now);\n await outbox.append([\n discussionEvents.messageUpdated.make({\n orgId,\n data: { ...eventMessage(next), mentionedOrgUserIds: keepAllowed(candidates, added) },\n metadata: { ...this.meta(actor), chatStatus: chat.status },\n }),\n ]);\n return next;\n });\n this.logger.info('message edited', { orgId, messageId, actorId: actor.id });\n return this.viewOf(actor, saved);\n }\n\n async deleteMessage(messageId: string, actor: Actor): Promise<MessageView> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(actor, message.communityId);\n if (!actor.permissions.has('communities:moderate', message.communityId)) {\n if (message.orgUserId !== actor.id) {\n this.logger.warn('message delete refused — not the sender or a moderator', {\n orgId,\n messageId,\n actorId: actor.id,\n });\n throw new ForbiddenError('You cannot delete this message');\n }\n await this.requireWritableChat(actor, message.chatId);\n }\n if (message.deletedAt) {\n throw new ConflictError('This message was deleted');\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n const next = await chats.updateMessage(orgId, messageId, { deletedAt: now });\n if (!next) {\n throw new NotFoundError('Message', messageId);\n }\n await mentions.removeItem(orgId, 'message', messageId);\n await chats.resolveFlags(orgId, messageId, {\n resolvedAt: now,\n resolvedBy: actor.id,\n resolution: 'deleted',\n });\n await outbox.append([\n discussionEvents.messageDeleted.make({\n orgId,\n data: eventMessage(next),\n metadata: this.meta(actor),\n }),\n ]);\n return next;\n });\n this.logger.info('message deleted', { orgId, messageId, actorId: actor.id });\n return this.viewOf(actor, saved);\n }\n\n async setReaction(\n messageId: string,\n emoji: ReactionEmoji | null,\n actor: Actor,\n ): Promise<MessageReactions> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(actor, message.communityId);\n await this.requireActive(this.repo, orgId, message.chatId, actor.id);\n await this.uow.run(async ({ chats, outbox }) => {\n const change = await chats.setReaction(\n orgId,\n message.communityId,\n messageId,\n actor.id,\n emoji,\n );\n if (!change.changed) {\n return;\n }\n const where = {\n messageId,\n chatId: message.chatId,\n communityId: message.communityId,\n orgUserId: actor.id,\n };\n const events: NewDiscussionEvent[] = [];\n if (change.inserted && emoji !== null) {\n events.push(\n discussionEvents.messageReactionAdded.make({\n orgId,\n data: { ...where, reaction: emoji },\n metadata: this.meta(actor),\n }),\n );\n }\n events.push(\n discussionEvents.messageReactionsChanged.make({\n orgId,\n data: { ...where, reaction: emoji },\n metadata: this.meta(actor),\n }),\n );\n await outbox.append(events);\n });\n this.logger.debug('message reaction set', { orgId, messageId });\n const counts = await this.repo.reactionsOf(orgId, [messageId], actor.id);\n return counts[messageId] ?? { reactions: {} };\n }\n\n async markRead(chatId: string, messageId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const message = await this.mustMessage(this.repo, orgId, messageId);\n if (message.chatId !== chatId) {\n throw new NotFoundError('Message', messageId);\n }\n const moved = await this.uow.run(async ({ chats, outbox }) => {\n if (!(await chats.setRead(orgId, chatId, actor.id, messageId))) {\n return false;\n }\n await outbox.append([\n discussionEvents.chatRead.make({\n orgId,\n data: {\n chatId,\n communityId: chat.communityId,\n orgUserId: actor.id,\n lastReadMessageId: messageId,\n },\n metadata: this.meta(actor),\n }),\n ]);\n return true;\n });\n this.logger.debug(moved ? 'chat marked read' : 'chat read mark kept', {\n orgId,\n chatId,\n messageId,\n });\n }\n\n async block(communityId: string, orgUserId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n if (actor.id === orgUserId) {\n throw new InvalidInputError('You cannot block yourself');\n }\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n await this.requirePerson(orgId, communityId, orgUserId);\n await this.uow.run(async ({ chats }) => {\n await chats.lockDirectPair(orgId, communityId, actor.id, orgUserId);\n await chats.block(orgId, communityId, actor.id, orgUserId);\n });\n this.logger.info('user blocked', { orgId, communityId, actorId: actor.id });\n }\n\n async unblock(communityId: string, orgUserId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n if (actor.id === orgUserId) {\n throw new InvalidInputError('You cannot block yourself');\n }\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n await this.uow.run(async ({ chats }) => {\n await chats.lockDirectPair(orgId, communityId, actor.id, orgUserId);\n await chats.unblock(orgId, communityId, actor.id, orgUserId);\n });\n this.logger.info('user unblocked', { orgId, communityId, actorId: actor.id });\n }\n\n async flagMessage(messageId: string, reason: string, actor: Actor): Promise<MessageFlag> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(actor, message.communityId);\n await this.requireActive(this.repo, orgId, message.chatId, actor.id);\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const flag = await chats.insertFlag(orgId, {\n orgId,\n id: genId('messageFlag'),\n communityId: message.communityId,\n messageId,\n orgUserId: actor.id,\n reason,\n resolvedAt: null,\n resolution: null,\n resolvedBy: null,\n createdAt: now,\n updatedAt: now,\n });\n if (!flag) {\n const existing = await chats.findFlag(orgId, messageId, actor.id);\n if (!existing) {\n throw new NotFoundError('Message', messageId);\n }\n return existing;\n }\n await outbox.append([\n discussionEvents.messageReported.make({\n orgId,\n data: flag,\n metadata: this.meta(actor),\n }),\n ]);\n return flag;\n });\n this.logger.info('message flagged', { orgId, messageId, actorId: actor.id });\n return saved;\n }\n\n async resolveMessageFlags(\n messageId: string,\n resolution: Extract<MessageFlagResolution, 'dismissed'>,\n communityId: string | null,\n actor: Actor,\n ): Promise<void> {\n const orgId = actor.orgId;\n const message = await this.mustMessage(this.repo, orgId, messageId);\n if (communityId !== null && message.communityId !== communityId) {\n throw new NotFoundError('Message', messageId);\n }\n this.requireModerator(actor, message.communityId, 'resolve message flags');\n await this.requireActor(actor, message.communityId);\n await this.repo.resolveFlags(orgId, messageId, {\n resolvedAt: new Date(),\n resolvedBy: actor.id,\n resolution,\n });\n this.logger.info('message flags resolved', {\n orgId,\n communityId: message.communityId,\n messageId,\n resolution,\n actorId: actor.id,\n });\n }\n\n async lock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat> {\n return this.setLock(actor, chatId, true, communityId);\n }\n\n async unlock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat> {\n return this.setLock(actor, chatId, false, communityId);\n }\n\n async listChats(\n communityId: string,\n query: ChatListQuery,\n actor: Actor,\n ): Promise<Page<ChatListItem>> {\n const orgId = actor.orgId;\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n const page = await this.repo.listForUser(orgId, communityId, actor.id, query);\n const items = await this.listItems(actor, communityId, page.rows);\n this.logger.debug('chats listed', { orgId, communityId, count: items.length });\n return { ...page, rows: items };\n }\n\n async chat(chatId: string, actor: Actor): Promise<ChatListItem> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const row = await this.repo.inboxRow(orgId, chatId, actor.id);\n if (!row) {\n throw new NotFoundError('Chat', chatId);\n }\n const [item] = await this.listItems(actor, chat.communityId, [row]);\n this.logger.debug('chat read', { orgId, chatId });\n return item!;\n }\n\n async directChat(communityId: string, orgUserId: string, actor: Actor): Promise<ChatListItem> {\n const orgId = actor.orgId;\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n const chat =\n orgUserId === actor.id\n ? null\n : await this.repo.findDirect(orgId, communityId, actor.id, orgUserId);\n const row = chat ? await this.repo.inboxRow(orgId, chat.id, actor.id) : null;\n if (!row) {\n throw new NotFoundError('Chat', orgUserId);\n }\n const [item] = await this.listItems(actor, communityId, [row]);\n this.logger.debug('direct chat read', { orgId, communityId, chatId: row.chat.id });\n return item!;\n }\n\n async unreadSummary(communityId: string, actor: Actor): Promise<ChatUnreadSummary> {\n const orgId = actor.orgId;\n await this.requireSettings(orgId, communityId);\n await this.requireActor(actor, communityId);\n return this.repo.unreadSummary(orgId, communityId, actor.id, new Date());\n }\n\n async typing(chatId: string, actor: Actor): Promise<void> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n if (chat.lockedAt || chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n if (!this.pubsub) {\n return;\n }\n const now = Date.now();\n const key = `${orgId}:${chatId}:${actor.id}`;\n const last = this.typed.get(key);\n if (last !== undefined && now - last < TYPING_REPEAT_MS) {\n return;\n }\n this.typed.set(key, now);\n for (const [seen, at] of this.typed) {\n if (now - at >= TYPING_REPEAT_MS) {\n this.typed.delete(seen);\n }\n }\n await publishChange(\n this.pubsub,\n {\n event: TYPING,\n orgId,\n communityId: chat.communityId,\n chatId,\n orgUserId: actor.id,\n until: new Date(now + TYPING_FOR_MS).toISOString(),\n },\n this.logger,\n );\n }\n\n async watch(communityId: string, actor: Actor): Promise<AsyncIterable<ChatLiveEvent>> {\n if (!this.hub) {\n throw new UnavailableError('Live chat is not available');\n }\n await this.requireSettings(actor.orgId, communityId);\n await this.requireActor(actor, communityId);\n return this.hub.open({ communityId, actor });\n }\n\n async messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]> {\n const messages = await this.repo.messagesByIds(orgId, [...new Set(messageIds)]);\n const views = await this.withSenders(orgId, null, messages);\n return views.map(({ viewerReaction: _viewerReaction, ...view }) => view);\n }\n\n activeParticipants(orgId: string, chatId: string): Promise<string[]> {\n return this.repo.activeParticipants(orgId, chatId);\n }\n\n async messageReactions(orgId: string, messageId: string): Promise<ReactionCounts> {\n const counts = await this.repo.reactionsOf(orgId, [messageId], '');\n return counts[messageId]?.reactions ?? {};\n }\n\n async liveChat(orgId: string, chatId: string): Promise<LiveChat | null> {\n const chat = await this.repo.findChat(orgId, chatId);\n if (!chat) {\n return null;\n }\n const participantIds = await this.repo.activeParticipants(orgId, chatId);\n const profiles = await this.participantsOf(orgId, chat.communityId, participantIds);\n return {\n chat,\n participantIds,\n participants: participantIds.flatMap((id) => profiles.get(id) ?? []),\n };\n }\n\n memberStates(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<ChatMemberStates[]> {\n return this.repo.memberStates(orgId, communityId, orgUserIds, new Date());\n }\n\n async messages(\n chatId: string,\n query: { page: number; pageSize: number },\n actor: Actor,\n ): Promise<Page<MessageView>> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, orgId, chat.id, actor.id);\n const page = await this.repo.listMessages(orgId, chatId, query);\n this.logger.debug('chat messages read', { orgId, chatId });\n return {\n ...page,\n rows: await this.withSenders(orgId, actor, page.rows),\n };\n }\n\n async messageQueue(communityId: string, actor: Actor): Promise<MessageQueue> {\n const orgId = actor.orgId;\n this.requireModerator(actor, communityId, 'message queue');\n await this.requireActor(actor, communityId);\n await this.requireSettings(orgId, communityId);\n const flags = await this.repo.listOpenFlags(orgId, communityId, MODERATION_QUEUE_LIMIT);\n const messages = await this.repo.messagesByIds(orgId, [\n ...new Set(flags.map((flag) => flag.messageId)),\n ]);\n const summaries = await this.repo.chatSummaries(orgId, [\n ...new Set(messages.map((message) => message.chatId)),\n ]);\n const byChat = new Map(summaries.map((summary) => [summary.chat.id, summary]));\n const views = await this.withSenders(orgId, actor, messages);\n const entries: MessageQueueEntry[] = views.flatMap((message) => {\n const summary = byChat.get(message.chatId);\n if (!summary) {\n return [];\n }\n return [\n {\n message,\n flags: flags.filter((flag) => flag.messageId === message.id),\n chat: summary.chat,\n participantIds: summary.participantIds,\n },\n ];\n });\n this.logger.debug('message queue read', { orgId, communityId, count: entries.length });\n return { entries };\n }\n\n async chatSummary(\n orgId: string,\n chatId: string,\n ): Promise<{ chat: Chat; participants: ChatUser[] } | null> {\n const chat = await this.repo.findChat(orgId, chatId);\n if (!chat) {\n return null;\n }\n const participants = await this.repo.participants(orgId, chatId);\n return { chat, participants };\n }\n\n async titlesFor(chatIds: string[], actor: Actor): Promise<Record<string, string>> {\n const orgId = actor.orgId;\n if (chatIds.length === 0) {\n return {};\n }\n const summaries = (await this.repo.chatSummaries(orgId, [...new Set(chatIds)])).filter(\n (summary) => summary.participantIds.includes(actor.id),\n );\n const others = [\n ...new Set(\n summaries.flatMap((summary) => summary.participantIds.filter((id) => id !== actor.id)),\n ),\n ];\n const records = others.length > 0 ? await this.profiles.authorsOf(orgId, others) : {};\n const nameOf = (id: string): string => {\n const record = records[id];\n return (record && authorName(record)) || 'A member';\n };\n return Object.fromEntries(\n summaries.flatMap((summary) => {\n const names = summary.participantIds.filter((id) => id !== actor.id).map(nameOf);\n if (names.length > 0) {\n return [[summary.chat.id, names.join(', ')]];\n }\n return summary.chat.kind === 'group' ? [[summary.chat.id, 'Group chat']] : [];\n }),\n );\n }\n\n unreadInChat(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread> {\n return this.repo.unreadFor(orgId, chatId, orgUserId);\n }\n\n findMessage(orgId: string, messageId: string): Promise<Message | null> {\n return this.repo.findMessage(orgId, messageId);\n }\n\n async latestMessageBody(orgId: string, chatId: string): Promise<string | null> {\n const page = await this.repo.listMessages(orgId, chatId, { page: 1, pageSize: 1 });\n return page.rows[0]?.body ?? null;\n }\n\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean> {\n return this.repo.isBlocked(orgId, communityId, a, b);\n }\n\n private async setLock(\n actor: Actor,\n chatId: string,\n locked: boolean,\n communityId: string | null,\n ): Promise<Chat> {\n const orgId = actor.orgId;\n const chat = await this.mustChat(this.repo, orgId, chatId);\n if (communityId !== null && chat.communityId !== communityId) {\n throw new NotFoundError('Chat', chatId);\n }\n this.requireModerator(actor, chat.communityId, locked ? 'lock chat' : 'unlock chat');\n await this.requireActor(actor, chat.communityId);\n if (Boolean(chat.lockedAt) === locked) {\n return chat;\n }\n const now = new Date();\n const saved = await this.uow.run(async ({ chats, outbox }) => {\n const next = await this.mustUpdate(chats, orgId, chatId, {\n lockedAt: locked ? now : null,\n lockedBy: locked ? actor.id : null,\n });\n if (locked) {\n await chats.resolveOpenFlagsForChat(orgId, chatId, {\n resolvedAt: now,\n resolvedBy: actor.id,\n resolution: 'locked',\n });\n }\n const users = await chats.participants(orgId, chatId);\n await outbox.append([this.updatedEvent(orgId, actor, next, users)]);\n return next;\n });\n this.logger.info(locked ? 'chat locked' : 'chat unlocked', {\n orgId,\n chatId,\n actorId: actor.id,\n });\n return saved;\n }\n\n private requireModerator(actor: Actor, communityId: string, action: string): void {\n if (!actor.permissions.has('communities:moderate', communityId)) {\n this.logger.warn('chat moderation refused — not a moderator', {\n orgId: actor.orgId,\n communityId,\n action,\n actorId: actor.id,\n });\n throw new ForbiddenError('You need communities:moderate');\n }\n }\n\n private async requireSettings(orgId: string, communityId: string): Promise<DmSettings> {\n const settings = await this.content.getDmSettings(communityId, this.authors.systemActor(orgId));\n if (!settings) {\n throw new NotFoundError('Community', communityId);\n }\n return settings;\n }\n\n private async requireActor(actor: Actor, communityId: string): Promise<void> {\n if (!(await this.access.isCommunityMember(communityId, actor))) {\n throw new ForbiddenError('You are not a member of this community');\n }\n }\n\n /** Whether the person is team in the community; throws when they are not a\n * member of it. */\n private async requirePerson(\n orgId: string,\n communityId: string,\n orgUserId: string,\n ): Promise<boolean> {\n const teams = await this.membersTeam(orgId, communityId, [orgUserId]);\n const team = teams.get(orgUserId);\n if (team === undefined) {\n throw new ForbiddenError('That person is not a member of this community');\n }\n return team;\n }\n\n /** Each member of the community among the given people, with whether they\n * are team there. People who are not members are left out. */\n private async membersTeam(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<Map<string, boolean>> {\n const members = await this.access.communityMembersAmong(orgId, communityId, orgUserIds);\n if (members.length === 0) {\n return new Map();\n }\n const permissions = await this.authors.permissionsOfMany(orgId, members);\n return new Map(\n members.map((orgUserId) => {\n const held = permissions.get(orgUserId);\n return [orgUserId, held !== undefined && isTeam(held, communityId)];\n }),\n );\n }\n\n private async teamsOf(orgId: string, communityId: string, orgUserIds: string[]): Promise<Teams> {\n return {\n asked: new Set(orgUserIds),\n team: await this.membersTeam(orgId, communityId, orgUserIds),\n };\n }\n\n private async assertMaySend(\n chats: ChatRepository,\n orgId: string,\n actor: Actor,\n chat: Chat,\n users: ChatUser[],\n settings: DmSettings,\n input: SendMessageInput,\n teams: Teams,\n ): Promise<void> {\n this.assertActive(users, actor.id);\n if (chat.lockedAt) {\n throw new ForbiddenError('This chat is locked');\n }\n if (chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n if (this.pendingCreator(chat, actor)) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n const others = users\n .filter(\n (other) =>\n other.orgUserId !== actor.id && (chat.kind === 'direct' || other.leftAt === null),\n )\n .map((other) => other.orgUserId);\n if (others.some((other) => !teams.asked.has(other))) {\n throw new ConflictError('This chat changed while you were sending; send again');\n }\n if (others.length > 0) {\n const recipients =\n chat.kind === 'group' ? others.filter((other) => teams.team.has(other)) : others;\n const actorTeam = isTeam(actor.permissions, chat.communityId);\n for (const other of recipients) {\n const team = teams.team.get(other);\n if (team === undefined || !this.mayMessage(settings, actorTeam, team)) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n if (recipients.length > 0) {\n const blocked = await chats.blockedWith(orgId, chat.communityId, actor.id, recipients);\n if (blocked.length > 0) {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n }\n if (input.replyToId) {\n const reply = await chats.findMessage(orgId, input.replyToId);\n if (!reply || reply.chatId !== chat.id) {\n throw new NotFoundError('Message', input.replyToId);\n }\n }\n }\n\n private mentionable(body: string, users: ChatUser[]): string[] {\n return keepAllowed(\n mentionTokens(body),\n users.filter((user) => user.leftAt === null).map((user) => user.orgUserId),\n );\n }\n\n private async writeMessage(\n chats: ChatRepository,\n mentions: MentionsRepository,\n orgId: string,\n actor: Actor,\n chat: Chat,\n users: ChatUser[],\n input: SendMessageInput,\n ): Promise<{ chat: Chat; message: Message; opened: boolean; mentioned: string[] }> {\n const opened =\n chat.kind === 'direct' && chat.status === 'pending' && chat.createdBy !== actor.id;\n const current = opened\n ? await this.mustUpdate(chats, orgId, chat.id, { status: 'open' })\n : chat;\n const message = await chats.insertMessage(\n orgId,\n this.newMessage(orgId, current, actor, input, new Date()),\n );\n if (input.assetIds.length > 0) {\n await chats.attach(orgId, current.communityId, message.id, input.assetIds);\n }\n const mentioned = this.mentionable(input.body, users);\n await mentions.sync(orgId, 'message', message.id, mentioned, message.createdAt);\n return { chat: current, message, opened, mentioned };\n }\n\n private assertMayMessage(settings: DmSettings, actorTeam: boolean, targetTeam: boolean): void {\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n if (!this.mayMessage(settings, actorTeam, targetTeam)) {\n throw new ForbiddenError('You cannot message this person');\n }\n }\n\n private assertGroup(settings: DmSettings): void {\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n if (!settings.groupChats) {\n throw new ForbiddenError('Group chats are off');\n }\n }\n\n private mayMessage(settings: DmSettings, actorTeam: boolean, targetTeam: boolean): boolean {\n if (actorTeam) {\n return settings.teamToAnyone;\n }\n if (targetTeam) {\n return settings.memberToTeam;\n }\n return settings.memberToMember;\n }\n\n private async requireActive(\n repo: ChatRepository,\n orgId: string,\n chatId: string,\n orgUserId: string,\n ): Promise<void> {\n const users = await repo.participants(orgId, chatId);\n this.assertActive(users, orgUserId);\n }\n\n private assertActive(users: ChatUser[], orgUserId: string): void {\n if (!users.some((user) => user.orgUserId === orgUserId && user.leftAt === null)) {\n throw new ForbiddenError('You are not in this chat');\n }\n }\n\n private async requireOwnedImages(\n orgId: string,\n orgUserId: string,\n assetIds: string[],\n ): Promise<void> {\n for (const assetId of assetIds) {\n if (!(await this.images.ownsImage(orgId, assetId, orgUserId))) {\n this.logger.debug('attachment refused', { orgId, orgUserId, assetId });\n throw new NotFoundError('Asset', assetId);\n }\n }\n }\n\n private async requireWritableChat(actor: Actor, chatId: string): Promise<void> {\n const chat = await this.mustChat(this.repo, actor.orgId, chatId);\n await this.requireActor(actor, chat.communityId);\n await this.requireActive(this.repo, actor.orgId, chat.id, actor.id);\n if (chat.lockedAt || chat.status === 'closed') {\n throw new ForbiddenError('You cannot send in this chat');\n }\n }\n\n private pendingCreator(chat: Chat, actor: Actor): boolean {\n return chat.kind === 'direct' && chat.status === 'pending' && chat.createdBy === actor.id;\n }\n\n private async mustChat(repo: ChatRepository, orgId: string, chatId: string): Promise<Chat> {\n const chat = await repo.findChat(orgId, chatId);\n if (!chat) {\n throw new NotFoundError('Chat', chatId);\n }\n return chat;\n }\n\n private async mustMessage(\n repo: ChatRepository,\n orgId: string,\n messageId: string,\n ): Promise<Message> {\n const message = await repo.findMessage(orgId, messageId);\n if (!message) {\n throw new NotFoundError('Message', messageId);\n }\n return message;\n }\n\n private async mustUpdate(\n chats: ChatRepository,\n orgId: string,\n chatId: string,\n patch: Partial<Pick<Chat, 'status' | 'lockedAt' | 'lockedBy'>>,\n ): Promise<Chat> {\n const next = await chats.updateChat(orgId, chatId, patch);\n if (!next) {\n throw new NotFoundError('Chat', chatId);\n }\n return next;\n }\n\n private memberRow(\n orgId: string,\n communityId: string,\n chatId: string,\n orgUserId: string,\n now: Date,\n ): ChatUser {\n return {\n orgId,\n communityId,\n chatId,\n orgUserId,\n joinedAt: now,\n leftAt: null,\n lastReadMessageId: null,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n private newMessage(\n orgId: string,\n chat: Chat,\n actor: Actor,\n input: SendMessageInput,\n now: Date,\n ): Message {\n return {\n orgId,\n id: genId('message'),\n communityId: chat.communityId,\n chatId: chat.id,\n orgUserId: actor.id,\n body: input.body,\n replyToId: input.replyToId,\n clientId: input.clientId ?? null,\n editedAt: null,\n deletedAt: null,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n private activeIds(users: ChatUser[]): string[] {\n return users\n .filter((user) => user.leftAt === null)\n .map((user) => user.orgUserId)\n .sort();\n }\n\n private chatData(chat: Chat, users: ChatUser[]) {\n return { ...chat, participantIds: this.activeIds(users) };\n }\n\n private meta(actor: Actor) {\n return { actorId: actor.id };\n }\n\n private createdEvent(orgId: string, actor: Actor, chat: Chat, users: ChatUser[]) {\n return discussionEvents.chatCreated.make({\n orgId,\n data: this.chatData(chat, users),\n metadata: this.meta(actor),\n });\n }\n\n private updatedEvent(orgId: string, actor: Actor, chat: Chat, users: ChatUser[]) {\n return discussionEvents.chatUpdated.make({\n orgId,\n data: this.chatData(chat, users),\n metadata: this.meta(actor),\n });\n }\n\n private addedEvent(orgId: string, actor: Actor, chat: Chat, orgUserId: string) {\n return discussionEvents.chatParticipantAdded.make({\n orgId,\n data: {\n chatId: chat.id,\n communityId: chat.communityId,\n orgUserId,\n addedBy: actor.id,\n },\n metadata: this.meta(actor),\n });\n }\n\n private sentEvent(\n orgId: string,\n actor: Actor,\n chat: Chat,\n message: Message,\n mentionedOrgUserIds: string[],\n ) {\n return discussionEvents.messageSent.make({\n orgId,\n data: { ...eventMessage(message), mentionedOrgUserIds },\n metadata: { ...this.meta(actor), chatStatus: chat.status },\n });\n }\n\n private async viewOf(actor: Actor, message: Message): Promise<MessageView> {\n const [view] = await this.withSenders(actor.orgId, actor, [message]);\n return view!;\n }\n\n private async withSenders(\n orgId: string,\n viewer: Actor | null,\n messages: Message[],\n ): Promise<MessageView[]> {\n if (messages.length === 0) {\n return [];\n }\n const messageIds = messages.map((message) => message.id);\n const senderIds = [...new Set(messages.map((message) => message.orgUserId))];\n const [records, permissions, attached, reactions, mentioned] = await Promise.all([\n this.profiles.authorsOf(orgId, senderIds),\n this.authors.permissionsOfMany(orgId, senderIds),\n this.repo.attachmentsOf(orgId, messageIds),\n this.repo.reactionsOf(orgId, messageIds, viewer?.id ?? ''),\n mentionViews(this.mentions, this.profiles, orgId, 'message', messageIds),\n ]);\n const hidden = (message: Message) =>\n message.deletedAt !== null &&\n !viewer?.permissions.has('communities:moderate', message.communityId);\n const shown = (message: Message) => (hidden(message) ? [] : (attached[message.id] ?? []));\n const links = await this.images.fileLinks(\n orgId,\n messages.flatMap((message) => shown(message).map((assetId) => ({ assetId }))),\n );\n return messages.map((message) => {\n const record = records[message.orgUserId];\n if (!record) {\n throw new NotFoundError('Member', message.orgUserId);\n }\n const counts = reactions[message.id];\n return {\n ...(hidden(message) ? { ...message, body: '' } : message),\n sender: authorView(\n orgId,\n record,\n this.fileUrls,\n teamBadge(permissions.get(message.orgUserId), message.communityId),\n ),\n attachments: shown(message).flatMap((assetId) => {\n const url = links[assetId];\n return url ? [{ assetId, url }] : [];\n }),\n reactions: counts?.reactions ?? {},\n viewerReaction: counts?.viewerReaction ?? null,\n mentions: hidden(message) ? [] : (mentioned[message.id] ?? []),\n };\n });\n }\n\n private async listItems(\n actor: Actor,\n communityId: string,\n rows: ChatInboxRow[],\n ): Promise<ChatListItem[]> {\n const orgId = actor.orgId;\n const seesDeleted = actor.permissions.has('communities:moderate', communityId);\n const now = new Date();\n const [subscribed, profiles] = await Promise.all([\n this.subscriptions.forViewer(\n orgId,\n actor.id,\n 'chat',\n rows.map((item) => item.chat.id),\n ),\n this.participantsOf(\n orgId,\n communityId,\n rows.flatMap((item) => item.participantIds),\n ),\n ]);\n const subscriptionOf = new Map(subscribed.map((row) => [row.itemId, row]));\n return rows.map((item) => ({\n ...item,\n participants: item.participantIds.flatMap((id) => profiles.get(id) ?? []),\n subscription: activeSubscription(subscriptionOf.get(item.chat.id), now),\n lastMessage:\n item.lastMessage && item.lastMessage.deletedAt !== null && !seesDeleted\n ? { ...item.lastMessage, body: '' }\n : item.lastMessage,\n }));\n }\n\n private async participantsOf(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<Map<string, CommentAuthor>> {\n const ids = [...new Set(orgUserIds)];\n if (ids.length === 0) {\n return new Map();\n }\n const [records, permissions] = await Promise.all([\n this.profiles.authorsOf(orgId, ids),\n this.authors.permissionsOfMany(orgId, ids),\n ]);\n return new Map(\n ids.flatMap((id) => {\n const record = records[id];\n return record\n ? [\n [\n id,\n authorView(\n orgId,\n record,\n this.fileUrls,\n teamBadge(permissions.get(id), communityId),\n ),\n ] as const,\n ]\n : [];\n }),\n );\n }\n}\n"],"mappings":"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"}
|
|
@@ -246,8 +246,6 @@ interface PostService {
|
|
|
246
246
|
listOwn(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;
|
|
247
247
|
listBookmarked(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;
|
|
248
248
|
/** Signed URL of the post's cover, for whoever can read the post. */
|
|
249
|
-
coverFile(postId: string, actor: Actor): Promise<string>;
|
|
250
|
-
assetFile(postId: string, assetId: string, actor: Actor): Promise<string>;
|
|
251
249
|
getPost(postId: string, actor: Actor): Promise<PostView>;
|
|
252
250
|
visible(postId: string, actor: Actor): Promise<{
|
|
253
251
|
post: Post;
|
|
@@ -431,10 +429,6 @@ interface ChatService extends ChatLiveReads {
|
|
|
431
429
|
isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;
|
|
432
430
|
titlesFor(chatIds: string[], actor: Actor): Promise<Record<string, string>>;
|
|
433
431
|
messages(chatId: string, query: Pick<FeedQuery, 'page' | 'pageSize'>, actor: Actor): Promise<Page<MessageView>>;
|
|
434
|
-
/** Signed URL of a message attachment: for the chat's participants while the
|
|
435
|
-
* message stands (moderators also after deletion), and for moderators while
|
|
436
|
-
* it is flagged. */
|
|
437
|
-
attachmentFile(chatId: string, messageId: string, assetId: string, actor: Actor): Promise<string>;
|
|
438
432
|
messageQueue(communityId: string, actor: Actor): Promise<MessageQueue>;
|
|
439
433
|
}
|
|
440
434
|
interface ChatRepository {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ports.js","names":[],"sources":["../../src/discussion/ports.ts"],"sourcesContent":["// discussion context — ports.\n// Inbound: the use cases the service implements.\n// Outbound: the persistence contract the repository fulfils.\nimport type {\n Activity,\n Chat,\n ChatLastMessage,\n ChatLiveEvent,\n ChatUnreadSummary,\n ChatUser,\n DigestRun,\n FlaggedComment,\n FlaggedCommentView,\n JsonValue,\n LiveMessage,\n MentionItemType,\n Message,\n MessageFlag,\n MessageFlagResolution,\n MessageView,\n Post,\n PostFlag,\n PostFlagResolution,\n PostStatus,\n PostView,\n SubjectRef,\n Subscription,\n SubscriptionItemType,\n SubscriptionView,\n} from '../types/index.js';\nimport type { CommunityDigest, DmSettings, SpaceDetail } from '../content/index.js';\nimport type { OutboxAppender, UnitOfWork } from '../shared/ports.js';\nimport type { Actor, EffectivePermissions } from '../shared/actor.js';\nimport type { Comment, CommentAuthor, CommentReport } from './model.js';\nimport type {\n CommentsConfig,\n CommentListItem,\n CommentView,\n ListCommentsQuery,\n Page,\n ReactionCounts,\n ReactionEmoji,\n} from './types.js';\n\n/** An activity's comments as this reader may see them, with the config that\n * decided it. */\nexport interface ActivityComments {\n config: CommentsConfig;\n comments: CommentView[];\n}\n\nexport interface CommentReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport interface CommentWithContext {\n comment: Comment;\n courseId: string;\n activityTitle: string;\n}\n\nexport type { Actor } from '../shared/actor.js';\nexport type PostCommentInput = {\n parentId: string | null;\n body: string;\n} & ({ activityId: string; postId?: undefined } | { postId: string; activityId?: undefined });\n\n/** A profile row as the repository loads it, keyed by `org_users.id`. `email`\n * is stripped before comments are served and kept only for the moderation list. */\nexport interface AuthorRecord {\n id: string;\n firstName: string | null;\n lastName: string | null;\n email: string;\n image: string | null;\n displayName: string | null;\n headline: string | null;\n imageAssetOrgId: string | null;\n imageAssetId: string | null;\n}\n\nexport interface ItemTitle {\n title: string;\n excerpt?: string;\n}\n\nexport interface ItemTitles {\n titlesFor(refs: SubjectRef[], actor: Actor): Promise<Record<string, ItemTitle>>;\n}\n\nexport interface AuthorProfileReader {\n authorsOf(orgId: string, orgUserIds: string[]): Promise<Record<string, AuthorRecord>>;\n}\n\n/** Where a comment must be for a moderation call: in one community, on a\n * lesson, or anywhere the actor can reach. */\nexport type CommentWithin = { communityId: string } | 'lesson' | 'any';\n\nexport interface DiscussionService {\n /** Post a root comment or a reply. Lands pending where review is required and\n * the poster does not skip review. */\n postComment(input: PostCommentInput, actor: Actor): Promise<CommentView>;\n /** Reply to a lesson comment as a moderator of its course; a reply to a reply\n * attaches to its root. */\n replyToComment(commentId: string, body: string, actor: Actor): Promise<CommentView>;\n /** Author-only. Throws ForbiddenError for anyone else, moderators included. */\n edit(commentId: string, body: string, actor: Actor): Promise<CommentView>;\n /** The author, or a moderator of where the comment is. */\n remove(commentId: string, within: CommentWithin, actor: Actor): Promise<CommentView>;\n /** A moderator of where the comment is. Returns the comment to published. */\n restore(commentId: string, within: CommentWithin, actor: Actor): Promise<CommentView>;\n /** A moderator of where the comment is. Publishes a pending comment. */\n approve(commentId: string, actor: Actor): Promise<Comment>;\n /** Make a comment published, whichever state it is in. A moderator of where\n * the comment is. */\n publish(commentId: string, within: CommentWithin, actor: Actor): Promise<Comment>;\n\n /** The activity's comments as this reader may see them. */\n activityComments(activityId: string, actor: Actor): Promise<ActivityComments>;\n\n /** One page of top-level comments, oldest first, each with its replies. */\n postComments(postId: string, query: PageQuery, actor: Actor): Promise<Page<CommentView>>;\n\n /** The lesson comments of the courses the actor moderates, filtered. Rows\n * carry the author's email and a removed comment's body. */\n listComments(query: ListCommentsQuery, actor: Actor): Promise<Page<CommentListItem>>;\n\n setReaction(\n commentId: string,\n emoji: ReactionEmoji | null,\n actor: Actor,\n ): Promise<CommentReactions>;\n\n resolveReports(commentId: string, within: CommentWithin, actor: Actor): Promise<void>;\n\n /** Accepted even on locked comments — a locked activity can still hold\n * something a moderator needs to see. */\n reportComment(commentId: string, reason: string, actor: Actor): Promise<CommentReport>;\n\n /** Plain read of one comment — no rules applied. */\n getComment(orgId: string, commentId: string): Promise<Comment | null>;\n commenterIds(\n orgId: string,\n target: { postId: string } | { activityId: string },\n ): Promise<string[]>;\n}\n\nexport interface ContentAccessReader {\n hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean>;\n}\n\nexport interface ActivityGetter {\n getActivity(activityId: string, actor: Actor): Promise<Activity | null>;\n}\n\nexport interface SpaceReader {\n getSpace(spaceId: string, actor: Actor): Promise<SpaceDetail | null>;\n getDmSettings(communityId: string, actor: Actor): Promise<DmSettings | null>;\n}\n\nexport interface OpenableSpace {\n id: string;\n communityId: string;\n accessRule: SpaceDetail['accessRule'];\n}\n\nexport interface CommunityAccess {\n isCommunityMember(communityId: string, actor: Actor): Promise<boolean>;\n canOpenSpace(spaceId: string, actor: Actor): Promise<boolean>;\n canOpen(space: OpenableSpace, actor: Actor): Promise<boolean>;\n openSpaceIds(communityId: string, actor: Actor): Promise<string[]>;\n communityMembersAmong(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<string[]>;\n spaceOpenersAmong(orgId: string, spaceId: string, orgUserIds: string[]): Promise<string[]>;\n}\n\nexport interface DiscussionRepository extends PostCommentReader, AuthorProfileReader {\n insertComment(orgId: string, comment: Comment): Promise<Comment>;\n findComment(orgId: string, id: string): Promise<Comment | null>;\n commenterIds(\n orgId: string,\n target: { postId: string } | { activityId: string },\n ): Promise<string[]>;\n updateComment(\n orgId: string,\n id: string,\n patch: Partial<Pick<Comment, 'body' | 'status' | 'removedBy' | 'updatedAt'>>,\n ): Promise<Comment | null>;\n /** Every comment on the activity, oldest first, including removed ones. */\n listByActivity(orgId: string, activityId: string): Promise<Comment[]>;\n /** A page of the post's top-level threads the viewer can see, oldest first, with their replies. `total` counts threads. */\n listThreadsByPost(orgId: string, postId: string, query: PostThreadsQuery): Promise<Page<Comment>>;\n\n reactionsOf(\n orgId: string,\n commentIds: string[],\n viewerId: string,\n ): Promise<Record<string, CommentReactions>>;\n setReaction(\n orgId: string,\n commentId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<boolean>;\n\n /** Returns null when this person has already reported this comment. */\n insertReport(orgId: string, report: CommentReport): Promise<CommentReport | null>;\n /** Every unresolved report against the given comments. */\n listOpenReports(orgId: string, commentIds: string[]): Promise<CommentReport[]>;\n resolveReportsFor(\n orgId: string,\n commentId: string,\n resolved: {\n resolvedAt: Date;\n resolvedBy: string;\n resolution: 'dismissed' | 'removed';\n },\n ): Promise<void>;\n\n /** `courseIds` limits the rows to those courses; undefined means every course. */\n listComments(\n orgId: string,\n query: ListCommentsQuery,\n courseIds?: string[],\n ): Promise<Page<CommentWithContext>>;\n}\n\n/** Writes that emit events run through this scope so the row and the outbox\n * entry commit in one transaction. */\nexport interface DiscussionWriteScope {\n discussion: DiscussionRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type DiscussionUnitOfWork = UnitOfWork<DiscussionWriteScope>;\n\nexport interface AuthorPermissionReader {\n permissionsOfMany(\n orgId: string,\n orgUserIds: string[],\n ): Promise<Map<string, EffectivePermissions>>;\n actorFor(orgId: string, orgUserId: string): Promise<Actor | null>;\n systemActor(orgId: string): Actor;\n}\n\nexport type PostMode = 'draft' | 'submit' | 'schedule';\n\nexport interface CreatePostInput {\n communityId: string;\n spaceId: string;\n title: string;\n body: string;\n topicId: string | null;\n extra: JsonValue;\n coverAssetId: string | null;\n mode: PostMode;\n scheduledAt: Date | null;\n}\n\nexport interface UpdatePostInput {\n title?: string;\n body?: string;\n topicId?: string | null;\n extra?: JsonValue;\n coverAssetId?: string | null;\n}\n\nexport type PostPatch = Partial<\n Pick<\n Post,\n | 'title'\n | 'body'\n | 'topicId'\n | 'extra'\n | 'coverAssetId'\n | 'status'\n | 'scheduledAt'\n | 'publishedAt'\n | 'removedBy'\n | 'pinned'\n | 'commentsClosed'\n | 'reactionsEnabled'\n >\n>;\n\nexport interface FeedQuery {\n page: number;\n pageSize: number;\n topicId?: string;\n}\n\nexport interface PostReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport type PageQuery = Pick<FeedQuery, 'page' | 'pageSize'>;\n\nexport interface PostThreadsQuery extends PageQuery {\n viewerId: string;\n seesHeld: boolean;\n}\n\nexport interface BookmarkQuery extends PageQuery {\n visibleStatuses: PostStatus[];\n}\n\nexport const MODERATION_QUEUE_LIMIT = 100;\n\nexport interface SpaceModerationQueue {\n pending: PostView[];\n flags: PostFlag[];\n commentFlags: FlaggedCommentView[];\n}\n\nexport interface PostCommentReader {\n listOpenFlagsForSpace(orgId: string, spaceId: string, limit: number): Promise<FlaggedComment[]>;\n publishedCommentCounts(orgId: string, postIds: string[]): Promise<Record<string, number>>;\n}\n\nexport interface PublishedPost {\n postId: string;\n spaceId: string;\n title: string;\n orgUserId: string;\n commentCount: number;\n}\n\nexport interface PostService {\n visibleTitles(postIds: string[], actor: Actor): Promise<Record<string, string>>;\n publishedBetween(\n orgId: string,\n communityId: string,\n from: Date,\n to: Date,\n ): Promise<PublishedPost[]>;\n create(input: CreatePostInput, actor: Actor): Promise<PostView>;\n update(postId: string, patch: UpdatePostInput, actor: Actor): Promise<PostView>;\n submit(postId: string, actor: Actor): Promise<PostView>;\n schedule(postId: string, scheduledAt: Date, actor: Actor): Promise<PostView>;\n unschedule(postId: string, actor: Actor): Promise<PostView>;\n publishScheduled(orgId: string, postId: string, now: Date): Promise<void>;\n approve(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;\n remove(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;\n restore(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;\n pin(postId: string, actor: Actor): Promise<PostView>;\n unpin(postId: string, actor: Actor): Promise<PostView>;\n closeComments(postId: string, actor: Actor): Promise<PostView>;\n openComments(postId: string, actor: Actor): Promise<PostView>;\n setReactions(postId: string, enabled: boolean, actor: Actor): Promise<PostView>;\n setReaction(postId: string, emoji: ReactionEmoji | null, actor: Actor): Promise<PostReactions>;\n bookmark(postId: string, actor: Actor): Promise<void>;\n unbookmark(postId: string, actor: Actor): Promise<void>;\n flag(postId: string, reason: string, actor: Actor): Promise<PostFlag>;\n resolveFlags(\n postId: string,\n resolution: Extract<PostFlagResolution, 'dismissed'>,\n communityId: string | null,\n actor: Actor,\n ): Promise<void>;\n spaceFeed(\n communityId: string,\n spaceId: string,\n query: FeedQuery,\n actor: Actor,\n ): Promise<Page<PostView>>;\n memberFeed(\n communityId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n actor: Actor,\n ): Promise<Page<PostView>>;\n listOwn(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;\n listBookmarked(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;\n /** Signed URL of the post's cover, for whoever can read the post. */\n coverFile(postId: string, actor: Actor): Promise<string>;\n assetFile(postId: string, assetId: string, actor: Actor): Promise<string>;\n getPost(postId: string, actor: Actor): Promise<PostView>;\n visible(postId: string, actor: Actor): Promise<{ post: Post; space: SpaceDetail }>;\n find(orgId: string, postId: string): Promise<Post | null>;\n moderationQueue(\n communityId: string,\n spaceId: string,\n actor: Actor,\n ): Promise<SpaceModerationQueue>;\n}\n\nexport interface PostGuard {\n status: PostStatus;\n scheduledAt?: Date;\n dueBy?: Date;\n}\n\nexport interface PostRepository {\n insert(orgId: string, post: Post): Promise<Post>;\n update(orgId: string, id: string, patch: PostPatch): Promise<Post | null>;\n updateIf(orgId: string, id: string, guard: PostGuard, patch: PostPatch): Promise<Post | null>;\n find(orgId: string, id: string): Promise<Post | null>;\n findMany(orgId: string, ids: string[]): Promise<Post[]>;\n publishedBetween(\n orgId: string,\n communityId: string,\n from: Date,\n to: Date,\n ): Promise<PublishedPost[]>;\n listPublished(orgId: string, spaceIds: string[], query: FeedQuery): Promise<Page<Post>>;\n listByAuthor(\n orgId: string,\n orgUserId: string,\n spaceIds: string[],\n query: PageQuery,\n ): Promise<Page<Post>>;\n listBookmarked(\n orgId: string,\n orgUserId: string,\n spaceIds: string[],\n query: BookmarkQuery,\n ): Promise<Page<Post>>;\n listPending(orgId: string, spaceId: string, limit: number): Promise<Post[]>;\n reactionsOf(\n orgId: string,\n postIds: string[],\n viewerId: string,\n ): Promise<Record<string, PostReactions>>;\n setReaction(\n orgId: string,\n postId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<boolean>;\n bookmark(orgId: string, postId: string, orgUserId: string): Promise<void>;\n unbookmark(orgId: string, postId: string, orgUserId: string): Promise<void>;\n bookmarkedAmong(orgId: string, postIds: string[], orgUserId: string): Promise<string[]>;\n insertFlag(orgId: string, flag: PostFlag): Promise<PostFlag | null>;\n findFlag(orgId: string, postId: string, orgUserId: string): Promise<PostFlag | null>;\n listOpenFlags(orgId: string, spaceId: string, limit: number): Promise<PostFlag[]>;\n resolveFlags(\n orgId: string,\n postId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: PostFlagResolution },\n ): Promise<void>;\n}\n\nexport interface PostWriteScope {\n posts: PostRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type PostUnitOfWork = UnitOfWork<PostWriteScope>;\n\nexport interface DigestScheduleReader {\n communitiesWithDigest(): Promise<CommunityDigest[]>;\n}\n\nexport interface DigestRunRepository {\n listRuns(): Promise<DigestRun[]>;\n advance(orgId: string, communityId: string, dueAt: Date): Promise<boolean>;\n}\n\nexport interface DigestWriteScope {\n digests: DigestRunRepository;\n outbox: OutboxAppender;\n}\nexport type DigestUnitOfWork = UnitOfWork<DigestWriteScope>;\n\nexport interface DigestService {\n openDue(now: Date): Promise<void>;\n}\n\nexport interface StartDirectInput {\n communityId: string;\n orgUserId: string;\n body: string | null;\n assetIds: string[];\n}\n\nexport interface StartGroupInput {\n communityId: string;\n orgUserIds: string[];\n}\n\nexport interface SendMessageInput {\n body: string;\n replyToId: string | null;\n assetIds: string[];\n clientId?: string;\n}\n\nexport interface ChatListItem {\n chat: Chat;\n participantIds: string[];\n participants: CommentAuthor[];\n lastReadMessageId: string | null;\n subscription: SubscriptionView | null;\n unread: number;\n lastMessage: ChatLastMessage | null;\n}\n\nexport type ChatInboxRow = Omit<ChatListItem, 'subscription' | 'participants'>;\n\n/** `requests`: true for only the pending requests to the member, false for\n * everything else, omitted for every chat. */\nexport interface ChatListQuery extends PageQuery {\n requests?: boolean;\n}\n\nexport interface ReactionChange {\n inserted: boolean;\n changed: boolean;\n}\n\nexport interface ChatMemberState {\n chatId: string;\n unread: number;\n lastReadMessageId: string | null;\n subscription: SubscriptionView | null;\n}\n\n/** A member's state in each chat they are in in one community, with their\n * unread total there. */\nexport interface ChatMemberStates {\n orgUserId: string;\n unread: ChatUnreadSummary;\n chats: ChatMemberState[];\n}\n\nexport interface LiveChat {\n chat: Chat;\n participantIds: string[];\n participants: CommentAuthor[];\n}\n\n/** What the chat hub reads for each change, as current state. */\nexport interface ChatLiveReads {\n activeParticipants(orgId: string, chatId: string): Promise<string[]>;\n messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]>;\n messageReactions(orgId: string, messageId: string): Promise<ReactionCounts>;\n /** The chat with its active participants and their profiles. */\n liveChat(orgId: string, chatId: string): Promise<LiveChat | null>;\n memberStates(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<ChatMemberStates[]>;\n}\n\nexport interface ChatUnread {\n count: number;\n latest: { messageId: string; senderOrgUserId: string; body: string } | null;\n}\n\nexport interface MessageReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport interface ChatSummary {\n chat: Chat;\n participantIds: string[];\n}\n\nexport interface MessageQueueEntry {\n message: MessageView;\n flags: MessageFlag[];\n chat: Chat;\n participantIds: string[];\n}\n\nexport interface MessageQueue {\n entries: MessageQueueEntry[];\n}\n\nexport interface ChatService extends ChatLiveReads {\n startDirect(input: StartDirectInput, actor: Actor): Promise<Chat>;\n startGroup(input: StartGroupInput, actor: Actor): Promise<Chat>;\n addParticipant(chatId: string, orgUserId: string, actor: Actor): Promise<Chat>;\n leave(chatId: string, actor: Actor): Promise<Chat>;\n accept(chatId: string, actor: Actor): Promise<Chat>;\n send(chatId: string, input: SendMessageInput, actor: Actor): Promise<MessageView>;\n editMessage(messageId: string, body: string, actor: Actor): Promise<MessageView>;\n deleteMessage(messageId: string, actor: Actor): Promise<MessageView>;\n setReaction(\n messageId: string,\n emoji: ReactionEmoji | null,\n actor: Actor,\n ): Promise<MessageReactions>;\n markRead(chatId: string, messageId: string, actor: Actor): Promise<void>;\n block(communityId: string, orgUserId: string, actor: Actor): Promise<void>;\n unblock(communityId: string, orgUserId: string, actor: Actor): Promise<void>;\n flagMessage(messageId: string, reason: string, actor: Actor): Promise<MessageFlag>;\n resolveMessageFlags(\n messageId: string,\n resolution: Extract<MessageFlagResolution, 'dismissed'>,\n communityId: string | null,\n actor: Actor,\n ): Promise<void>;\n lock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat>;\n unlock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat>;\n listChats(communityId: string, query: ChatListQuery, actor: Actor): Promise<Page<ChatListItem>>;\n /** One live stream of the member's chat events in the community. */\n watch(communityId: string, actor: Actor): Promise<AsyncIterable<ChatLiveEvent>>;\n chat(chatId: string, actor: Actor): Promise<ChatListItem>;\n directChat(communityId: string, orgUserId: string, actor: Actor): Promise<ChatListItem>;\n unreadSummary(communityId: string, actor: Actor): Promise<ChatUnreadSummary>;\n typing(chatId: string, actor: Actor): Promise<void>;\n /** The messages as every participant sees them: a deleted message without\n * its body, attachments or mentions, and no viewer reaction. */\n messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]>;\n chatSummary(\n orgId: string,\n chatId: string,\n ): Promise<{ chat: Chat; participants: ChatUser[] } | null>;\n latestMessageBody(orgId: string, chatId: string): Promise<string | null>;\n unreadInChat(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;\n findMessage(orgId: string, messageId: string): Promise<Message | null>;\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;\n titlesFor(chatIds: string[], actor: Actor): Promise<Record<string, string>>;\n messages(\n chatId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n actor: Actor,\n ): Promise<Page<MessageView>>;\n /** Signed URL of a message attachment: for the chat's participants while the\n * message stands (moderators also after deletion), and for moderators while\n * it is flagged. */\n attachmentFile(chatId: string, messageId: string, assetId: string, actor: Actor): Promise<string>;\n messageQueue(communityId: string, actor: Actor): Promise<MessageQueue>;\n}\n\nexport interface ChatRepository {\n lockDirectPair(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherOrgUserId: string,\n ): Promise<void>;\n findDirect(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherOrgUserId: string,\n ): Promise<Chat | null>;\n insertChat(orgId: string, chat: Chat): Promise<Chat>;\n updateChat(\n orgId: string,\n id: string,\n patch: Partial<Pick<Chat, 'status' | 'lockedAt' | 'lockedBy'>>,\n ): Promise<Chat | null>;\n findChat(orgId: string, id: string): Promise<Chat | null>;\n listForUser(\n orgId: string,\n communityId: string,\n orgUserId: string,\n query: ChatListQuery,\n ): Promise<Page<ChatInboxRow>>;\n /** The member's inbox row for one chat they are in, or null. */\n inboxRow(orgId: string, chatId: string, orgUserId: string): Promise<ChatInboxRow | null>;\n participants(orgId: string, chatId: string): Promise<ChatUser[]>;\n activeParticipants(orgId: string, chatId: string): Promise<string[]>;\n join(orgId: string, user: ChatUser): Promise<void>;\n markLeft(orgId: string, chatId: string, orgUserId: string, leftAt: Date): Promise<void>;\n /** Moves the read mark to the message only when it is newer (by created\n * time, then id) than the current one. True when it moved. */\n setRead(orgId: string, chatId: string, orgUserId: string, messageId: string): Promise<boolean>;\n unreadFor(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;\n /** Unread messages across the member's chats in the community that are\n * neither muted at `at` nor pending requests to them, plus those requests. */\n unreadSummary(\n orgId: string,\n communityId: string,\n orgUserId: string,\n at: Date,\n ): Promise<ChatUnreadSummary>;\n memberStates(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<ChatMemberStates[]>;\n findByClientId(\n orgId: string,\n chatId: string,\n orgUserId: string,\n clientId: string,\n ): Promise<Message | null>;\n insertMessage(orgId: string, message: Message): Promise<Message>;\n updateMessage(\n orgId: string,\n id: string,\n patch: Partial<Pick<Message, 'body' | 'editedAt' | 'deletedAt'>>,\n ): Promise<Message | null>;\n findMessage(orgId: string, id: string): Promise<Message | null>;\n listMessages(\n orgId: string,\n chatId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n ): Promise<Page<Message>>;\n attach(orgId: string, communityId: string, messageId: string, assetIds: string[]): Promise<void>;\n attachmentsOf(orgId: string, messageIds: string[]): Promise<Record<string, string[]>>;\n blockedWith(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherIds: string[],\n ): Promise<string[]>;\n setReaction(\n orgId: string,\n communityId: string,\n messageId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<ReactionChange>;\n reactionsOf(\n orgId: string,\n messageIds: string[],\n viewerId: string,\n ): Promise<Record<string, MessageReactions>>;\n block(\n orgId: string,\n communityId: string,\n blockerOrgUserId: string,\n blockedOrgUserId: string,\n ): Promise<void>;\n unblock(\n orgId: string,\n communityId: string,\n blockerOrgUserId: string,\n blockedOrgUserId: string,\n ): Promise<void>;\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;\n anyBlockedAmong(orgId: string, communityId: string, orgUserIds: string[]): Promise<boolean>;\n insertFlag(orgId: string, flag: MessageFlag): Promise<MessageFlag | null>;\n findFlag(orgId: string, messageId: string, orgUserId: string): Promise<MessageFlag | null>;\n listOpenFlags(orgId: string, communityId: string, limit?: number): Promise<MessageFlag[]>;\n messagesByIds(orgId: string, ids: string[]): Promise<Message[]>;\n chatSummaries(orgId: string, chatIds: string[]): Promise<ChatSummary[]>;\n resolveFlags(\n orgId: string,\n messageId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: MessageFlagResolution },\n ): Promise<void>;\n resolveOpenFlagsForChat(\n orgId: string,\n chatId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: MessageFlagResolution },\n ): Promise<void>;\n}\n\nexport interface ChatWriteScope {\n chats: ChatRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type ChatUnitOfWork = UnitOfWork<ChatWriteScope>;\n\nexport type SubscriptionInput =\n { state: 'following' } | { state: 'muted'; mutedUntil?: Date | null } | { state: 'none' };\n\nexport type MuteInput = Exclude<SubscriptionInput, { state: 'following' }>;\n\nexport interface SubscriptionWriteScope {\n subscriptions: SubscriptionsRepository;\n outbox: OutboxAppender;\n}\nexport type SubscriptionUnitOfWork = UnitOfWork<SubscriptionWriteScope>;\n\nexport interface SubscriptionService {\n setPost(postId: string, input: SubscriptionInput, actor: Actor): Promise<void>;\n setSpace(spaceId: string, input: MuteInput, actor: Actor): Promise<void>;\n setChat(chatId: string, input: MuteInput, actor: Actor): Promise<void>;\n viewerSubscriptions(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemIds: string[],\n at: Date,\n ): Promise<Record<string, SubscriptionView>>;\n followerIds(orgId: string, postId: string): Promise<string[]>;\n mutedAmong(orgId: string, item: SubjectRef, orgUserIds: string[], at: Date): Promise<string[]>;\n itemSubscriptions(orgId: string, item: SubjectRef): Promise<Subscription[]>;\n}\n\nexport interface SubscriptionsRepository {\n upsert(orgId: string, subscription: Subscription): Promise<void>;\n remove(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemId: string,\n ): Promise<void>;\n forViewer(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemIds: string[],\n ): Promise<Subscription[]>;\n ofItem(orgId: string, itemType: SubscriptionItemType, itemId: string): Promise<Subscription[]>;\n followerIds(orgId: string, postId: string): Promise<string[]>;\n mutedAmong(\n orgId: string,\n itemType: SubscriptionItemType,\n itemId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<string[]>;\n}\n\nexport interface MentionsRepository {\n ofItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<string[]>;\n forItems(\n orgId: string,\n itemType: MentionItemType,\n itemIds: string[],\n ): Promise<Record<string, string[]>>;\n sync(\n orgId: string,\n itemType: MentionItemType,\n itemId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<string[]>;\n removeItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<void>;\n}\n"],"mappings":";AAuTA,MAAa,yBAAyB"}
|
|
1
|
+
{"version":3,"file":"ports.js","names":[],"sources":["../../src/discussion/ports.ts"],"sourcesContent":["// discussion context — ports.\n// Inbound: the use cases the service implements.\n// Outbound: the persistence contract the repository fulfils.\nimport type {\n Activity,\n Chat,\n ChatLastMessage,\n ChatLiveEvent,\n ChatUnreadSummary,\n ChatUser,\n DigestRun,\n FlaggedComment,\n FlaggedCommentView,\n JsonValue,\n LiveMessage,\n MentionItemType,\n Message,\n MessageFlag,\n MessageFlagResolution,\n MessageView,\n Post,\n PostFlag,\n PostFlagResolution,\n PostStatus,\n PostView,\n SubjectRef,\n Subscription,\n SubscriptionItemType,\n SubscriptionView,\n} from '../types/index.js';\nimport type { CommunityDigest, DmSettings, SpaceDetail } from '../content/index.js';\nimport type { OutboxAppender, UnitOfWork } from '../shared/ports.js';\nimport type { Actor, EffectivePermissions } from '../shared/actor.js';\nimport type { Comment, CommentAuthor, CommentReport } from './model.js';\nimport type {\n CommentsConfig,\n CommentListItem,\n CommentView,\n ListCommentsQuery,\n Page,\n ReactionCounts,\n ReactionEmoji,\n} from './types.js';\n\n/** An activity's comments as this reader may see them, with the config that\n * decided it. */\nexport interface ActivityComments {\n config: CommentsConfig;\n comments: CommentView[];\n}\n\nexport interface CommentReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport interface CommentWithContext {\n comment: Comment;\n courseId: string;\n activityTitle: string;\n}\n\nexport type { Actor } from '../shared/actor.js';\nexport type PostCommentInput = {\n parentId: string | null;\n body: string;\n} & ({ activityId: string; postId?: undefined } | { postId: string; activityId?: undefined });\n\n/** A profile row as the repository loads it, keyed by `org_users.id`. `email`\n * is stripped before comments are served and kept only for the moderation list. */\nexport interface AuthorRecord {\n id: string;\n firstName: string | null;\n lastName: string | null;\n email: string;\n image: string | null;\n displayName: string | null;\n headline: string | null;\n imageAssetOrgId: string | null;\n imageAssetId: string | null;\n}\n\nexport interface ItemTitle {\n title: string;\n excerpt?: string;\n}\n\nexport interface ItemTitles {\n titlesFor(refs: SubjectRef[], actor: Actor): Promise<Record<string, ItemTitle>>;\n}\n\nexport interface AuthorProfileReader {\n authorsOf(orgId: string, orgUserIds: string[]): Promise<Record<string, AuthorRecord>>;\n}\n\n/** Where a comment must be for a moderation call: in one community, on a\n * lesson, or anywhere the actor can reach. */\nexport type CommentWithin = { communityId: string } | 'lesson' | 'any';\n\nexport interface DiscussionService {\n /** Post a root comment or a reply. Lands pending where review is required and\n * the poster does not skip review. */\n postComment(input: PostCommentInput, actor: Actor): Promise<CommentView>;\n /** Reply to a lesson comment as a moderator of its course; a reply to a reply\n * attaches to its root. */\n replyToComment(commentId: string, body: string, actor: Actor): Promise<CommentView>;\n /** Author-only. Throws ForbiddenError for anyone else, moderators included. */\n edit(commentId: string, body: string, actor: Actor): Promise<CommentView>;\n /** The author, or a moderator of where the comment is. */\n remove(commentId: string, within: CommentWithin, actor: Actor): Promise<CommentView>;\n /** A moderator of where the comment is. Returns the comment to published. */\n restore(commentId: string, within: CommentWithin, actor: Actor): Promise<CommentView>;\n /** A moderator of where the comment is. Publishes a pending comment. */\n approve(commentId: string, actor: Actor): Promise<Comment>;\n /** Make a comment published, whichever state it is in. A moderator of where\n * the comment is. */\n publish(commentId: string, within: CommentWithin, actor: Actor): Promise<Comment>;\n\n /** The activity's comments as this reader may see them. */\n activityComments(activityId: string, actor: Actor): Promise<ActivityComments>;\n\n /** One page of top-level comments, oldest first, each with its replies. */\n postComments(postId: string, query: PageQuery, actor: Actor): Promise<Page<CommentView>>;\n\n /** The lesson comments of the courses the actor moderates, filtered. Rows\n * carry the author's email and a removed comment's body. */\n listComments(query: ListCommentsQuery, actor: Actor): Promise<Page<CommentListItem>>;\n\n setReaction(\n commentId: string,\n emoji: ReactionEmoji | null,\n actor: Actor,\n ): Promise<CommentReactions>;\n\n resolveReports(commentId: string, within: CommentWithin, actor: Actor): Promise<void>;\n\n /** Accepted even on locked comments — a locked activity can still hold\n * something a moderator needs to see. */\n reportComment(commentId: string, reason: string, actor: Actor): Promise<CommentReport>;\n\n /** Plain read of one comment — no rules applied. */\n getComment(orgId: string, commentId: string): Promise<Comment | null>;\n commenterIds(\n orgId: string,\n target: { postId: string } | { activityId: string },\n ): Promise<string[]>;\n}\n\nexport interface ContentAccessReader {\n hasAccess(orgId: string, orgUserId: string, contentId: string): Promise<boolean>;\n}\n\nexport interface ActivityGetter {\n getActivity(activityId: string, actor: Actor): Promise<Activity | null>;\n}\n\nexport interface SpaceReader {\n getSpace(spaceId: string, actor: Actor): Promise<SpaceDetail | null>;\n getDmSettings(communityId: string, actor: Actor): Promise<DmSettings | null>;\n}\n\nexport interface OpenableSpace {\n id: string;\n communityId: string;\n accessRule: SpaceDetail['accessRule'];\n}\n\nexport interface CommunityAccess {\n isCommunityMember(communityId: string, actor: Actor): Promise<boolean>;\n canOpenSpace(spaceId: string, actor: Actor): Promise<boolean>;\n canOpen(space: OpenableSpace, actor: Actor): Promise<boolean>;\n openSpaceIds(communityId: string, actor: Actor): Promise<string[]>;\n communityMembersAmong(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<string[]>;\n spaceOpenersAmong(orgId: string, spaceId: string, orgUserIds: string[]): Promise<string[]>;\n}\n\nexport interface DiscussionRepository extends PostCommentReader, AuthorProfileReader {\n insertComment(orgId: string, comment: Comment): Promise<Comment>;\n findComment(orgId: string, id: string): Promise<Comment | null>;\n commenterIds(\n orgId: string,\n target: { postId: string } | { activityId: string },\n ): Promise<string[]>;\n updateComment(\n orgId: string,\n id: string,\n patch: Partial<Pick<Comment, 'body' | 'status' | 'removedBy' | 'updatedAt'>>,\n ): Promise<Comment | null>;\n /** Every comment on the activity, oldest first, including removed ones. */\n listByActivity(orgId: string, activityId: string): Promise<Comment[]>;\n /** A page of the post's top-level threads the viewer can see, oldest first, with their replies. `total` counts threads. */\n listThreadsByPost(orgId: string, postId: string, query: PostThreadsQuery): Promise<Page<Comment>>;\n\n reactionsOf(\n orgId: string,\n commentIds: string[],\n viewerId: string,\n ): Promise<Record<string, CommentReactions>>;\n setReaction(\n orgId: string,\n commentId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<boolean>;\n\n /** Returns null when this person has already reported this comment. */\n insertReport(orgId: string, report: CommentReport): Promise<CommentReport | null>;\n /** Every unresolved report against the given comments. */\n listOpenReports(orgId: string, commentIds: string[]): Promise<CommentReport[]>;\n resolveReportsFor(\n orgId: string,\n commentId: string,\n resolved: {\n resolvedAt: Date;\n resolvedBy: string;\n resolution: 'dismissed' | 'removed';\n },\n ): Promise<void>;\n\n /** `courseIds` limits the rows to those courses; undefined means every course. */\n listComments(\n orgId: string,\n query: ListCommentsQuery,\n courseIds?: string[],\n ): Promise<Page<CommentWithContext>>;\n}\n\n/** Writes that emit events run through this scope so the row and the outbox\n * entry commit in one transaction. */\nexport interface DiscussionWriteScope {\n discussion: DiscussionRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type DiscussionUnitOfWork = UnitOfWork<DiscussionWriteScope>;\n\nexport interface AuthorPermissionReader {\n permissionsOfMany(\n orgId: string,\n orgUserIds: string[],\n ): Promise<Map<string, EffectivePermissions>>;\n actorFor(orgId: string, orgUserId: string): Promise<Actor | null>;\n systemActor(orgId: string): Actor;\n}\n\nexport type PostMode = 'draft' | 'submit' | 'schedule';\n\nexport interface CreatePostInput {\n communityId: string;\n spaceId: string;\n title: string;\n body: string;\n topicId: string | null;\n extra: JsonValue;\n coverAssetId: string | null;\n mode: PostMode;\n scheduledAt: Date | null;\n}\n\nexport interface UpdatePostInput {\n title?: string;\n body?: string;\n topicId?: string | null;\n extra?: JsonValue;\n coverAssetId?: string | null;\n}\n\nexport type PostPatch = Partial<\n Pick<\n Post,\n | 'title'\n | 'body'\n | 'topicId'\n | 'extra'\n | 'coverAssetId'\n | 'status'\n | 'scheduledAt'\n | 'publishedAt'\n | 'removedBy'\n | 'pinned'\n | 'commentsClosed'\n | 'reactionsEnabled'\n >\n>;\n\nexport interface FeedQuery {\n page: number;\n pageSize: number;\n topicId?: string;\n}\n\nexport interface PostReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport type PageQuery = Pick<FeedQuery, 'page' | 'pageSize'>;\n\nexport interface PostThreadsQuery extends PageQuery {\n viewerId: string;\n seesHeld: boolean;\n}\n\nexport interface BookmarkQuery extends PageQuery {\n visibleStatuses: PostStatus[];\n}\n\nexport const MODERATION_QUEUE_LIMIT = 100;\n\nexport interface SpaceModerationQueue {\n pending: PostView[];\n flags: PostFlag[];\n commentFlags: FlaggedCommentView[];\n}\n\nexport interface PostCommentReader {\n listOpenFlagsForSpace(orgId: string, spaceId: string, limit: number): Promise<FlaggedComment[]>;\n publishedCommentCounts(orgId: string, postIds: string[]): Promise<Record<string, number>>;\n}\n\nexport interface PublishedPost {\n postId: string;\n spaceId: string;\n title: string;\n orgUserId: string;\n commentCount: number;\n}\n\nexport interface PostService {\n visibleTitles(postIds: string[], actor: Actor): Promise<Record<string, string>>;\n publishedBetween(\n orgId: string,\n communityId: string,\n from: Date,\n to: Date,\n ): Promise<PublishedPost[]>;\n create(input: CreatePostInput, actor: Actor): Promise<PostView>;\n update(postId: string, patch: UpdatePostInput, actor: Actor): Promise<PostView>;\n submit(postId: string, actor: Actor): Promise<PostView>;\n schedule(postId: string, scheduledAt: Date, actor: Actor): Promise<PostView>;\n unschedule(postId: string, actor: Actor): Promise<PostView>;\n publishScheduled(orgId: string, postId: string, now: Date): Promise<void>;\n approve(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;\n remove(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;\n restore(postId: string, communityId: string | null, actor: Actor): Promise<PostView>;\n pin(postId: string, actor: Actor): Promise<PostView>;\n unpin(postId: string, actor: Actor): Promise<PostView>;\n closeComments(postId: string, actor: Actor): Promise<PostView>;\n openComments(postId: string, actor: Actor): Promise<PostView>;\n setReactions(postId: string, enabled: boolean, actor: Actor): Promise<PostView>;\n setReaction(postId: string, emoji: ReactionEmoji | null, actor: Actor): Promise<PostReactions>;\n bookmark(postId: string, actor: Actor): Promise<void>;\n unbookmark(postId: string, actor: Actor): Promise<void>;\n flag(postId: string, reason: string, actor: Actor): Promise<PostFlag>;\n resolveFlags(\n postId: string,\n resolution: Extract<PostFlagResolution, 'dismissed'>,\n communityId: string | null,\n actor: Actor,\n ): Promise<void>;\n spaceFeed(\n communityId: string,\n spaceId: string,\n query: FeedQuery,\n actor: Actor,\n ): Promise<Page<PostView>>;\n memberFeed(\n communityId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n actor: Actor,\n ): Promise<Page<PostView>>;\n listOwn(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;\n listBookmarked(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;\n /** Signed URL of the post's cover, for whoever can read the post. */\n getPost(postId: string, actor: Actor): Promise<PostView>;\n visible(postId: string, actor: Actor): Promise<{ post: Post; space: SpaceDetail }>;\n find(orgId: string, postId: string): Promise<Post | null>;\n moderationQueue(\n communityId: string,\n spaceId: string,\n actor: Actor,\n ): Promise<SpaceModerationQueue>;\n}\n\nexport interface PostGuard {\n status: PostStatus;\n scheduledAt?: Date;\n dueBy?: Date;\n}\n\nexport interface PostRepository {\n insert(orgId: string, post: Post): Promise<Post>;\n update(orgId: string, id: string, patch: PostPatch): Promise<Post | null>;\n updateIf(orgId: string, id: string, guard: PostGuard, patch: PostPatch): Promise<Post | null>;\n find(orgId: string, id: string): Promise<Post | null>;\n findMany(orgId: string, ids: string[]): Promise<Post[]>;\n publishedBetween(\n orgId: string,\n communityId: string,\n from: Date,\n to: Date,\n ): Promise<PublishedPost[]>;\n listPublished(orgId: string, spaceIds: string[], query: FeedQuery): Promise<Page<Post>>;\n listByAuthor(\n orgId: string,\n orgUserId: string,\n spaceIds: string[],\n query: PageQuery,\n ): Promise<Page<Post>>;\n listBookmarked(\n orgId: string,\n orgUserId: string,\n spaceIds: string[],\n query: BookmarkQuery,\n ): Promise<Page<Post>>;\n listPending(orgId: string, spaceId: string, limit: number): Promise<Post[]>;\n reactionsOf(\n orgId: string,\n postIds: string[],\n viewerId: string,\n ): Promise<Record<string, PostReactions>>;\n setReaction(\n orgId: string,\n postId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<boolean>;\n bookmark(orgId: string, postId: string, orgUserId: string): Promise<void>;\n unbookmark(orgId: string, postId: string, orgUserId: string): Promise<void>;\n bookmarkedAmong(orgId: string, postIds: string[], orgUserId: string): Promise<string[]>;\n insertFlag(orgId: string, flag: PostFlag): Promise<PostFlag | null>;\n findFlag(orgId: string, postId: string, orgUserId: string): Promise<PostFlag | null>;\n listOpenFlags(orgId: string, spaceId: string, limit: number): Promise<PostFlag[]>;\n resolveFlags(\n orgId: string,\n postId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: PostFlagResolution },\n ): Promise<void>;\n}\n\nexport interface PostWriteScope {\n posts: PostRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type PostUnitOfWork = UnitOfWork<PostWriteScope>;\n\nexport interface DigestScheduleReader {\n communitiesWithDigest(): Promise<CommunityDigest[]>;\n}\n\nexport interface DigestRunRepository {\n listRuns(): Promise<DigestRun[]>;\n advance(orgId: string, communityId: string, dueAt: Date): Promise<boolean>;\n}\n\nexport interface DigestWriteScope {\n digests: DigestRunRepository;\n outbox: OutboxAppender;\n}\nexport type DigestUnitOfWork = UnitOfWork<DigestWriteScope>;\n\nexport interface DigestService {\n openDue(now: Date): Promise<void>;\n}\n\nexport interface StartDirectInput {\n communityId: string;\n orgUserId: string;\n body: string | null;\n assetIds: string[];\n}\n\nexport interface StartGroupInput {\n communityId: string;\n orgUserIds: string[];\n}\n\nexport interface SendMessageInput {\n body: string;\n replyToId: string | null;\n assetIds: string[];\n clientId?: string;\n}\n\nexport interface ChatListItem {\n chat: Chat;\n participantIds: string[];\n participants: CommentAuthor[];\n lastReadMessageId: string | null;\n subscription: SubscriptionView | null;\n unread: number;\n lastMessage: ChatLastMessage | null;\n}\n\nexport type ChatInboxRow = Omit<ChatListItem, 'subscription' | 'participants'>;\n\n/** `requests`: true for only the pending requests to the member, false for\n * everything else, omitted for every chat. */\nexport interface ChatListQuery extends PageQuery {\n requests?: boolean;\n}\n\nexport interface ReactionChange {\n inserted: boolean;\n changed: boolean;\n}\n\nexport interface ChatMemberState {\n chatId: string;\n unread: number;\n lastReadMessageId: string | null;\n subscription: SubscriptionView | null;\n}\n\n/** A member's state in each chat they are in in one community, with their\n * unread total there. */\nexport interface ChatMemberStates {\n orgUserId: string;\n unread: ChatUnreadSummary;\n chats: ChatMemberState[];\n}\n\nexport interface LiveChat {\n chat: Chat;\n participantIds: string[];\n participants: CommentAuthor[];\n}\n\n/** What the chat hub reads for each change, as current state. */\nexport interface ChatLiveReads {\n activeParticipants(orgId: string, chatId: string): Promise<string[]>;\n messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]>;\n messageReactions(orgId: string, messageId: string): Promise<ReactionCounts>;\n /** The chat with its active participants and their profiles. */\n liveChat(orgId: string, chatId: string): Promise<LiveChat | null>;\n memberStates(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<ChatMemberStates[]>;\n}\n\nexport interface ChatUnread {\n count: number;\n latest: { messageId: string; senderOrgUserId: string; body: string } | null;\n}\n\nexport interface MessageReactions {\n reactions: ReactionCounts;\n viewerReaction?: ReactionEmoji;\n}\n\nexport interface ChatSummary {\n chat: Chat;\n participantIds: string[];\n}\n\nexport interface MessageQueueEntry {\n message: MessageView;\n flags: MessageFlag[];\n chat: Chat;\n participantIds: string[];\n}\n\nexport interface MessageQueue {\n entries: MessageQueueEntry[];\n}\n\nexport interface ChatService extends ChatLiveReads {\n startDirect(input: StartDirectInput, actor: Actor): Promise<Chat>;\n startGroup(input: StartGroupInput, actor: Actor): Promise<Chat>;\n addParticipant(chatId: string, orgUserId: string, actor: Actor): Promise<Chat>;\n leave(chatId: string, actor: Actor): Promise<Chat>;\n accept(chatId: string, actor: Actor): Promise<Chat>;\n send(chatId: string, input: SendMessageInput, actor: Actor): Promise<MessageView>;\n editMessage(messageId: string, body: string, actor: Actor): Promise<MessageView>;\n deleteMessage(messageId: string, actor: Actor): Promise<MessageView>;\n setReaction(\n messageId: string,\n emoji: ReactionEmoji | null,\n actor: Actor,\n ): Promise<MessageReactions>;\n markRead(chatId: string, messageId: string, actor: Actor): Promise<void>;\n block(communityId: string, orgUserId: string, actor: Actor): Promise<void>;\n unblock(communityId: string, orgUserId: string, actor: Actor): Promise<void>;\n flagMessage(messageId: string, reason: string, actor: Actor): Promise<MessageFlag>;\n resolveMessageFlags(\n messageId: string,\n resolution: Extract<MessageFlagResolution, 'dismissed'>,\n communityId: string | null,\n actor: Actor,\n ): Promise<void>;\n lock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat>;\n unlock(chatId: string, communityId: string | null, actor: Actor): Promise<Chat>;\n listChats(communityId: string, query: ChatListQuery, actor: Actor): Promise<Page<ChatListItem>>;\n /** One live stream of the member's chat events in the community. */\n watch(communityId: string, actor: Actor): Promise<AsyncIterable<ChatLiveEvent>>;\n chat(chatId: string, actor: Actor): Promise<ChatListItem>;\n directChat(communityId: string, orgUserId: string, actor: Actor): Promise<ChatListItem>;\n unreadSummary(communityId: string, actor: Actor): Promise<ChatUnreadSummary>;\n typing(chatId: string, actor: Actor): Promise<void>;\n /** The messages as every participant sees them: a deleted message without\n * its body, attachments or mentions, and no viewer reaction. */\n messageViews(orgId: string, messageIds: string[]): Promise<LiveMessage[]>;\n chatSummary(\n orgId: string,\n chatId: string,\n ): Promise<{ chat: Chat; participants: ChatUser[] } | null>;\n latestMessageBody(orgId: string, chatId: string): Promise<string | null>;\n unreadInChat(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;\n findMessage(orgId: string, messageId: string): Promise<Message | null>;\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;\n titlesFor(chatIds: string[], actor: Actor): Promise<Record<string, string>>;\n messages(\n chatId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n actor: Actor,\n ): Promise<Page<MessageView>>;\n messageQueue(communityId: string, actor: Actor): Promise<MessageQueue>;\n}\n\nexport interface ChatRepository {\n lockDirectPair(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherOrgUserId: string,\n ): Promise<void>;\n findDirect(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherOrgUserId: string,\n ): Promise<Chat | null>;\n insertChat(orgId: string, chat: Chat): Promise<Chat>;\n updateChat(\n orgId: string,\n id: string,\n patch: Partial<Pick<Chat, 'status' | 'lockedAt' | 'lockedBy'>>,\n ): Promise<Chat | null>;\n findChat(orgId: string, id: string): Promise<Chat | null>;\n listForUser(\n orgId: string,\n communityId: string,\n orgUserId: string,\n query: ChatListQuery,\n ): Promise<Page<ChatInboxRow>>;\n /** The member's inbox row for one chat they are in, or null. */\n inboxRow(orgId: string, chatId: string, orgUserId: string): Promise<ChatInboxRow | null>;\n participants(orgId: string, chatId: string): Promise<ChatUser[]>;\n activeParticipants(orgId: string, chatId: string): Promise<string[]>;\n join(orgId: string, user: ChatUser): Promise<void>;\n markLeft(orgId: string, chatId: string, orgUserId: string, leftAt: Date): Promise<void>;\n /** Moves the read mark to the message only when it is newer (by created\n * time, then id) than the current one. True when it moved. */\n setRead(orgId: string, chatId: string, orgUserId: string, messageId: string): Promise<boolean>;\n unreadFor(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;\n /** Unread messages across the member's chats in the community that are\n * neither muted at `at` nor pending requests to them, plus those requests. */\n unreadSummary(\n orgId: string,\n communityId: string,\n orgUserId: string,\n at: Date,\n ): Promise<ChatUnreadSummary>;\n memberStates(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<ChatMemberStates[]>;\n findByClientId(\n orgId: string,\n chatId: string,\n orgUserId: string,\n clientId: string,\n ): Promise<Message | null>;\n insertMessage(orgId: string, message: Message): Promise<Message>;\n updateMessage(\n orgId: string,\n id: string,\n patch: Partial<Pick<Message, 'body' | 'editedAt' | 'deletedAt'>>,\n ): Promise<Message | null>;\n findMessage(orgId: string, id: string): Promise<Message | null>;\n listMessages(\n orgId: string,\n chatId: string,\n query: Pick<FeedQuery, 'page' | 'pageSize'>,\n ): Promise<Page<Message>>;\n attach(orgId: string, communityId: string, messageId: string, assetIds: string[]): Promise<void>;\n attachmentsOf(orgId: string, messageIds: string[]): Promise<Record<string, string[]>>;\n blockedWith(\n orgId: string,\n communityId: string,\n orgUserId: string,\n otherIds: string[],\n ): Promise<string[]>;\n setReaction(\n orgId: string,\n communityId: string,\n messageId: string,\n orgUserId: string,\n emoji: ReactionEmoji | null,\n ): Promise<ReactionChange>;\n reactionsOf(\n orgId: string,\n messageIds: string[],\n viewerId: string,\n ): Promise<Record<string, MessageReactions>>;\n block(\n orgId: string,\n communityId: string,\n blockerOrgUserId: string,\n blockedOrgUserId: string,\n ): Promise<void>;\n unblock(\n orgId: string,\n communityId: string,\n blockerOrgUserId: string,\n blockedOrgUserId: string,\n ): Promise<void>;\n isBlocked(orgId: string, communityId: string, a: string, b: string): Promise<boolean>;\n anyBlockedAmong(orgId: string, communityId: string, orgUserIds: string[]): Promise<boolean>;\n insertFlag(orgId: string, flag: MessageFlag): Promise<MessageFlag | null>;\n findFlag(orgId: string, messageId: string, orgUserId: string): Promise<MessageFlag | null>;\n listOpenFlags(orgId: string, communityId: string, limit?: number): Promise<MessageFlag[]>;\n messagesByIds(orgId: string, ids: string[]): Promise<Message[]>;\n chatSummaries(orgId: string, chatIds: string[]): Promise<ChatSummary[]>;\n resolveFlags(\n orgId: string,\n messageId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: MessageFlagResolution },\n ): Promise<void>;\n resolveOpenFlagsForChat(\n orgId: string,\n chatId: string,\n resolved: { resolvedAt: Date; resolvedBy: string; resolution: MessageFlagResolution },\n ): Promise<void>;\n}\n\nexport interface ChatWriteScope {\n chats: ChatRepository;\n mentions: MentionsRepository;\n outbox: OutboxAppender;\n}\nexport type ChatUnitOfWork = UnitOfWork<ChatWriteScope>;\n\nexport type SubscriptionInput =\n { state: 'following' } | { state: 'muted'; mutedUntil?: Date | null } | { state: 'none' };\n\nexport type MuteInput = Exclude<SubscriptionInput, { state: 'following' }>;\n\nexport interface SubscriptionWriteScope {\n subscriptions: SubscriptionsRepository;\n outbox: OutboxAppender;\n}\nexport type SubscriptionUnitOfWork = UnitOfWork<SubscriptionWriteScope>;\n\nexport interface SubscriptionService {\n setPost(postId: string, input: SubscriptionInput, actor: Actor): Promise<void>;\n setSpace(spaceId: string, input: MuteInput, actor: Actor): Promise<void>;\n setChat(chatId: string, input: MuteInput, actor: Actor): Promise<void>;\n viewerSubscriptions(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemIds: string[],\n at: Date,\n ): Promise<Record<string, SubscriptionView>>;\n followerIds(orgId: string, postId: string): Promise<string[]>;\n mutedAmong(orgId: string, item: SubjectRef, orgUserIds: string[], at: Date): Promise<string[]>;\n itemSubscriptions(orgId: string, item: SubjectRef): Promise<Subscription[]>;\n}\n\nexport interface SubscriptionsRepository {\n upsert(orgId: string, subscription: Subscription): Promise<void>;\n remove(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemId: string,\n ): Promise<void>;\n forViewer(\n orgId: string,\n orgUserId: string,\n itemType: SubscriptionItemType,\n itemIds: string[],\n ): Promise<Subscription[]>;\n ofItem(orgId: string, itemType: SubscriptionItemType, itemId: string): Promise<Subscription[]>;\n followerIds(orgId: string, postId: string): Promise<string[]>;\n mutedAmong(\n orgId: string,\n itemType: SubscriptionItemType,\n itemId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<string[]>;\n}\n\nexport interface MentionsRepository {\n ofItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<string[]>;\n forItems(\n orgId: string,\n itemType: MentionItemType,\n itemIds: string[],\n ): Promise<Record<string, string[]>>;\n sync(\n orgId: string,\n itemType: MentionItemType,\n itemId: string,\n orgUserIds: string[],\n at: Date,\n ): Promise<string[]>;\n removeItem(orgId: string, itemType: MentionItemType, itemId: string): Promise<void>;\n}\n"],"mappings":";AAuTA,MAAa,yBAAyB"}
|
|
@@ -21,7 +21,7 @@ type PostServiceParams = {
|
|
|
21
21
|
comments: PostCommentReader;
|
|
22
22
|
subscriptions: SubscriptionsRepository;
|
|
23
23
|
mentions: MentionsRepository;
|
|
24
|
-
images: MemberImageReader & MemberUploadReader & Pick<AssetFiles, '
|
|
24
|
+
images: MemberImageReader & MemberUploadReader & Pick<AssetFiles, 'fileLinks' | 'syncPublicImages'>;
|
|
25
25
|
fileUrls: FileUrls;
|
|
26
26
|
bodies: BodyParser;
|
|
27
27
|
uow: PostUnitOfWork;
|
|
@@ -73,9 +73,6 @@ declare class PostServiceImpl implements PostService {
|
|
|
73
73
|
private openSpaceIdsIn;
|
|
74
74
|
listOwn(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;
|
|
75
75
|
listBookmarked(communityId: string, query: PageQuery, actor: Actor): Promise<Page<PostView>>;
|
|
76
|
-
coverFile(postId: string, actor: Actor): Promise<string>;
|
|
77
|
-
assetFile(postId: string, assetId: string, actor: Actor): Promise<string>;
|
|
78
|
-
private signShown;
|
|
79
76
|
getPost(postId: string, actor: Actor): Promise<PostView>;
|
|
80
77
|
moderationQueue(communityId: string, spaceId: string, actor: Actor): Promise<SpaceModerationQueue>;
|
|
81
78
|
private flaggedCommentViews;
|