@headless-lms/core 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/events.d.ts +42 -24
- package/dist/assets/events.js +1 -1
- package/dist/assets/events.js.map +1 -1
- package/dist/assets/file-urls.d.ts +20 -0
- package/dist/assets/file-urls.js +25 -0
- package/dist/assets/file-urls.js.map +1 -0
- package/dist/assets/index.d.ts +6 -4
- package/dist/assets/index.js +4 -2
- package/dist/assets/jobs.d.ts +15 -0
- package/dist/assets/jobs.js +30 -0
- package/dist/assets/jobs.js.map +1 -0
- package/dist/assets/model.d.ts +2 -2
- package/dist/assets/ports.d.ts +46 -15
- package/dist/assets/service.d.ts +38 -7
- package/dist/assets/service.js +287 -38
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.d.ts +13 -24
- package/dist/automations/actions.js +106 -42
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.js +226 -3
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/community-derivations.d.ts +14 -0
- package/dist/automations/community-derivations.js +439 -0
- package/dist/automations/community-derivations.js.map +1 -0
- package/dist/automations/community-mail.d.ts +98 -0
- package/dist/automations/community-mail.js +70 -0
- package/dist/automations/community-mail.js.map +1 -0
- package/dist/automations/events.d.ts +18 -0
- package/dist/automations/index.d.ts +13 -4
- package/dist/automations/index.js +10 -2
- package/dist/automations/model.d.ts +10 -3
- package/dist/automations/model.js +15 -2
- package/dist/automations/model.js.map +1 -1
- package/dist/automations/notifications/debounce.d.ts +25 -0
- package/dist/automations/notifications/debounce.js +93 -0
- package/dist/automations/notifications/debounce.js.map +1 -0
- package/dist/automations/notifications/defaults.d.ts +8 -0
- package/dist/automations/notifications/defaults.js +316 -0
- package/dist/automations/notifications/defaults.js.map +1 -0
- package/dist/automations/notifications/delivery.d.ts +28 -0
- package/dist/automations/notifications/delivery.js +88 -0
- package/dist/automations/notifications/delivery.js.map +1 -0
- package/dist/automations/notifications/processor.d.ts +64 -0
- package/dist/automations/notifications/processor.js +270 -0
- package/dist/automations/notifications/processor.js.map +1 -0
- package/dist/automations/notifications/rules/content.d.ts +6 -0
- package/dist/automations/notifications/rules/content.js +39 -0
- package/dist/automations/notifications/rules/content.js.map +1 -0
- package/dist/automations/notifications/rules/discussion.d.ts +7 -0
- package/dist/automations/notifications/rules/discussion.js +678 -0
- package/dist/automations/notifications/rules/discussion.js.map +1 -0
- package/dist/automations/notifications/rules/entitlements.d.ts +6 -0
- package/dist/automations/notifications/rules/entitlements.js +249 -0
- package/dist/automations/notifications/rules/entitlements.js.map +1 -0
- package/dist/automations/notifications/rules/index.d.ts +6 -0
- package/dist/automations/notifications/rules/index.js +15 -0
- package/dist/automations/notifications/rules/index.js.map +1 -0
- package/dist/automations/notifications/rules/organizations.d.ts +6 -0
- package/dist/automations/notifications/rules/organizations.js +28 -0
- package/dist/automations/notifications/rules/organizations.js.map +1 -0
- package/dist/automations/notifications/rules/recipients.d.ts +32 -0
- package/dist/automations/notifications/rules/recipients.js +99 -0
- package/dist/automations/notifications/rules/recipients.js.map +1 -0
- package/dist/automations/notifications/service.d.ts +27 -0
- package/dist/automations/notifications/service.js +45 -0
- package/dist/automations/notifications/service.js.map +1 -0
- package/dist/automations/notifications/types.d.ts +7 -0
- package/dist/automations/notifications/types.js +473 -0
- package/dist/automations/notifications/types.js.map +1 -0
- package/dist/automations/ports.d.ts +121 -3
- package/dist/automations/schedule.d.ts +6 -0
- package/dist/automations/schedule.js +41 -0
- package/dist/automations/schedule.js.map +1 -0
- package/dist/automations/service.d.ts +20 -4
- package/dist/automations/service.js +246 -45
- package/dist/automations/service.js.map +1 -1
- package/dist/content/events.d.ts +712 -50
- package/dist/content/events.js +55 -1
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +6 -4
- package/dist/content/index.js +3 -1
- package/dist/content/model.d.ts +2 -2
- package/dist/content/notification-categories.d.ts +36 -0
- package/dist/content/notification-categories.js +64 -0
- package/dist/content/notification-categories.js.map +1 -0
- package/dist/content/ports.d.ts +90 -18
- package/dist/content/service.d.ts +57 -18
- package/dist/content/service.js +520 -25
- package/dist/content/service.js.map +1 -1
- package/dist/content/titles.d.ts +22 -0
- package/dist/content/titles.js +69 -0
- package/dist/content/titles.js.map +1 -0
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/authors.d.ts +10 -0
- package/dist/discussion/authors.js +23 -0
- package/dist/discussion/authors.js.map +1 -0
- package/dist/discussion/chats.d.ts +109 -0
- package/dist/discussion/chats.js +831 -0
- package/dist/discussion/chats.js.map +1 -0
- package/dist/discussion/digests.d.ts +24 -0
- package/dist/discussion/digests.js +104 -0
- package/dist/discussion/digests.js.map +1 -0
- package/dist/discussion/events.d.ts +785 -56
- package/dist/discussion/events.js +165 -5
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/index.d.ts +12 -3
- package/dist/discussion/index.js +9 -1
- package/dist/discussion/jobs.d.ts +16 -0
- package/dist/discussion/jobs.js +40 -0
- package/dist/discussion/jobs.js.map +1 -0
- package/dist/discussion/mentions.d.ts +11 -0
- package/dist/discussion/mentions.js +35 -0
- package/dist/discussion/mentions.js.map +1 -0
- package/dist/discussion/ports.d.ts +372 -28
- package/dist/discussion/ports.js +6 -1
- package/dist/discussion/ports.js.map +1 -0
- package/dist/discussion/posts.d.ts +113 -0
- package/dist/discussion/posts.js +775 -0
- package/dist/discussion/posts.js.map +1 -0
- package/dist/discussion/service.d.ts +42 -11
- package/dist/discussion/service.js +364 -85
- package/dist/discussion/service.js.map +1 -1
- package/dist/discussion/subscriptions.d.ts +37 -0
- package/dist/discussion/subscriptions.js +99 -0
- package/dist/discussion/subscriptions.js.map +1 -0
- package/dist/discussion/titles.d.ts +26 -0
- package/dist/discussion/titles.js +84 -0
- package/dist/discussion/titles.js.map +1 -0
- package/dist/entitlements/events.d.ts +178 -11
- package/dist/entitlements/events.js +28 -1
- package/dist/entitlements/events.js.map +1 -1
- package/dist/entitlements/index.d.ts +6 -4
- package/dist/entitlements/index.js +3 -1
- package/dist/entitlements/jobs.d.ts +12 -0
- package/dist/entitlements/jobs.js +18 -0
- package/dist/entitlements/jobs.js.map +1 -0
- package/dist/entitlements/model.d.ts +3 -2
- package/dist/entitlements/ports.d.ts +128 -11
- package/dist/entitlements/rules.d.ts +12 -0
- package/dist/entitlements/rules.js +17 -0
- package/dist/entitlements/rules.js.map +1 -0
- package/dist/entitlements/service.d.ts +57 -3
- package/dist/entitlements/service.js +492 -2
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/access.d.ts +6 -0
- package/dist/identity/access.js +7 -0
- package/dist/identity/access.js.map +1 -0
- package/dist/identity/devices.d.ts +30 -0
- package/dist/identity/devices.js +107 -0
- package/dist/identity/devices.js.map +1 -0
- package/dist/identity/events.d.ts +2 -2
- package/dist/identity/index.d.ts +7 -4
- package/dist/identity/index.js +4 -1
- package/dist/identity/model.d.ts +2 -1
- package/dist/identity/notification-settings.d.ts +28 -0
- package/dist/identity/notification-settings.js +113 -0
- package/dist/identity/notification-settings.js.map +1 -0
- package/dist/identity/ports.d.ts +97 -3
- package/dist/identity/service.d.ts +14 -3
- package/dist/identity/service.js +62 -5
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/events.d.ts +3 -3
- package/dist/integrations/model.js +1 -1
- package/dist/integrations/model.js.map +1 -1
- package/dist/integrations/service.js +2 -2
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/access-scopes.d.ts +19 -0
- package/dist/organizations/access-scopes.js +89 -0
- package/dist/organizations/access-scopes.js.map +1 -0
- package/dist/organizations/events.d.ts +75 -33
- package/dist/organizations/events.js +6 -0
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +8 -6
- package/dist/organizations/index.js +5 -3
- package/dist/organizations/members.d.ts +8 -5
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/notification-categories.d.ts +32 -0
- package/dist/organizations/notification-categories.js +51 -0
- package/dist/organizations/notification-categories.js.map +1 -0
- package/dist/organizations/ports.d.ts +17 -11
- package/dist/organizations/roles.d.ts +6 -10
- package/dist/organizations/roles.js +19 -7
- package/dist/organizations/roles.js.map +1 -1
- package/dist/organizations/service.d.ts +22 -11
- package/dist/organizations/service.js +164 -47
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/types.d.ts +1 -10
- package/dist/progress/events.d.ts +14 -14
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/ports.d.ts +6 -1
- package/dist/progress/service.d.ts +3 -1
- package/dist/progress/service.js +6 -2
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/admin/index.d.ts +4 -0
- package/dist/reporting/admin/index.js +2 -0
- package/dist/reporting/admin/model.d.ts +108 -0
- package/dist/reporting/admin/ports.d.ts +27 -0
- package/dist/reporting/admin/service.d.ts +22 -0
- package/dist/reporting/admin/service.js +38 -0
- package/dist/reporting/admin/service.js.map +1 -0
- package/dist/reporting/member/index.d.ts +5 -0
- package/dist/reporting/member/index.js +2 -0
- package/dist/reporting/member/model.d.ts +157 -0
- package/dist/reporting/member/ports.d.ts +48 -0
- package/dist/reporting/member/service.d.ts +92 -0
- package/dist/reporting/member/service.js +364 -0
- package/dist/reporting/member/service.js.map +1 -0
- package/dist/shared/actor.d.ts +9 -0
- package/dist/shared/errors.d.ts +19 -1
- package/dist/shared/errors.js +27 -1
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/id.d.ts +14 -0
- package/dist/shared/id.js +15 -1
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/mailer.d.ts +15 -2
- package/dist/shared/mailer.js +32 -5
- package/dist/shared/mailer.js.map +1 -1
- package/dist/shared/network.d.ts +10 -0
- package/dist/shared/network.js +211 -0
- package/dist/shared/network.js.map +1 -0
- package/dist/shared/notification-categories.d.ts +16 -0
- package/dist/shared/notification-categories.js +94 -0
- package/dist/shared/notification-categories.js.map +1 -0
- package/dist/shared/notification-settings.d.ts +14 -0
- package/dist/shared/notification-settings.js +61 -0
- package/dist/shared/notification-settings.js.map +1 -0
- package/dist/shared/ports.d.ts +48 -2
- package/dist/shared/settings.d.ts +2 -1
- package/dist/shared/settings.js +1 -0
- package/dist/shared/settings.js.map +1 -1
- package/dist/types/assets.d.ts +2 -2
- package/dist/types/automations.d.ts +15 -1
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +11 -3
- package/dist/types/email-templates.d.ts +216 -1
- package/dist/types/entitlements.d.ts +2 -2
- package/dist/types/identity.d.ts +48 -2
- package/dist/types/index.d.ts +9 -8
- package/dist/types/notifications.d.ts +157 -0
- package/dist/types/ports.d.ts +6 -0
- package/dist/types/schemas/assets.d.ts +65 -37
- package/dist/types/schemas/assets.js +17 -12
- package/dist/types/schemas/assets.js.map +1 -1
- package/dist/types/schemas/automations.d.ts +9 -1
- package/dist/types/schemas/automations.js +13 -6
- package/dist/types/schemas/automations.js.map +1 -1
- package/dist/types/schemas/content.d.ts +396 -3
- package/dist/types/schemas/content.js +161 -3
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +443 -3
- package/dist/types/schemas/discussion.js +233 -4
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +36 -2
- package/dist/types/schemas/entitlements.js +23 -1
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/identity.d.ts +27 -1
- package/dist/types/schemas/identity.js +14 -2
- package/dist/types/schemas/identity.js.map +1 -1
- package/dist/types/schemas/index.d.ts +9 -8
- package/dist/types/schemas/index.js +9 -8
- package/dist/types/schemas/notifications.d.ts +95 -0
- package/dist/types/schemas/notifications.js +56 -0
- package/dist/types/schemas/notifications.js.map +1 -0
- package/dist/types/schemas/organizations.d.ts +8 -0
- package/dist/types/schemas/organizations.js +2 -0
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +3 -3
- package/dist/types/schemas/shared.d.ts +3 -1
- package/dist/types/schemas/shared.js +5 -1
- package/dist/types/schemas/shared.js.map +1 -1
- package/package.json +2 -2
- package/dist/reporting/courses/index.d.ts +0 -4
- package/dist/reporting/courses/index.js +0 -2
- package/dist/reporting/courses/model.d.ts +0 -24
- package/dist/reporting/courses/ports.d.ts +0 -13
- package/dist/reporting/courses/service.d.ts +0 -19
- package/dist/reporting/courses/service.js +0 -20
- package/dist/reporting/courses/service.js.map +0 -1
- package/dist/reporting/dashboard/index.d.ts +0 -4
- package/dist/reporting/dashboard/index.js +0 -2
- package/dist/reporting/dashboard/model.d.ts +0 -14
- package/dist/reporting/dashboard/ports.d.ts +0 -13
- package/dist/reporting/dashboard/service.d.ts +0 -19
- package/dist/reporting/dashboard/service.js +0 -20
- package/dist/reporting/dashboard/service.js.map +0 -1
- package/dist/reporting/learn/index.d.ts +0 -5
- package/dist/reporting/learn/index.js +0 -2
- package/dist/reporting/learn/model.d.ts +0 -23
- package/dist/reporting/learn/ports.d.ts +0 -43
- package/dist/reporting/learn/service.d.ts +0 -49
- package/dist/reporting/learn/service.js +0 -116
- package/dist/reporting/learn/service.js.map +0 -1
- package/dist/reporting/students/index.d.ts +0 -4
- package/dist/reporting/students/index.js +0 -2
- package/dist/reporting/students/model.d.ts +0 -51
- package/dist/reporting/students/model.js +0 -1
- package/dist/reporting/students/ports.d.ts +0 -15
- package/dist/reporting/students/ports.js +0 -1
- package/dist/reporting/students/service.d.ts +0 -20
- package/dist/reporting/students/service.js +0 -23
- package/dist/reporting/students/service.js.map +0 -1
- /package/dist/reporting/{courses → admin}/model.js +0 -0
- /package/dist/reporting/{courses → admin}/ports.js +0 -0
- /package/dist/reporting/{dashboard → member}/model.js +0 -0
- /package/dist/reporting/{dashboard → member}/ports.js +0 -0
- /package/dist/{reporting/learn/model.js → shared/actor.js} +0 -0
- /package/dist/{reporting/learn/ports.js → types/notifications.js} +0 -0
|
@@ -0,0 +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} from '../shared/errors.js';\nimport type { Logger } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { isStaffRole } from '../organizations/index.js';\nimport type { Role } from '../organizations/index.js';\nimport type { CommunityReader, DmSettings } from '../content/index.js';\nimport type { CommunityAccessReader } from '../entitlements/index.js';\nimport type { AssetFiles, FileUrls, MemberImageReader } from '../assets/index.js';\nimport type {\n Chat,\n ChatUser,\n Message,\n MessageFlag,\n MessageFlagResolution,\n MessageView,\n Page,\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 { authorView } from './authors.js';\nimport { activeSubscription } from './subscriptions.js';\nimport { keepAllowed, mentionTokens, mentionViews } from './mentions.js';\nimport type {\n AuthorProfileReader,\n AuthorRoleReader,\n ChatListItem,\n ChatRepository,\n ChatService,\n ChatUnread,\n ChatUnitOfWork,\n MentionsRepository,\n MessageQueue,\n MessageQueueEntry,\n MessageReactions,\n PageQuery,\n SendMessageInput,\n StartDirectInput,\n StartGroupInput,\n SubscriptionsRepository,\n} from './ports.js';\nimport { MODERATION_QUEUE_LIMIT } from './ports.js';\n\nexport type ChatServiceParams = {\n repo: ChatRepository;\n access: CommunityAccessReader;\n content: CommunityReader;\n authors: AuthorRoleReader;\n profiles: AuthorProfileReader;\n subscriptions: SubscriptionsRepository;\n mentions: MentionsRepository;\n images: MemberImageReader & Pick<AssetFiles, 'signFile'>;\n fileUrls: FileUrls;\n uow: ChatUnitOfWork;\n logger?: Logger;\n};\n\nexport class ChatServiceImpl implements ChatService {\n private readonly repo: ChatRepository;\n private readonly access: CommunityAccessReader;\n private readonly content: CommunityReader;\n private readonly authors: AuthorRoleReader;\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 logger: Logger;\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.logger = params.logger ?? noopLogger;\n }\n\n async startDirect(orgId: string, actor: Actor, input: StartDirectInput): Promise<Chat> {\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(orgId, actor, input.communityId);\n const role = await this.requirePerson(orgId, input.communityId, input.orgUserId);\n this.assertMayMessage(settings, actor, role);\n const first: SendMessageInput | null =\n input.body === null ? null : { body: input.body, replyToId: null, assetIds: input.assetIds };\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);\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 await this.requireOwnedImages(orgId, actor.id, input.assetIds);\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 =\n settings.firstMessageIsRequest && !isStaffRole(actor.role) && !isStaffRole(role);\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(orgId: string, actor: Actor, input: StartGroupInput): Promise<Chat> {\n const settings = await this.requireSettings(orgId, input.communityId);\n this.assertGroup(settings);\n await this.requireActor(orgId, 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 for (const orgUserId of others) {\n const role = await this.requirePerson(orgId, input.communityId, orgUserId);\n this.assertMayMessage(settings, actor, role);\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(\n orgId: string,\n actor: Actor,\n chatId: string,\n orgUserId: string,\n ): Promise<Chat> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, 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 role = await this.requirePerson(orgId, chat.communityId, orgUserId);\n this.assertMayMessage(settings, actor, role);\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(orgId: string, actor: Actor, chatId: string): Promise<Chat> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, 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(orgId: string, actor: Actor, chatId: string): Promise<Chat> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, 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(\n orgId: string,\n actor: Actor,\n chatId: string,\n input: SendMessageInput,\n ): Promise<MessageView> {\n const preview = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, 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 saved = await this.uow.run(async ({ chats, mentions, outbox }) => {\n const chat = await this.mustChat(chats, orgId, chatId);\n const users = await chats.participants(orgId, chatId);\n await this.assertMaySend(chats, orgId, actor, chat, users, settings, input);\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 written.message;\n });\n this.logger.info('message sent', { orgId, chatId, messageId: saved.id, actorId: actor.id });\n return this.viewOf(orgId, actor, saved);\n }\n\n async editMessage(\n orgId: string,\n actor: Actor,\n messageId: string,\n body: string,\n ): Promise<MessageView> {\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireWritableChat(orgId, 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(orgId, 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: { ...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(orgId, actor, saved);\n }\n\n async deleteMessage(orgId: string, actor: Actor, messageId: string): Promise<MessageView> {\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(orgId, actor, message.communityId);\n if (!isStaffRole(actor.role)) {\n if (message.orgUserId !== actor.id) {\n throw new ForbiddenError('You cannot delete this message');\n }\n await this.requireWritableChat(orgId, 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: 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(orgId, actor, saved);\n }\n\n async setReaction(\n orgId: string,\n actor: Actor,\n messageId: string,\n emoji: ReactionEmoji | null,\n ): Promise<MessageReactions> {\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(orgId, actor, message.communityId);\n await this.requireActive(this.repo, orgId, message.chatId, actor.id);\n await this.uow.run(async ({ chats, outbox }) => {\n const first = await chats.setReaction(orgId, message.communityId, messageId, actor.id, emoji);\n if (!first || emoji === null) {\n return;\n }\n await outbox.append([\n discussionEvents.messageReactionAdded.make({\n orgId,\n data: {\n messageId,\n chatId: message.chatId,\n communityId: message.communityId,\n orgUserId: actor.id,\n reaction: emoji,\n },\n metadata: this.meta(actor),\n }),\n ]);\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(orgId: string, actor: Actor, chatId: string, messageId: string): Promise<void> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, 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 await this.repo.setRead(orgId, chatId, actor.id, messageId);\n this.logger.debug('chat marked read', { orgId, chatId, messageId });\n }\n\n async block(orgId: string, actor: Actor, communityId: string, orgUserId: string): Promise<void> {\n if (actor.id === orgUserId) {\n throw new InvalidInputError('You cannot block yourself');\n }\n await this.requireSettings(orgId, communityId);\n await this.requireActor(orgId, 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(\n orgId: string,\n actor: Actor,\n communityId: string,\n orgUserId: string,\n ): Promise<void> {\n if (actor.id === orgUserId) {\n throw new InvalidInputError('You cannot block yourself');\n }\n await this.requireSettings(orgId, communityId);\n await this.requireActor(orgId, 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(\n orgId: string,\n actor: Actor,\n messageId: string,\n reason: string,\n ): Promise<MessageFlag> {\n const message = await this.mustMessage(this.repo, orgId, messageId);\n await this.requireActor(orgId, 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 orgId: string,\n actor: Actor,\n messageId: string,\n resolution: Extract<MessageFlagResolution, 'dismissed'>,\n communityId?: string,\n ): Promise<void> {\n if (!isStaffRole(actor.role)) {\n throw new ForbiddenError('You cannot work this queue');\n }\n const message = await this.mustMessage(this.repo, orgId, messageId);\n if (communityId !== undefined && message.communityId !== communityId) {\n throw new NotFoundError('Message', messageId);\n }\n await this.requireActor(orgId, 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(orgId: string, actor: Actor, chatId: string, communityId?: string): Promise<Chat> {\n return this.setLock(orgId, actor, chatId, true, communityId);\n }\n\n async unlock(orgId: string, actor: Actor, chatId: string, communityId?: string): Promise<Chat> {\n return this.setLock(orgId, actor, chatId, false, communityId);\n }\n\n async listChats(\n orgId: string,\n actor: Actor,\n communityId: string,\n query: PageQuery,\n ): Promise<Page<ChatListItem>> {\n await this.requireSettings(orgId, communityId);\n await this.requireActor(orgId, actor, communityId);\n const now = new Date();\n const page = await this.repo.listForUser(orgId, communityId, actor.id, query);\n const subscribed = await this.subscriptions.forViewer(\n orgId,\n actor.id,\n 'chat',\n page.rows.map((item) => item.chat.id),\n );\n const subscriptionOf = new Map(subscribed.map((row) => [row.itemId, row]));\n const items = page.rows.map((item) => ({\n ...item,\n subscription: activeSubscription(subscriptionOf.get(item.chat.id), now),\n lastMessage:\n item.lastMessage && item.lastMessage.deletedAt !== null && !isStaffRole(actor.role)\n ? { ...item.lastMessage, body: '' }\n : item.lastMessage,\n }));\n this.logger.debug('chats listed', { orgId, communityId, count: items.length });\n return { ...page, rows: items };\n }\n\n async messages(\n orgId: string,\n actor: Actor,\n chatId: string,\n query: { page: number; pageSize: number },\n ): Promise<Page<MessageView>> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, 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 orgId: string,\n actor: Actor,\n chatId: string,\n messageId: string,\n assetId: string,\n ): Promise<string | null> {\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 return null;\n }\n const attached = (await this.repo.attachmentsOf(orgId, [messageId]))[messageId] ?? [];\n if (!attached.includes(assetId)) {\n return null;\n }\n if (!(await this.access.isCommunityMember(orgId, actor, chat.communityId))) {\n return null;\n }\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 || isStaffRole(actor.role)\n : isStaffRole(actor.role) &&\n (await this.repo.listOpenFlags(orgId, chat.communityId)).some(\n (flag) => flag.messageId === messageId,\n );\n if (!readable) {\n return null;\n }\n return this.images.signFile(orgId, assetId);\n }\n\n async messageQueue(orgId: string, actor: Actor, communityId: string): Promise<MessageQueue> {\n if (!isStaffRole(actor.role)) {\n throw new ForbiddenError('You cannot work this queue');\n }\n await this.requireActor(orgId, 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(orgId: string, actor: Actor, chatIds: string[]): Promise<Record<string, string>> {\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 && authorView(orgId, record, null).displayName) || '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 orgId: string,\n actor: Actor,\n chatId: string,\n locked: boolean,\n communityId?: string,\n ): Promise<Chat> {\n if (!isStaffRole(actor.role)) {\n throw new ForbiddenError('You cannot lock this chat');\n }\n const chat = await this.mustChat(this.repo, orgId, chatId);\n if (communityId !== undefined && chat.communityId !== communityId) {\n throw new NotFoundError('Chat', chatId);\n }\n await this.requireActor(orgId, 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 async requireSettings(orgId: string, communityId: string): Promise<DmSettings> {\n const settings = await this.content.getDmSettings(orgId, communityId);\n if (!settings) {\n throw new NotFoundError('Community', communityId);\n }\n return settings;\n }\n\n private async requireActor(orgId: string, actor: Actor, communityId: string): Promise<void> {\n if (!(await this.access.isCommunityMember(orgId, actor, communityId))) {\n throw new ForbiddenError('You are not a member of this community');\n }\n }\n\n private async requirePerson(\n orgId: string,\n communityId: string,\n orgUserId: string,\n ): Promise<Role> {\n const role = await this.personRole(orgId, communityId, orgUserId);\n if (!role) {\n throw new ForbiddenError('That person is not a member of this community');\n }\n return role;\n }\n\n private async personRole(\n orgId: string,\n communityId: string,\n orgUserId: string,\n ): Promise<Role | null> {\n const role = await this.authors.roleOf(orgId, orgUserId);\n if (!role) {\n return null;\n }\n const member = await this.access.isCommunityMember(orgId, { id: orgUserId, role }, communityId);\n return member ? role : null;\n }\n\n private async personRoles(\n orgId: string,\n communityId: string,\n orgUserIds: string[],\n ): Promise<Record<string, Role>> {\n const roles = await this.authors.rolesOf(orgId, orgUserIds);\n const members = new Set(\n await this.access.communityMembersAmong(orgId, communityId, Object.keys(roles)),\n );\n return Object.fromEntries(\n Object.entries(roles).filter(([orgUserId]) => members.has(orgUserId)),\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 ): 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.length > 0) {\n const roles = await this.personRoles(orgId, chat.communityId, others);\n const recipients = chat.kind === 'group' ? others.filter((other) => roles[other]) : others;\n for (const other of recipients) {\n const role = roles[other];\n if (!role || !this.mayMessage(settings, actor, role)) {\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 await this.requireOwnedImages(orgId, actor.id, input.assetIds);\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, actor: Actor, role: Role): void {\n if (!settings.enabled) {\n throw new ForbiddenError('Direct messages are off');\n }\n if (!this.mayMessage(settings, actor, role)) {\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, actor: Actor, target: Role): boolean {\n if (isStaffRole(actor.role)) {\n return settings.staffToAnyone;\n }\n if (isStaffRole(target)) {\n return settings.memberToStaff;\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(orgId: string, actor: Actor, chatId: string): Promise<void> {\n const chat = await this.mustChat(this.repo, orgId, chatId);\n await this.requireActor(orgId, 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 }\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 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 { actorOrgUserId: 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: { ...message, mentionedOrgUserIds },\n metadata: { ...this.meta(actor), chatStatus: chat.status },\n });\n }\n\n private async viewOf(orgId: string, actor: Actor, message: Message): Promise<MessageView> {\n const [view] = await this.withSenders(orgId, actor, [message]);\n return view!;\n }\n\n private async withSenders(\n orgId: string,\n actor: Actor,\n messages: Message[],\n ): Promise<MessageView[]> {\n if (messages.length === 0) {\n return [];\n }\n const messageIds = messages.map((message) => message.id);\n const [records, attached, reactions, mentioned] = await Promise.all([\n this.profiles.authorsOf(orgId, [...new Set(messages.map((message) => message.orgUserId))]),\n this.repo.attachmentsOf(orgId, messageIds),\n this.repo.reactionsOf(orgId, messageIds, actor.id),\n mentionViews(this.mentions, this.profiles, orgId, 'message', messageIds),\n ]);\n const shown = (message: Message) =>\n message.deletedAt !== null && !isStaffRole(actor.role) ? [] : (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 ...this.present(message, actor),\n sender: authorView(orgId, record, this.fileUrls),\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:\n message.deletedAt !== null && !isStaffRole(actor.role)\n ? []\n : (mentioned[message.id] ?? []),\n };\n });\n }\n\n private present(message: Message, actor: Actor): Message {\n if (message.deletedAt !== null && !isStaffRole(actor.role)) {\n return { ...message, body: '' };\n }\n return message;\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Logger } from "../types/ports.js";
|
|
2
|
+
import { DigestSchedule } from "../types/schemas/content.js";
|
|
3
|
+
import "../shared/ports.js";
|
|
4
|
+
import "../content/index.js";
|
|
5
|
+
import { DigestRunRepository, DigestScheduleReader, DigestService, DigestUnitOfWork } from "./ports.js";
|
|
6
|
+
//#region src/discussion/digests.d.ts
|
|
7
|
+
declare function latestDueAt(schedule: DigestSchedule, now: Date): Date;
|
|
8
|
+
type DigestServiceParams = {
|
|
9
|
+
schedules: DigestScheduleReader;
|
|
10
|
+
runs: Pick<DigestRunRepository, 'listRuns'>;
|
|
11
|
+
uow: DigestUnitOfWork;
|
|
12
|
+
logger?: Logger;
|
|
13
|
+
};
|
|
14
|
+
declare class DigestServiceImpl implements DigestService {
|
|
15
|
+
private readonly schedules;
|
|
16
|
+
private readonly runs;
|
|
17
|
+
private readonly uow;
|
|
18
|
+
private readonly logger;
|
|
19
|
+
constructor(params: DigestServiceParams);
|
|
20
|
+
openDue(now: Date): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { DigestServiceImpl, DigestServiceParams, latestDueAt };
|
|
24
|
+
//# sourceMappingURL=digests.d.ts.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { noopLogger } from "../shared/logger.js";
|
|
2
|
+
import { discussionEvents } from "./events.js";
|
|
3
|
+
//#region src/discussion/digests.ts
|
|
4
|
+
const HOUR_MS = 3600 * 1e3;
|
|
5
|
+
function wallTimeIn(at, timeZone) {
|
|
6
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
7
|
+
timeZone,
|
|
8
|
+
hourCycle: "h23",
|
|
9
|
+
year: "numeric",
|
|
10
|
+
month: "numeric",
|
|
11
|
+
day: "numeric",
|
|
12
|
+
hour: "numeric",
|
|
13
|
+
minute: "numeric",
|
|
14
|
+
second: "numeric"
|
|
15
|
+
}).formatToParts(at);
|
|
16
|
+
const part = (type) => Number(parts.find((entry) => entry.type === type)?.value);
|
|
17
|
+
return {
|
|
18
|
+
year: part("year"),
|
|
19
|
+
month: part("month"),
|
|
20
|
+
day: part("day"),
|
|
21
|
+
hour: part("hour"),
|
|
22
|
+
minute: part("minute"),
|
|
23
|
+
second: part("second")
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function offsetAt(at, timeZone) {
|
|
27
|
+
const wall = wallTimeIn(new Date(at), timeZone);
|
|
28
|
+
return Date.UTC(wall.year, wall.month - 1, wall.day, wall.hour, wall.minute, wall.second) - Math.floor(at / 1e3) * 1e3;
|
|
29
|
+
}
|
|
30
|
+
function instantOf(timeZone, year, month, day, hour) {
|
|
31
|
+
const wall = Date.UTC(year, month - 1, day, hour);
|
|
32
|
+
const guess = wall - offsetAt(wall, timeZone);
|
|
33
|
+
return new Date(wall - offsetAt(guess, timeZone));
|
|
34
|
+
}
|
|
35
|
+
function latestDueAt(schedule, now) {
|
|
36
|
+
const today = wallTimeIn(now, schedule.timeZone);
|
|
37
|
+
const weekday = new Date(Date.UTC(today.year, today.month - 1, today.day)).getUTCDay();
|
|
38
|
+
const weekly = schedule.cadence === "weekly";
|
|
39
|
+
const back = weekly ? (weekday - schedule.weekday + 7) % 7 : 0;
|
|
40
|
+
const slot = (daysBack) => instantOf(schedule.timeZone, today.year, today.month, today.day - daysBack, schedule.hour);
|
|
41
|
+
const candidate = slot(back);
|
|
42
|
+
return candidate.getTime() <= now.getTime() ? candidate : slot(back + (weekly ? 7 : 1));
|
|
43
|
+
}
|
|
44
|
+
function runKey(orgId, communityId) {
|
|
45
|
+
return `${orgId}\0${communityId}`;
|
|
46
|
+
}
|
|
47
|
+
var DigestServiceImpl = class {
|
|
48
|
+
schedules;
|
|
49
|
+
runs;
|
|
50
|
+
uow;
|
|
51
|
+
logger;
|
|
52
|
+
constructor(params) {
|
|
53
|
+
this.schedules = params.schedules;
|
|
54
|
+
this.runs = params.runs;
|
|
55
|
+
this.uow = params.uow;
|
|
56
|
+
this.logger = params.logger ?? noopLogger;
|
|
57
|
+
}
|
|
58
|
+
async openDue(now) {
|
|
59
|
+
const [communities, runs] = await Promise.all([this.schedules.communitiesWithDigest(), this.runs.listRuns()]);
|
|
60
|
+
const handled = new Map(runs.map((run) => [runKey(run.orgId, run.communityId), run.lastDueAt]));
|
|
61
|
+
let due = 0;
|
|
62
|
+
let moved = 0;
|
|
63
|
+
let failed = 0;
|
|
64
|
+
for (const { orgId, communityId, digest } of communities) {
|
|
65
|
+
const dueAt = latestDueAt(digest, now);
|
|
66
|
+
const previous = handled.get(runKey(orgId, communityId)) ?? null;
|
|
67
|
+
if (previous !== null && dueAt.getTime() <= previous.getTime()) continue;
|
|
68
|
+
const emits = previous !== null && now.getTime() - dueAt.getTime() <= HOUR_MS;
|
|
69
|
+
try {
|
|
70
|
+
const advanced = await this.uow.run(async ({ digests, outbox }) => {
|
|
71
|
+
if (!await digests.advance(orgId, communityId, dueAt)) return false;
|
|
72
|
+
if (emits && previous !== null) await outbox.append([discussionEvents.digestDue.make({
|
|
73
|
+
orgId,
|
|
74
|
+
data: {
|
|
75
|
+
communityId,
|
|
76
|
+
from: previous,
|
|
77
|
+
to: dueAt
|
|
78
|
+
}
|
|
79
|
+
})]);
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
if (advanced && emits) due += 1;
|
|
83
|
+
else if (advanced) moved += 1;
|
|
84
|
+
} catch (err) {
|
|
85
|
+
failed += 1;
|
|
86
|
+
this.logger.error("digest not opened", {
|
|
87
|
+
orgId,
|
|
88
|
+
communityId,
|
|
89
|
+
err
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (due > 0 || moved > 0) this.logger.info("digests opened", {
|
|
94
|
+
due,
|
|
95
|
+
moved,
|
|
96
|
+
failed
|
|
97
|
+
});
|
|
98
|
+
if (failed > 0) throw new Error(`${failed} community digests could not be opened`);
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
//#endregion
|
|
102
|
+
export { DigestServiceImpl, latestDueAt };
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=digests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digests.js","names":[],"sources":["../../src/discussion/digests.ts"],"sourcesContent":["import type { DigestSchedule } from '../content/index.js';\nimport type { Logger } from '../shared/ports.js';\nimport { noopLogger } from '../shared/logger.js';\nimport { discussionEvents } from './events.js';\nimport type {\n DigestRunRepository,\n DigestScheduleReader,\n DigestService,\n DigestUnitOfWork,\n} from './ports.js';\n\nconst HOUR_MS = 60 * 60 * 1000;\n\ninterface WallTime {\n year: number;\n month: number;\n day: number;\n hour: number;\n minute: number;\n second: number;\n}\n\nfunction wallTimeIn(at: Date, timeZone: string): WallTime {\n const parts = new Intl.DateTimeFormat('en-US', {\n timeZone,\n hourCycle: 'h23',\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric',\n }).formatToParts(at);\n const part = (type: Intl.DateTimeFormatPartTypes) =>\n Number(parts.find((entry) => entry.type === type)?.value);\n return {\n year: part('year'),\n month: part('month'),\n day: part('day'),\n hour: part('hour'),\n minute: part('minute'),\n second: part('second'),\n };\n}\n\nfunction offsetAt(at: number, timeZone: string): number {\n const wall = wallTimeIn(new Date(at), timeZone);\n const asUtc = Date.UTC(wall.year, wall.month - 1, wall.day, wall.hour, wall.minute, wall.second);\n return asUtc - Math.floor(at / 1000) * 1000;\n}\n\nfunction instantOf(timeZone: string, year: number, month: number, day: number, hour: number): Date {\n const wall = Date.UTC(year, month - 1, day, hour);\n const guess = wall - offsetAt(wall, timeZone);\n return new Date(wall - offsetAt(guess, timeZone));\n}\n\nexport function latestDueAt(schedule: DigestSchedule, now: Date): Date {\n const today = wallTimeIn(now, schedule.timeZone);\n const weekday = new Date(Date.UTC(today.year, today.month - 1, today.day)).getUTCDay();\n const weekly = schedule.cadence === 'weekly';\n const back = weekly ? (weekday - schedule.weekday + 7) % 7 : 0;\n const slot = (daysBack: number) =>\n instantOf(schedule.timeZone, today.year, today.month, today.day - daysBack, schedule.hour);\n const candidate = slot(back);\n return candidate.getTime() <= now.getTime() ? candidate : slot(back + (weekly ? 7 : 1));\n}\n\nfunction runKey(orgId: string, communityId: string): string {\n return `${orgId}\\0${communityId}`;\n}\n\nexport type DigestServiceParams = {\n schedules: DigestScheduleReader;\n runs: Pick<DigestRunRepository, 'listRuns'>;\n uow: DigestUnitOfWork;\n logger?: Logger;\n};\n\nexport class DigestServiceImpl implements DigestService {\n private readonly schedules: DigestScheduleReader;\n private readonly runs: Pick<DigestRunRepository, 'listRuns'>;\n private readonly uow: DigestUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: DigestServiceParams) {\n this.schedules = params.schedules;\n this.runs = params.runs;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n async openDue(now: Date): Promise<void> {\n const [communities, runs] = await Promise.all([\n this.schedules.communitiesWithDigest(),\n this.runs.listRuns(),\n ]);\n const handled = new Map(runs.map((run) => [runKey(run.orgId, run.communityId), run.lastDueAt]));\n let due = 0;\n let moved = 0;\n let failed = 0;\n for (const { orgId, communityId, digest } of communities) {\n const dueAt = latestDueAt(digest, now);\n const previous = handled.get(runKey(orgId, communityId)) ?? null;\n if (previous !== null && dueAt.getTime() <= previous.getTime()) {\n continue;\n }\n const emits = previous !== null && now.getTime() - dueAt.getTime() <= HOUR_MS;\n try {\n const advanced = await this.uow.run(async ({ digests, outbox }) => {\n if (!(await digests.advance(orgId, communityId, dueAt))) {\n return false;\n }\n if (emits && previous !== null) {\n await outbox.append([\n discussionEvents.digestDue.make({\n orgId,\n data: { communityId, from: previous, to: dueAt },\n }),\n ]);\n }\n return true;\n });\n if (advanced && emits) {\n due += 1;\n } else if (advanced) {\n moved += 1;\n }\n } catch (err) {\n failed += 1;\n this.logger.error('digest not opened', { orgId, communityId, err });\n }\n }\n if (due > 0 || moved > 0) {\n this.logger.info('digests opened', { due, moved, failed });\n }\n if (failed > 0) {\n throw new Error(`${failed} community digests could not be opened`);\n }\n }\n}\n"],"mappings":"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"}
|