@headless-lms/server 0.5.0 → 0.6.1

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.
Files changed (44) hide show
  1. package/README.md +6 -0
  2. package/dist/app/container.d.ts +5 -1
  3. package/dist/app/container.js +3 -1
  4. package/dist/app/container.js.map +1 -1
  5. package/dist/http/mcp/tools/communities.js +1 -1
  6. package/dist/http/mcp/tools/communities.js.map +1 -1
  7. package/dist/http/routes/access.js +1 -1
  8. package/dist/http/routes/access.js.map +1 -1
  9. package/dist/http/routes/communities.js +1 -1
  10. package/dist/http/routes/communities.js.map +1 -1
  11. package/dist/http/routes/member/files.js +18 -1
  12. package/dist/http/routes/member/files.js.map +1 -1
  13. package/dist/http/routes/platform.js +1 -1
  14. package/dist/http/routes/platform.js.map +1 -1
  15. package/dist/http/schemas/access.d.ts +6 -6
  16. package/dist/http/schemas/assets.d.ts +6 -6
  17. package/dist/http/schemas/chats.d.ts +27 -27
  18. package/dist/http/schemas/community.d.ts +61 -34
  19. package/dist/http/schemas/community.js +6 -3
  20. package/dist/http/schemas/community.js.map +1 -1
  21. package/dist/http/schemas/content.d.ts +5 -5
  22. package/dist/http/schemas/course-analytics.d.ts +2 -2
  23. package/dist/http/schemas/downloads.d.ts +5 -5
  24. package/dist/http/schemas/entitlements.d.ts +5 -5
  25. package/dist/http/schemas/events.d.ts +57 -57
  26. package/dist/http/schemas/files.d.ts +10 -1
  27. package/dist/http/schemas/files.js +6 -1
  28. package/dist/http/schemas/files.js.map +1 -1
  29. package/dist/http/schemas/index.d.ts +2 -2
  30. package/dist/http/schemas/index.js +2 -2
  31. package/dist/http/schemas/integrations.d.ts +4 -4
  32. package/dist/http/schemas/member.d.ts +3 -3
  33. package/dist/http/schemas/members.d.ts +22 -22
  34. package/dist/http/schemas/notifications.d.ts +20 -20
  35. package/dist/http/schemas/platform.d.ts +2 -1
  36. package/dist/http/schemas/platform.js +2 -2
  37. package/dist/http/schemas/platform.js.map +1 -1
  38. package/dist/http/schemas/posts.d.ts +6 -6
  39. package/dist/http/schemas/profile.d.ts +3 -3
  40. package/dist/http/schemas/students.d.ts +7 -7
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.js.map +1 -1
  43. package/dist/packages/server/package.js +1 -1
  44. package/package.json +5 -5
package/README.md CHANGED
@@ -32,6 +32,12 @@ and the hub before the relay. `buildServer` closes the hub in `preClose`, so ope
32
32
  streams receive their closing frame, and on close stops the relay, the engine and then
33
33
  `PubSub`.
34
34
 
35
+ The outbox relay publishes every committed event to the `EventBus`. Absent
36
+ `adapters.eventBus`, it is the same-process `InMemoryEventBus`. An installation
37
+ supplies its own (an instance, or a factory given its logger) to carry events over
38
+ its own transport; the container attaches the core subscribers to it and exposes it
39
+ as `container.eventBus`, which the entry point starts and stops when the bus needs it.
40
+
35
41
  Public surface (`src/index.ts`): `createContainer`, `buildServer`,
36
42
  `loadIntegrations`, the operational function `runMigrations` (re-exported from
37
43
  `@headless-lms/adapter-db`, which ships the `drizzle/` migration assets;
@@ -18,7 +18,7 @@ import { AdminReportServiceImpl } from "@headless-lms/core/reporting/admin";
18
18
  import { MemberReportServiceImpl } from "@headless-lms/core/reporting/member";
19
19
  import { SettingsService } from "@headless-lms/core/shared/settings";
20
20
  import { AutomationEngine, PushConfig, WebhookSender } from "@headless-lms/core/types";
21
- import { BodyParser, ChannelRegistry, CredentialStore, EmailSender, Logger, NotificationChannel, NotificationTypeRegistry, ObjectStorage, OutboxRelay, PubSub, PushSender, Scheduler, TemplateContext, TemplateRenderer } from "@headless-lms/core/shared/ports";
21
+ import { BodyParser, ChannelRegistry, CredentialStore, EmailSender, EventBus, Logger, NotificationChannel, NotificationTypeRegistry, ObjectStorage, OutboxRelay, PubSub, PushSender, Scheduler, TemplateContext, TemplateRenderer } from "@headless-lms/core/shared/ports";
22
22
  //#region src/app/container.d.ts
23
23
  /** Installation-supplied ports; an absent slot falls back to a fail-loudly stub. */
24
24
  interface AdapterOverrides {
@@ -39,6 +39,8 @@ interface AdapterOverrides {
39
39
  bodyParser?: BodyParser;
40
40
  /** Delivery to every API instance, or a factory given its logger. Absent → no live chat; the chat stream answers 503. */
41
41
  pubsub?: PubSub | ((logger: Logger) => PubSub);
42
+ /** Delivers outbox events to their subscribers, or a factory given its logger. Absent → InMemoryEventBus (same process). */
43
+ eventBus?: EventBus | ((logger: Logger) => EventBus);
42
44
  }
43
45
  interface BuildContainerOptions {
44
46
  /** Installation's plugins folder, scanned by loadIntegrations. Absent → no integrations. */
@@ -143,6 +145,8 @@ interface Container {
143
145
  * installation's entry point starts it after listen (gen-openapi must not
144
146
  * poll). buildServer stops it onClose. */
145
147
  outboxRelay: OutboxRelay;
148
+ /** The event bus the outbox relay publishes to, with the core subscribers attached. */
149
+ eventBus: EventBus;
146
150
  /** The automation engine — constructed but NEVER started by the container;
147
151
  * the installation's entry point starts it after listen (gen-openapi must
148
152
  * not run a worker). buildServer stops it onClose. */
@@ -504,7 +504,8 @@ async function buildContainer(config, options) {
504
504
  ...entitlementsRecurringJobs,
505
505
  ...assetsRecurringJobs
506
506
  ]);
507
- const eventBus = new InMemoryEventBus();
507
+ const eventBusOption = options?.adapters?.eventBus;
508
+ const eventBus = typeof eventBusOption === "function" ? eventBusOption(logger.child({ name: "events" })) : eventBusOption ?? new InMemoryEventBus();
508
509
  if (pubsub) new ChatLiveForwarder({
509
510
  pubsub,
510
511
  logger: discussionLogger
@@ -640,6 +641,7 @@ async function buildContainer(config, options) {
640
641
  mailer,
641
642
  credentials: credentialStore,
642
643
  outboxRelay,
644
+ eventBus,
643
645
  automationEngine,
644
646
  scheduler: automationEngine,
645
647
  pubsub,
@@ -1 +1 @@
1
- {"version":3,"file":"container.js","names":[],"sources":["../../src/app/container.ts"],"sourcesContent":["// Wires adapters + services in dependency order. Starts nothing.\nimport {\n createDb,\n type DatabasePoolOptions,\n DrizzleAssetsRepository,\n DrizzleAutomationRunsRepository,\n DrizzleAutomationsRepository,\n DrizzleConnectionsRepository,\n DrizzleContentRepository,\n DrizzleCredentialStore,\n DrizzleAdminRepository,\n DrizzleDigestRunRepository,\n DrizzleDevicesRepository,\n DrizzleDiscussionRepository,\n DrizzlePostRepository,\n DrizzleChatRepository,\n DrizzleEntitlementsRepository,\n DrizzleIdentityRepository,\n DrizzleMemberRepository,\n DrizzleMembersRepository,\n DrizzleMentionsRepository,\n DrizzleNotificationsRepository,\n DrizzleNotificationSettingsRepository,\n DrizzleOrganizationsRepository,\n DrizzleRolesRepository,\n DrizzleOutboxAppender,\n DrizzleOutboxStore,\n DrizzleProgressRepository,\n DrizzleSettingsRepository,\n DrizzleSubscriptionsRepository,\n DrizzleUnitOfWork,\n} from '@headless-lms/adapter-db';\nimport { InMemoryEventBus } from '@headless-lms/adapter-defaults/events';\nimport {\n PollingOutboxRelay,\n type PollingOutboxRelayConfig,\n} from '@headless-lms/adapter-defaults/events/outbox-relay';\nimport { InlineAutomationEngine } from '@headless-lms/adapter-defaults/workflows';\nimport { FetchWebhookSender } from '@headless-lms/adapter-defaults/webhooks';\nimport { EmailAdapter, StubTemplateRenderer } from '@headless-lms/adapter-defaults/email';\nimport {\n createRootLogger,\n type LogLevel,\n type PinoInstance,\n requestLogContext,\n type RequestLogContext,\n} from '@headless-lms/adapter-defaults/logging';\nimport { StorageAdapter } from '@headless-lms/adapter-defaults/storage';\nimport { PlainBodyParser } from '@headless-lms/adapter-defaults/body';\nimport { BetterAuth } from '@headless-lms/adapter-auth';\n\nimport {\n CommunityNotificationCategories,\n ContentItemTitles,\n ContentService,\n contentJobs,\n} from '@headless-lms/core/content';\nimport {\n EntitlementsServiceImpl,\n entitlementsJobs,\n entitlementsRecurringJobs,\n} from '@headless-lms/core/entitlements';\nimport { ProgressServiceImpl } from '@headless-lms/core/progress';\nimport {\n type ChatHub,\n type ChatHubOptions,\n ChatLiveForwarder,\n ChatServiceImpl,\n DigestServiceImpl,\n DiscussionItemTitles,\n DiscussionServiceImpl,\n type AuthorPermissionReader,\n PostServiceImpl,\n SubscriptionServiceImpl,\n discussionJobs,\n discussionRecurringJobs,\n} from '@headless-lms/core/discussion';\nimport {\n DevicesServiceImpl,\n IdentityServiceImpl,\n NotificationSettingsServiceImpl,\n type SessionAdmin,\n} from '@headless-lms/core/identity';\nimport {\n type ApiKeys,\n type GrantReader,\n type OrgAdmin,\n NotificationCatalog,\n OrganizationServiceImpl,\n OrgNotificationCategories,\n type RoleDefinition,\n} from '@headless-lms/core/organizations';\nimport {\n AssetsServiceImpl,\n assetsJobs,\n assetsRecurringJobs,\n createFileUrls,\n} from '@headless-lms/core/assets';\nimport { IntegrationsServiceImpl } from '@headless-lms/core/integrations';\nimport {\n AutomationsServiceImpl,\n DEFAULT_COMMUNITY_CATEGORIES,\n DEFAULT_ORG_CATEGORIES,\n NOTIFICATION_TYPES,\n NotificationDelivery,\n NotificationProcessor,\n NotificationsServiceImpl,\n RECIPIENT_RULES,\n SchedulerChatDebounce,\n notificationJobs,\n type NotificationCategories,\n} from '@headless-lms/core/automations';\nimport { buildNotificationRegistries, configuredPushSender } from './notification-channels.js';\nimport { loadIntegrations } from './integrations.js';\nimport { mcpEndpoints, type McpEndpoints } from './mcp-endpoints.js';\nimport { communityMail } from './community-mail.js';\nimport { notificationReads } from './notification-reads.js';\nimport { AdminReportServiceImpl } from '@headless-lms/core/reporting/admin';\nimport { MemberReportServiceImpl } from '@headless-lms/core/reporting/member';\nimport { Mailer, type MailerLookups } from '@headless-lms/core/shared/mailer';\nimport { SettingsService } from '@headless-lms/core/shared/settings';\nimport { platformKeyCheck, type PlatformConfig, type PlatformKeyCheck } from './platform.js';\n\nimport type {\n BodyParser,\n ChannelRegistry,\n CredentialStore,\n EmailSender,\n Logger,\n NotificationChannel,\n NotificationTypeRegistry,\n ObjectStorage,\n OutboxRelay,\n PubSub,\n PushSender,\n Scheduler,\n TemplateContext,\n TemplateRenderer,\n} from '@headless-lms/core/shared/ports';\nimport type { AutomationEngine, PushConfig, WebhookSender } from '@headless-lms/core/types';\n\n/** Installation-supplied ports; an absent slot falls back to a fail-loudly stub. */\nexport interface AdapterOverrides {\n email?: EmailSender;\n storage?: ObjectStorage;\n /** Resolves email templates to rendered content. Absent → fail-loudly stub. */\n templates?: TemplateRenderer;\n /** Durable automation engine, or a factory given the automations logger. Absent → InlineAutomationEngine (in-process, one attempt per action). */\n workflows?: (AutomationEngine & Scheduler) | ((logger: Logger) => AutomationEngine & Scheduler);\n /** Outbound HTTP for webhook deliveries. Absent → FetchWebhookSender (public addresses only, 10s timeout). */\n webhooks?: WebhookSender;\n /** Notification channels beyond in-app, email and push; each key must be new. */\n notificationChannels?: NotificationChannel[];\n /** Push credentials per provider. Absent → no provider is configured and devices cannot register. */\n push?: PushConfig;\n pushSender?: PushSender;\n /** Reads post bodies: their text, mentions and images. Absent → PlainBodyParser (plain text, no images). */\n bodyParser?: BodyParser;\n /** Delivery to every API instance, or a factory given its logger. Absent → no live chat; the chat stream answers 503. */\n pubsub?: PubSub | ((logger: Logger) => PubSub);\n}\n\nexport interface BuildContainerOptions {\n /** Installation's plugins folder, scanned by loadIntegrations. Absent → no integrations. */\n pluginsDir?: string;\n adapters?: AdapterOverrides;\n roles?: readonly RoleDefinition[];\n /** Live chat hub timings. Absent → the defaults (1 s state flush, 30 s session check). */\n live?: ChatHubOptions;\n}\n\nexport interface Config {\n databaseUrl: string;\n databasePool?: DatabasePoolOptions;\n authBaseURL: string;\n authSecret: string;\n trustedOrigins: string[];\n /** Branding threaded into every email template. Default: brandName \"Headless LMS\", baseUrl = adminAppUrl.\n * (studentPortalUrl is composed in from the top-level config field.) */\n emailBranding?: Omit<TemplateContext, 'studentPortalUrl'>;\n /** base64-encoded 32-byte key for the credential store (CREDENTIAL_STORE_KEY). */\n credentialStoreKey: string;\n /** Parent domain for cross-subdomain session cookies (e.g. \".example.com\"); undefined → host-only cookie. */\n cookieDomain?: string;\n /** Mark session cookies Secure (set behind HTTPS / in production). */\n secureCookies?: boolean;\n /** Student portal origin — invite links for students, and the origin whose signups are invite-gated. */\n studentPortalUrl: string;\n /** Admin app origin — invite links for staff. */\n adminAppUrl: string;\n /** Transactional-outbox relay tuning. All optional — see OUTBOX_DEFAULTS. */\n outbox?: OutboxConfig;\n /** Log level for the process-wide logger (HTTP + domain + relay). Default \"info\". */\n logging?: LoggingConfig;\n /** Lifetime of every signed file URL a response carries, seconds. Mirrors\n * ServerConfig.deliveryExpirySeconds — createContainer threads it through. */\n deliveryExpirySeconds: number;\n memberImageMaxBytes: number;\n memberFileMaxBytes: number;\n /** Let the default webhook sender reach loopback and private network addresses. Local development only. */\n webhookAllowPrivateNetwork?: boolean;\n platform?: PlatformConfig;\n}\n\n/** Tuning for the transactional-outbox relay. Every field is optional; the\n * container resolves against OUTBOX_DEFAULTS. */\nexport interface OutboxConfig {\n /** Master switch for the same-process relay. Default true. */\n enabled?: boolean;\n /** Idle delay between polls. Default 1000. */\n pollIntervalMs?: number;\n /** Max rows fetched/dispatched per tick. Default 100. */\n batchSize?: number;\n concurrency?: number;\n}\n\nexport const OUTBOX_DEFAULTS: PollingOutboxRelayConfig = {\n enabled: true,\n pollIntervalMs: 1000,\n batchSize: 100,\n concurrency: 16,\n};\n\nexport function resolveOutboxConfig(config: OutboxConfig = {}): PollingOutboxRelayConfig {\n const overrides = Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n );\n return { ...OUTBOX_DEFAULTS, ...overrides };\n}\n\n/** Logging tuning. Optional; resolved against LOGGING_DEFAULTS. */\nexport interface LoggingConfig {\n /** Minimum level emitted. Default \"info\". */\n level?: LogLevel;\n}\n\nexport const LOGGING_DEFAULTS: Required<LoggingConfig> = { level: 'info' };\n\nexport function resolveLoggingConfig(config: LoggingConfig = {}): Required<LoggingConfig> {\n return { level: config.level ?? LOGGING_DEFAULTS.level };\n}\n\nexport interface Container {\n auth: BetterAuth;\n mcp: McpEndpoints;\n platform: PlatformKeyCheck | null;\n // Org Provider\n orgProvider: OrgAdmin;\n\n // Domains\n identity: IdentityServiceImpl;\n organizations: OrganizationServiceImpl;\n content: ContentService;\n entitlements: EntitlementsServiceImpl;\n progress: ProgressServiceImpl;\n discussion: DiscussionServiceImpl;\n posts: PostServiceImpl;\n chats: ChatServiceImpl;\n subscriptions: SubscriptionServiceImpl;\n assets: AssetsServiceImpl;\n integrations: IntegrationsServiceImpl;\n automations: AutomationsServiceImpl;\n notificationTypes: NotificationTypeRegistry;\n notificationChannels: ChannelRegistry;\n notificationCategories: {\n community: CommunityNotificationCategories;\n org: OrgNotificationCategories;\n };\n notificationCatalog: NotificationCatalog;\n notificationCategoryLists: NotificationCategories;\n notificationSettings: NotificationSettingsServiceImpl;\n devices: DevicesServiceImpl;\n notificationList: NotificationsServiceImpl;\n /** Cross-cutting settings store. Not a context — a leaf every context may use;\n * inject `settings.for('<namespace>')` into a service, never the service itself. */\n settings: SettingsService;\n // Reporting read layer (composed cross-context reads; owns no domain rules).\n reporting: {\n admin: AdminReportServiceImpl;\n member: MemberReportServiceImpl;\n };\n storage: ObjectStorage;\n mailer: Mailer;\n /** Shared secure credential store — encrypted at rest, org-scoped, decrypt at point of use. */\n credentials: CredentialStore;\n /** The outbox relay — constructed but NEVER started by the container; the\n * installation's entry point starts it after listen (gen-openapi must not\n * poll). buildServer stops it onClose. */\n outboxRelay: OutboxRelay;\n /** The automation engine — constructed but NEVER started by the container;\n * the installation's entry point starts it after listen (gen-openapi must\n * not run a worker). buildServer stops it onClose. */\n automationEngine: AutomationEngine;\n scheduler: Scheduler;\n /** The PubSub adapter — constructed but NEVER started by the container; the\n * installation's entry point starts it after listen. buildServer stops it\n * onClose. Null without one. */\n pubsub: PubSub | null;\n /** This process's live chat hub; started by the entry point after `pubsub`,\n * closed by buildServer preClose. Null without a PubSub. */\n chatHub: ChatHub | null;\n /** Root logger port — components receive children bound with { name }. */\n logger: Logger;\n /** The raw pino root; buildServer hands it to Fastify so HTTP shares the stream. */\n loggerInstance: PinoInstance;\n /** Request-scoped log correlation; the HTTP layer enters it per request. */\n requestContext: RequestLogContext;\n /** Identity's session-write port; the auth adapter fulfils it. */\n sessions: SessionAdmin;\n}\n\nexport async function buildContainer(\n config: Config,\n options?: BuildContainerOptions,\n): Promise<Container> {\n const { instance: loggerInstance, logger } = createRootLogger(\n resolveLoggingConfig(config.logging).level,\n );\n // One child per domain — a context's service and repositories share it.\n\n const contentLogger = logger.child({ name: 'content' });\n const entitlementsLogger = logger.child({ name: 'entitlements' });\n const progressLogger = logger.child({ name: 'progress' });\n const discussionLogger = logger.child({ name: 'discussion' });\n const assetsLogger = logger.child({ name: 'assets' });\n const integrationsLogger = logger.child({ name: 'integrations' });\n const automationsLogger = logger.child({ name: 'automations' });\n const reportingLogger = logger.child({ name: 'reporting' });\n const outboxLogger = logger.child({ name: 'outbox' });\n\n // Outbound adapters\n const db = createDb(config.databaseUrl, logger.child({ name: 'db' }), config.databasePool);\n const email = options?.adapters?.email ?? new EmailAdapter(logger.child({ name: 'email' }));\n const storage: ObjectStorage =\n options?.adapters?.storage ?? new StorageAdapter(logger.child({ name: 'storage' }));\n const templates =\n options?.adapters?.templates ?? new StubTemplateRenderer(logger.child({ name: 'email' }));\n const bodies = options?.adapters?.bodyParser ?? new PlainBodyParser();\n const workflows = options?.adapters?.workflows;\n const automationEngine: AutomationEngine & Scheduler =\n typeof workflows === 'function'\n ? workflows(automationsLogger)\n : (workflows ?? new InlineAutomationEngine(automationsLogger));\n const pubsubOption = options?.adapters?.pubsub;\n const pubsub: PubSub | null =\n typeof pubsubOption === 'function'\n ? pubsubOption(logger.child({ name: 'pubsub' }))\n : (pubsubOption ?? null);\n const mailer = new Mailer(templates, email, {\n ...(config.emailBranding ?? { brandName: 'Headless LMS', baseUrl: config.adminAppUrl }),\n studentPortalUrl: config.studentPortalUrl,\n });\n\n const fileUrls = createFileUrls(config.authBaseURL, (key) => storage.publicUrl(key));\n const assets = new AssetsServiceImpl({\n storage,\n repo: new DrizzleAssetsRepository(db, assetsLogger),\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n assets: new DrizzleAssetsRepository(tx, assetsLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n memberImageMaxBytes: config.memberImageMaxBytes,\n memberFileMaxBytes: config.memberFileMaxBytes,\n downloadLinkExpirySeconds: config.deliveryExpirySeconds,\n fileUrls,\n logger: assetsLogger,\n });\n\n /*\n * Identity\n */\n const identityUoW = new DrizzleUnitOfWork(db, (tx) => ({\n identity: new DrizzleIdentityRepository(tx, logger.child({ name: 'identity' })),\n outbox: new DrizzleOutboxAppender(tx, logger.child({ name: 'identity' })),\n }));\n const identity = new IdentityServiceImpl({\n repo: new DrizzleIdentityRepository(db, logger.child({ name: 'identity_repo' })),\n logger: logger.child({ name: 'identity' }),\n uow: identityUoW,\n mailer,\n images: assets,\n fileUrls,\n });\n\n /*\n * Organization\n */\n const organizationsRepo = new DrizzleOrganizationsRepository(db, logger.child({ name: 'org' }));\n const organizationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n organizations: new DrizzleOrganizationsRepository(tx, logger.child({ name: 'org' })),\n roles: new DrizzleRolesRepository(tx),\n outbox: new DrizzleOutboxAppender(tx, logger.child({ name: 'org' })),\n }));\n const orgAdminRef: { current: BetterAuth | undefined } = { current: undefined };\n const platform = config.platform ? platformKeyCheck(config.platform) : null;\n const organizations: OrganizationServiceImpl = new OrganizationServiceImpl({\n repo: organizationsRepo,\n roles: new DrizzleRolesRepository(db),\n ...(options?.roles && { defaultRoles: options.roles }),\n apiKeys: (): ApiKeys => {\n if (!orgAdminRef.current) {\n throw new Error('api keys not initialised');\n }\n return orgAdminRef.current;\n },\n accounts: () => {\n if (!orgAdminRef.current) {\n throw new Error('accounts not initialised');\n }\n return orgAdminRef.current;\n },\n ...(config.platform && {\n platform: {\n email: config.platform.accountEmail,\n name: config.platform.accountName ?? 'Platform',\n },\n }),\n membersRepo: new DrizzleMembersRepository(db, logger.child({ name: 'org' })),\n people: identity,\n images: assets,\n uow: organizationsUow,\n orgAdmin: () => {\n if (!orgAdminRef.current) {\n throw new Error('orgAdmin not initialised');\n }\n return orgAdminRef.current;\n },\n sessions: () => {\n if (!orgAdminRef.current) {\n throw new Error('sessions not initialised');\n }\n return orgAdminRef.current;\n },\n logger: logger.child({ name: 'org' }),\n mailer,\n inviteUrls: { studentPortalUrl: config.studentPortalUrl, adminAppUrl: config.adminAppUrl },\n grants: (): GrantReader => entitlements,\n });\n\n /*\n * Settings\n */\n const settings = new SettingsService(\n new DrizzleSettingsRepository(db, logger.child({ name: 'settings' })),\n );\n // Content: reads on the root db; course writes + outbox append in one tx.\n const contentUow = new DrizzleUnitOfWork(db, (tx) => ({\n content: new DrizzleContentRepository(tx, contentLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const content = new ContentService({\n repo: new DrizzleContentRepository(db, contentLogger),\n uow: contentUow,\n libraryAssets: assets,\n files: assets,\n fileUrls,\n scheduler: automationEngine,\n logger: contentLogger,\n });\n const pushConfig = options?.adapters?.push ?? {};\n const pushSender = configuredPushSender(pushConfig, options?.adapters?.pushSender);\n const notificationRegistries = buildNotificationRegistries({\n declarations: NOTIFICATION_TYPES,\n defaults: { community: DEFAULT_COMMUNITY_CATEGORIES, org: DEFAULT_ORG_CATEGORIES },\n mailer: options?.adapters?.email ? mailer : null,\n emailOf: async (_orgId, userId) => (await identity.getUserById(userId))?.email ?? null,\n push: pushSender\n ? {\n sender: pushSender,\n activeDevices: (orgId, userId, since) => devices.activeDevices(orgId, userId, since),\n }\n : null,\n channels: options?.adapters?.notificationChannels ?? [],\n });\n const notificationCategories = {\n community: new CommunityNotificationCategories({\n communities: new DrizzleContentRepository(db, contentLogger),\n settings,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n logger: contentLogger,\n }),\n org: new OrgNotificationCategories({\n settings,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n logger: logger.child({ name: 'org' }),\n }),\n };\n const notificationCatalog = new NotificationCatalog({\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n });\n const notificationCategoryLists: NotificationCategories = {\n listFor: async (orgId, communityId) =>\n (communityId\n ? await notificationCategories.community.get(communityId, organizations.systemActor(orgId))\n : await notificationCategories.org.get(orgId, organizations.systemActor(orgId))\n ).categories,\n };\n // Entitlements: reads on the root db; writes + outbox append in one tx.\n const entitlementsUow = new DrizzleUnitOfWork(db, (tx) => ({\n entitlements: new DrizzleEntitlementsRepository(tx, entitlementsLogger),\n spaces: new DrizzleContentRepository(tx, contentLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const authors: AuthorPermissionReader = organizations;\n const entitlementsRepo = new DrizzleEntitlementsRepository(db, entitlementsLogger);\n const entitlements = new EntitlementsServiceImpl({\n repo: entitlementsRepo,\n content,\n permissions: organizations,\n uow: entitlementsUow,\n logger: entitlementsLogger,\n });\n const identityLogger = logger.child({ name: 'identity' });\n const notificationSettings = new NotificationSettingsServiceImpl({\n repo: new DrizzleNotificationSettingsRepository(db, identityLogger),\n categories: {\n communityList: async (orgId, communityId) =>\n (await notificationCategories.community.get(communityId, organizations.systemActor(orgId)))\n .categories,\n orgList: async (orgId) =>\n (await notificationCategories.org.get(orgId, organizations.systemActor(orgId))).categories,\n },\n access: {\n canOpen: (orgId, actor, communityId) => entitlements.isCommunityMember(communityId, actor),\n },\n channels: notificationRegistries.channels,\n logger: identityLogger,\n });\n const devices = new DevicesServiceImpl({\n repo: new DrizzleDevicesRepository(db, identityLogger),\n push: pushConfig,\n logger: identityLogger,\n });\n // Progress: report writes + outbox append in one tx; content supplies the\n // structure and completion rules the service evaluates against.\n const progressUow = new DrizzleUnitOfWork(db, (tx) => ({\n progress: new DrizzleProgressRepository(tx, progressLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const progress = new ProgressServiceImpl({\n repo: new DrizzleProgressRepository(db, progressLogger),\n content,\n access: entitlements,\n system: organizations,\n uow: progressUow,\n logger: progressLogger,\n });\n\n const postReads = new DrizzlePostRepository(db, discussionLogger);\n const discussionReads = new DrizzleDiscussionRepository(db, discussionLogger);\n const chatReads = new DrizzleChatRepository(db, discussionLogger);\n const subscriptionReads = new DrizzleSubscriptionsRepository(db, discussionLogger);\n const mentionReads = new DrizzleMentionsRepository(db, discussionLogger);\n const posts = new PostServiceImpl({\n repo: postReads,\n access: entitlements,\n content,\n authors,\n profiles: discussionReads,\n comments: discussionReads,\n subscriptions: subscriptionReads,\n mentions: mentionReads,\n images: assets,\n fileUrls,\n bodies,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n posts: new DrizzlePostRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n scheduler: automationEngine,\n logger: discussionLogger,\n });\n const chats = new ChatServiceImpl({\n repo: chatReads,\n access: entitlements,\n content,\n authors,\n profiles: discussionReads,\n subscriptions: subscriptionReads,\n mentions: mentionReads,\n images: assets,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n chats: new DrizzleChatRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n ...(pubsub && { pubsub }),\n ...(options?.live && { live: options.live }),\n logger: discussionLogger,\n });\n const discussion = new DiscussionServiceImpl({\n repo: discussionReads,\n access: entitlements,\n content: content,\n posts,\n spaces: entitlements,\n authors,\n mentions: mentionReads,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n discussion: new DrizzleDiscussionRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n settings,\n logger: discussionLogger,\n });\n\n const subscriptions = new SubscriptionServiceImpl({\n repo: subscriptionReads,\n access: entitlements,\n posts,\n chats,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n subscriptions: new DrizzleSubscriptionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n logger: discussionLogger,\n });\n\n const digests = new DigestServiceImpl({\n schedules: { communitiesWithDigest: () => content.communitiesWithDigest() },\n runs: new DrizzleDigestRunRepository(db, discussionLogger),\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n digests: new DrizzleDigestRunRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n logger: discussionLogger,\n });\n\n const notificationsRepo = new DrizzleNotificationsRepository(db);\n const notificationList = new NotificationsServiceImpl({\n repo: notificationsRepo,\n logger: automationsLogger,\n });\n const itemTitles = {\n discussion: new DiscussionItemTitles({\n posts,\n chats,\n comments: discussionReads,\n messages: chatReads,\n profiles: discussionReads,\n activities: content,\n access: entitlements,\n actors: organizations,\n }),\n content: new ContentItemTitles({ content, access: entitlements, organizations }),\n };\n\n const reporting = {\n admin: new AdminReportServiceImpl({\n repo: new DrizzleAdminRepository(db, reportingLogger),\n permissions: organizations,\n spaces: entitlements,\n events: content,\n fileUrls,\n }),\n member: new MemberReportServiceImpl({\n repo: new DrizzleMemberRepository(db, reportingLogger),\n content,\n progress,\n files: assets,\n fileUrls,\n access: entitlements,\n people: organizations,\n notifications: notificationList,\n titles: itemTitles,\n subscriptions,\n notificationCategories,\n notificationSettings,\n channels: notificationRegistries.channels,\n studentPortalUrl: config.studentPortalUrl,\n }),\n };\n\n const credentialStore = new DrizzleCredentialStore(\n db,\n config.credentialStoreKey,\n integrationsLogger,\n );\n // The integrations this deployment supports: the installation's declared\n // plugins folder (directory name = integration id), loaded at startup.\n // Connect/configure reject undeclared ids and validate config with the\n // integration's own schema. No folder → no integrations.\n const integrationsRegistry = await loadIntegrations(options?.pluginsDir, integrationsLogger);\n // Integrations: credential + connection writes + outbox append in one tx\n // (a tx-bound credential store instance shares the scope's transaction).\n const integrationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n connections: new DrizzleConnectionsRepository(tx, integrationsLogger),\n credentials: new DrizzleCredentialStore(tx, config.credentialStoreKey, integrationsLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const integrations = new IntegrationsServiceImpl({\n registry: integrationsRegistry,\n repo: new DrizzleConnectionsRepository(db, integrationsLogger),\n uow: integrationsUow,\n credentials: credentialStore,\n logger: integrationsLogger,\n });\n\n // Automations: run writes + outbox append in one tx; the engine drives\n // execution (register below), integrations supplies the loaded\n // integrations' actions for `available`.\n const automationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n automations: new DrizzleAutomationsRepository(tx, automationsLogger),\n runs: new DrizzleAutomationRunsRepository(tx, automationsLogger),\n credentials: new DrizzleCredentialStore(tx, config.credentialStoreKey, automationsLogger),\n outbox: new DrizzleOutboxAppender(tx, automationsLogger),\n }));\n // Emails triggered by row-shaped events resolve their recipient and content\n // details at send time — the event carries ids only.\n const mailerLookups: MailerLookups = {\n orgUserPerson: async (orgId, orgUserId) => {\n const orgUser = await organizationsRepo.findOrgUserById(orgId, orgUserId);\n const user = orgUser ? await identity.getUserById(orgUser.userId) : null;\n return user ? { userId: user.id, email: user.email } : null;\n },\n contentInfo: async (orgId, contentId) => {\n const system = organizations.systemActor(orgId);\n const course = await content.findCourse(contentId, system);\n if (course) {\n return { id: course.id, title: course.title, type: course.type };\n }\n const download = await content.findDownload(contentId, system);\n if (download) {\n return { id: download.id, title: download.title, type: download.type };\n }\n const community = await content.findCommunity(contentId, system);\n if (community) {\n return { id: community.id, title: community.title, type: community.type };\n }\n const event = await content.findEvent(contentId, system);\n return event ? { id: event.id, title: event.title, type: event.type } : null;\n },\n };\n\n const runsRepo = new DrizzleAutomationRunsRepository(db, automationsLogger);\n const wired = communityMail({\n content,\n entitlements,\n organizations,\n identity,\n posts,\n subscriptions,\n discussion,\n chats,\n bodies,\n });\n const recipientReads = notificationReads({\n content,\n entitlements,\n organizations,\n identity,\n posts,\n subscriptions,\n discussion,\n chats,\n bodies,\n settings: notificationSettings,\n });\n const notificationProcessor = new NotificationProcessor({\n rules: RECIPIENT_RULES,\n reads: recipientReads,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n categories: notificationCategoryLists,\n settings: notificationSettings,\n devices,\n repo: notificationsRepo,\n engine: automationEngine,\n debounce: new SchedulerChatDebounce(automationEngine),\n logger: automationsLogger,\n });\n const automations = new AutomationsServiceImpl({\n repo: new DrizzleAutomationsRepository(db, automationsLogger),\n runsRepo,\n uow: automationsUow,\n engine: automationEngine,\n mailer,\n lookups: mailerLookups,\n integrations,\n systemActor: (orgId) => organizations.systemActor(orgId),\n credentials: credentialStore,\n webhooks:\n options?.adapters?.webhooks ??\n new FetchWebhookSender({ allowPrivateNetwork: config.webhookAllowPrivateNetwork }),\n mail: wired.mail,\n grants: wired.grants,\n delivery: new NotificationDelivery({\n repo: notificationsRepo,\n channels: notificationRegistries.channels,\n devices,\n logger: automationsLogger,\n }),\n processor: notificationProcessor,\n logger: automationsLogger,\n });\n automationEngine.register(automations);\n automationEngine.registerJobs(\n [\n ...discussionJobs({ posts, digests, logger: discussionLogger }),\n ...contentJobs({ events: content, logger: contentLogger }),\n ...entitlementsJobs({ grants: entitlements }),\n ...assetsJobs({ assets, orgs: organizations, logger: assetsLogger }),\n ...notificationJobs({\n processor: notificationProcessor,\n reads: recipientReads,\n logger: automationsLogger,\n }),\n ],\n [...discussionRecurringJobs, ...entitlementsRecurringJobs, ...assetsRecurringJobs],\n );\n\n const eventBus = new InMemoryEventBus();\n if (pubsub) {\n new ChatLiveForwarder({ pubsub, logger: discussionLogger }).attach(eventBus);\n }\n eventBus.subscribeAll((event) => automations.handle(event));\n const outboxConfig = resolveOutboxConfig(config.outbox);\n const outboxRelay = new PollingOutboxRelay(\n new DrizzleOutboxStore(db, outboxLogger),\n eventBus,\n outboxConfig,\n outboxLogger,\n );\n\n /*\n * Auth Adapter\n */\n const authLogger = logger.child({ name: 'auth' });\n // Hook failures otherwise surface through Better Auth's route as an opaque\n // 500 — log which hook died before rethrowing.\n const authHook =\n <A extends unknown[], R>(name: string, fn: (...args: A) => Promise<R>) =>\n async (...args: A): Promise<R> => {\n try {\n return await fn(...args);\n } catch (err) {\n authLogger.error(`auth hook ${name} failed`, { err });\n throw err;\n }\n };\n const mcp = mcpEndpoints(config);\n if (!mcp.oauthEnabled) {\n authLogger.warn(\n 'MCP OAuth is off: BETTER_AUTH_URL must be https or a loopback host; /mcp accepts API keys only',\n { authBaseURL: config.authBaseURL },\n );\n }\n const auth: BetterAuth = new BetterAuth({\n db,\n baseUrl: config.authBaseURL,\n mcp: {\n oauthEnabled: mcp.oauthEnabled,\n resource: mcp.resource,\n loginPage: mcp.loginPage,\n consentPage: mcp.consentPage,\n orgPickerPage: mcp.orgPickerPage,\n },\n secret: config.authSecret,\n trustedOrigins: config.trustedOrigins,\n hooks: {\n sendResetPassword: async ({ user, url }) => {\n await identity.sendPasswordReset({ email: user.email, url });\n },\n onPasswordReset: async ({ user }) => {\n await identity.sendPasswordChanged({ email: user.email });\n },\n sendMagicLink: authHook('sendMagicLink', async ({ email, url }) => {\n await identity.sendMagicLink({\n email,\n url,\n });\n }),\n beforeUserCreate: authHook('beforeUserCreate', async (user) => {\n const { email, name } = user;\n const [firstName, lastName] = name.split(' ');\n // Better Auth has not minted an id yet — the id returned below becomes\n // its account id. A person provisioned at invite time is linked instead of re-created.\n const domainUser = await identity.linkOrCreateUser({\n email,\n ...(firstName !== undefined && { firstName }),\n ...(lastName !== undefined && { lastName }),\n });\n\n return {\n data: {\n ...user,\n id: domainUser.id,\n },\n };\n }),\n beforeCreateSession: authHook('beforeCreateSession', async (session) => {\n const person = await identity.getUserByExternalId(session.userId);\n if (!person) {\n return;\n }\n if (organizations.isPlatformAccount(person.email)) {\n authLogger.warn('sign-in refused for the platform account', { userId: person.id });\n return false;\n }\n const orgs = await organizations.getOrgUsersForUser(person.id);\n if (orgs.length !== 1) {\n return;\n }\n // TODO - fix, why default to the first?\n const org = await organizations.getById(orgs[0]!.orgId);\n if (!org) {\n return;\n }\n\n return { data: { ...session, activeOrganizationId: org.id } };\n }),\n /*\n * We're making sure the domain org creates and using the same ID for better-auth org ID.\n */\n beforeCreateOrganization: authHook(\n 'beforeCreateOrganization',\n async ({ organization: org, user: baUser }) => {\n const domainOrg = await organizations.provisionOrganization({\n ownerId: baUser.id,\n ...org,\n logo: org.logo ?? undefined,\n });\n return { data: { ...org, id: domainOrg.id } };\n },\n ),\n beforeDeleteOrganization: authHook('beforeDeleteOrganization', async ({ organization }) => {\n await organizations.deleteOrganization(organization.id);\n }),\n beforeAddMember: authHook('beforeAddMember', async ({ member }) => {\n await organizations.addOrgUser({\n orgId: member.organizationId,\n userId: member.userId,\n });\n }),\n beforeRemoveMember: authHook('beforeRemoveMember', async ({ member }) => {\n await organizations.removeOrgUser(member.organizationId, member.userId);\n }),\n },\n logger: authLogger,\n cookieDomain: config.cookieDomain,\n secureCookies: config.secureCookies,\n });\n orgAdminRef.current = auth;\n\n return {\n auth,\n mcp,\n platform,\n identity,\n organizations,\n // Better Auth owns org writes; the same instance fulfils the OrgAdmin port.\n orgProvider: auth,\n content,\n entitlements,\n progress,\n discussion,\n posts,\n chats,\n subscriptions,\n assets,\n integrations,\n automations,\n notificationTypes: notificationRegistries.types,\n notificationChannels: notificationRegistries.channels,\n notificationCategories,\n notificationCatalog,\n notificationCategoryLists,\n notificationSettings,\n devices,\n notificationList,\n settings,\n reporting,\n storage,\n mailer,\n credentials: credentialStore,\n outboxRelay,\n automationEngine,\n scheduler: automationEngine,\n pubsub,\n chatHub: chats.hub,\n logger,\n loggerInstance,\n requestContext: requestLogContext,\n // Better Auth owns the session store; the same instance fulfils SessionAdmin.\n sessions: auth,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"container.js","names":[],"sources":["../../src/app/container.ts"],"sourcesContent":["// Wires adapters + services in dependency order. Starts nothing.\nimport {\n createDb,\n type DatabasePoolOptions,\n DrizzleAssetsRepository,\n DrizzleAutomationRunsRepository,\n DrizzleAutomationsRepository,\n DrizzleConnectionsRepository,\n DrizzleContentRepository,\n DrizzleCredentialStore,\n DrizzleAdminRepository,\n DrizzleDigestRunRepository,\n DrizzleDevicesRepository,\n DrizzleDiscussionRepository,\n DrizzlePostRepository,\n DrizzleChatRepository,\n DrizzleEntitlementsRepository,\n DrizzleIdentityRepository,\n DrizzleMemberRepository,\n DrizzleMembersRepository,\n DrizzleMentionsRepository,\n DrizzleNotificationsRepository,\n DrizzleNotificationSettingsRepository,\n DrizzleOrganizationsRepository,\n DrizzleRolesRepository,\n DrizzleOutboxAppender,\n DrizzleOutboxStore,\n DrizzleProgressRepository,\n DrizzleSettingsRepository,\n DrizzleSubscriptionsRepository,\n DrizzleUnitOfWork,\n} from '@headless-lms/adapter-db';\nimport { InMemoryEventBus } from '@headless-lms/adapter-defaults/events';\nimport {\n PollingOutboxRelay,\n type PollingOutboxRelayConfig,\n} from '@headless-lms/adapter-defaults/events/outbox-relay';\nimport { InlineAutomationEngine } from '@headless-lms/adapter-defaults/workflows';\nimport { FetchWebhookSender } from '@headless-lms/adapter-defaults/webhooks';\nimport { EmailAdapter, StubTemplateRenderer } from '@headless-lms/adapter-defaults/email';\nimport {\n createRootLogger,\n type LogLevel,\n type PinoInstance,\n requestLogContext,\n type RequestLogContext,\n} from '@headless-lms/adapter-defaults/logging';\nimport { StorageAdapter } from '@headless-lms/adapter-defaults/storage';\nimport { PlainBodyParser } from '@headless-lms/adapter-defaults/body';\nimport { BetterAuth } from '@headless-lms/adapter-auth';\n\nimport {\n CommunityNotificationCategories,\n ContentItemTitles,\n ContentService,\n contentJobs,\n} from '@headless-lms/core/content';\nimport {\n EntitlementsServiceImpl,\n entitlementsJobs,\n entitlementsRecurringJobs,\n} from '@headless-lms/core/entitlements';\nimport { ProgressServiceImpl } from '@headless-lms/core/progress';\nimport {\n type ChatHub,\n type ChatHubOptions,\n ChatLiveForwarder,\n ChatServiceImpl,\n DigestServiceImpl,\n DiscussionItemTitles,\n DiscussionServiceImpl,\n type AuthorPermissionReader,\n PostServiceImpl,\n SubscriptionServiceImpl,\n discussionJobs,\n discussionRecurringJobs,\n} from '@headless-lms/core/discussion';\nimport {\n DevicesServiceImpl,\n IdentityServiceImpl,\n NotificationSettingsServiceImpl,\n type SessionAdmin,\n} from '@headless-lms/core/identity';\nimport {\n type ApiKeys,\n type GrantReader,\n type OrgAdmin,\n NotificationCatalog,\n OrganizationServiceImpl,\n OrgNotificationCategories,\n type RoleDefinition,\n} from '@headless-lms/core/organizations';\nimport {\n AssetsServiceImpl,\n assetsJobs,\n assetsRecurringJobs,\n createFileUrls,\n} from '@headless-lms/core/assets';\nimport { IntegrationsServiceImpl } from '@headless-lms/core/integrations';\nimport {\n AutomationsServiceImpl,\n DEFAULT_COMMUNITY_CATEGORIES,\n DEFAULT_ORG_CATEGORIES,\n NOTIFICATION_TYPES,\n NotificationDelivery,\n NotificationProcessor,\n NotificationsServiceImpl,\n RECIPIENT_RULES,\n SchedulerChatDebounce,\n notificationJobs,\n type NotificationCategories,\n} from '@headless-lms/core/automations';\nimport { buildNotificationRegistries, configuredPushSender } from './notification-channels.js';\nimport { loadIntegrations } from './integrations.js';\nimport { mcpEndpoints, type McpEndpoints } from './mcp-endpoints.js';\nimport { communityMail } from './community-mail.js';\nimport { notificationReads } from './notification-reads.js';\nimport { AdminReportServiceImpl } from '@headless-lms/core/reporting/admin';\nimport { MemberReportServiceImpl } from '@headless-lms/core/reporting/member';\nimport { Mailer, type MailerLookups } from '@headless-lms/core/shared/mailer';\nimport { SettingsService } from '@headless-lms/core/shared/settings';\nimport { platformKeyCheck, type PlatformConfig, type PlatformKeyCheck } from './platform.js';\n\nimport type {\n BodyParser,\n ChannelRegistry,\n CredentialStore,\n EmailSender,\n EventBus,\n Logger,\n NotificationChannel,\n NotificationTypeRegistry,\n ObjectStorage,\n OutboxRelay,\n PubSub,\n PushSender,\n Scheduler,\n TemplateContext,\n TemplateRenderer,\n} from '@headless-lms/core/shared/ports';\nimport type { AutomationEngine, PushConfig, WebhookSender } from '@headless-lms/core/types';\n\n/** Installation-supplied ports; an absent slot falls back to a fail-loudly stub. */\nexport interface AdapterOverrides {\n email?: EmailSender;\n storage?: ObjectStorage;\n /** Resolves email templates to rendered content. Absent → fail-loudly stub. */\n templates?: TemplateRenderer;\n /** Durable automation engine, or a factory given the automations logger. Absent → InlineAutomationEngine (in-process, one attempt per action). */\n workflows?: (AutomationEngine & Scheduler) | ((logger: Logger) => AutomationEngine & Scheduler);\n /** Outbound HTTP for webhook deliveries. Absent → FetchWebhookSender (public addresses only, 10s timeout). */\n webhooks?: WebhookSender;\n /** Notification channels beyond in-app, email and push; each key must be new. */\n notificationChannels?: NotificationChannel[];\n /** Push credentials per provider. Absent → no provider is configured and devices cannot register. */\n push?: PushConfig;\n pushSender?: PushSender;\n /** Reads post bodies: their text, mentions and images. Absent → PlainBodyParser (plain text, no images). */\n bodyParser?: BodyParser;\n /** Delivery to every API instance, or a factory given its logger. Absent → no live chat; the chat stream answers 503. */\n pubsub?: PubSub | ((logger: Logger) => PubSub);\n /** Delivers outbox events to their subscribers, or a factory given its logger. Absent → InMemoryEventBus (same process). */\n eventBus?: EventBus | ((logger: Logger) => EventBus);\n}\n\nexport interface BuildContainerOptions {\n /** Installation's plugins folder, scanned by loadIntegrations. Absent → no integrations. */\n pluginsDir?: string;\n adapters?: AdapterOverrides;\n roles?: readonly RoleDefinition[];\n /** Live chat hub timings. Absent → the defaults (1 s state flush, 30 s session check). */\n live?: ChatHubOptions;\n}\n\nexport interface Config {\n databaseUrl: string;\n databasePool?: DatabasePoolOptions;\n authBaseURL: string;\n authSecret: string;\n trustedOrigins: string[];\n /** Branding threaded into every email template. Default: brandName \"Headless LMS\", baseUrl = adminAppUrl.\n * (studentPortalUrl is composed in from the top-level config field.) */\n emailBranding?: Omit<TemplateContext, 'studentPortalUrl'>;\n /** base64-encoded 32-byte key for the credential store (CREDENTIAL_STORE_KEY). */\n credentialStoreKey: string;\n /** Parent domain for cross-subdomain session cookies (e.g. \".example.com\"); undefined → host-only cookie. */\n cookieDomain?: string;\n /** Mark session cookies Secure (set behind HTTPS / in production). */\n secureCookies?: boolean;\n /** Student portal origin — invite links for students, and the origin whose signups are invite-gated. */\n studentPortalUrl: string;\n /** Admin app origin — invite links for staff. */\n adminAppUrl: string;\n /** Transactional-outbox relay tuning. All optional — see OUTBOX_DEFAULTS. */\n outbox?: OutboxConfig;\n /** Log level for the process-wide logger (HTTP + domain + relay). Default \"info\". */\n logging?: LoggingConfig;\n /** Lifetime of every signed file URL a response carries, seconds. Mirrors\n * ServerConfig.deliveryExpirySeconds — createContainer threads it through. */\n deliveryExpirySeconds: number;\n memberImageMaxBytes: number;\n memberFileMaxBytes: number;\n /** Let the default webhook sender reach loopback and private network addresses. Local development only. */\n webhookAllowPrivateNetwork?: boolean;\n platform?: PlatformConfig;\n}\n\n/** Tuning for the transactional-outbox relay. Every field is optional; the\n * container resolves against OUTBOX_DEFAULTS. */\nexport interface OutboxConfig {\n /** Master switch for the same-process relay. Default true. */\n enabled?: boolean;\n /** Idle delay between polls. Default 1000. */\n pollIntervalMs?: number;\n /** Max rows fetched/dispatched per tick. Default 100. */\n batchSize?: number;\n concurrency?: number;\n}\n\nexport const OUTBOX_DEFAULTS: PollingOutboxRelayConfig = {\n enabled: true,\n pollIntervalMs: 1000,\n batchSize: 100,\n concurrency: 16,\n};\n\nexport function resolveOutboxConfig(config: OutboxConfig = {}): PollingOutboxRelayConfig {\n const overrides = Object.fromEntries(\n Object.entries(config).filter(([, value]) => value !== undefined),\n );\n return { ...OUTBOX_DEFAULTS, ...overrides };\n}\n\n/** Logging tuning. Optional; resolved against LOGGING_DEFAULTS. */\nexport interface LoggingConfig {\n /** Minimum level emitted. Default \"info\". */\n level?: LogLevel;\n}\n\nexport const LOGGING_DEFAULTS: Required<LoggingConfig> = { level: 'info' };\n\nexport function resolveLoggingConfig(config: LoggingConfig = {}): Required<LoggingConfig> {\n return { level: config.level ?? LOGGING_DEFAULTS.level };\n}\n\nexport interface Container {\n auth: BetterAuth;\n mcp: McpEndpoints;\n platform: PlatformKeyCheck | null;\n // Org Provider\n orgProvider: OrgAdmin;\n\n // Domains\n identity: IdentityServiceImpl;\n organizations: OrganizationServiceImpl;\n content: ContentService;\n entitlements: EntitlementsServiceImpl;\n progress: ProgressServiceImpl;\n discussion: DiscussionServiceImpl;\n posts: PostServiceImpl;\n chats: ChatServiceImpl;\n subscriptions: SubscriptionServiceImpl;\n assets: AssetsServiceImpl;\n integrations: IntegrationsServiceImpl;\n automations: AutomationsServiceImpl;\n notificationTypes: NotificationTypeRegistry;\n notificationChannels: ChannelRegistry;\n notificationCategories: {\n community: CommunityNotificationCategories;\n org: OrgNotificationCategories;\n };\n notificationCatalog: NotificationCatalog;\n notificationCategoryLists: NotificationCategories;\n notificationSettings: NotificationSettingsServiceImpl;\n devices: DevicesServiceImpl;\n notificationList: NotificationsServiceImpl;\n /** Cross-cutting settings store. Not a context — a leaf every context may use;\n * inject `settings.for('<namespace>')` into a service, never the service itself. */\n settings: SettingsService;\n // Reporting read layer (composed cross-context reads; owns no domain rules).\n reporting: {\n admin: AdminReportServiceImpl;\n member: MemberReportServiceImpl;\n };\n storage: ObjectStorage;\n mailer: Mailer;\n /** Shared secure credential store — encrypted at rest, org-scoped, decrypt at point of use. */\n credentials: CredentialStore;\n /** The outbox relay — constructed but NEVER started by the container; the\n * installation's entry point starts it after listen (gen-openapi must not\n * poll). buildServer stops it onClose. */\n outboxRelay: OutboxRelay;\n /** The event bus the outbox relay publishes to, with the core subscribers attached. */\n eventBus: EventBus;\n /** The automation engine — constructed but NEVER started by the container;\n * the installation's entry point starts it after listen (gen-openapi must\n * not run a worker). buildServer stops it onClose. */\n automationEngine: AutomationEngine;\n scheduler: Scheduler;\n /** The PubSub adapter — constructed but NEVER started by the container; the\n * installation's entry point starts it after listen. buildServer stops it\n * onClose. Null without one. */\n pubsub: PubSub | null;\n /** This process's live chat hub; started by the entry point after `pubsub`,\n * closed by buildServer preClose. Null without a PubSub. */\n chatHub: ChatHub | null;\n /** Root logger port — components receive children bound with { name }. */\n logger: Logger;\n /** The raw pino root; buildServer hands it to Fastify so HTTP shares the stream. */\n loggerInstance: PinoInstance;\n /** Request-scoped log correlation; the HTTP layer enters it per request. */\n requestContext: RequestLogContext;\n /** Identity's session-write port; the auth adapter fulfils it. */\n sessions: SessionAdmin;\n}\n\nexport async function buildContainer(\n config: Config,\n options?: BuildContainerOptions,\n): Promise<Container> {\n const { instance: loggerInstance, logger } = createRootLogger(\n resolveLoggingConfig(config.logging).level,\n );\n // One child per domain — a context's service and repositories share it.\n\n const contentLogger = logger.child({ name: 'content' });\n const entitlementsLogger = logger.child({ name: 'entitlements' });\n const progressLogger = logger.child({ name: 'progress' });\n const discussionLogger = logger.child({ name: 'discussion' });\n const assetsLogger = logger.child({ name: 'assets' });\n const integrationsLogger = logger.child({ name: 'integrations' });\n const automationsLogger = logger.child({ name: 'automations' });\n const reportingLogger = logger.child({ name: 'reporting' });\n const outboxLogger = logger.child({ name: 'outbox' });\n\n // Outbound adapters\n const db = createDb(config.databaseUrl, logger.child({ name: 'db' }), config.databasePool);\n const email = options?.adapters?.email ?? new EmailAdapter(logger.child({ name: 'email' }));\n const storage: ObjectStorage =\n options?.adapters?.storage ?? new StorageAdapter(logger.child({ name: 'storage' }));\n const templates =\n options?.adapters?.templates ?? new StubTemplateRenderer(logger.child({ name: 'email' }));\n const bodies = options?.adapters?.bodyParser ?? new PlainBodyParser();\n const workflows = options?.adapters?.workflows;\n const automationEngine: AutomationEngine & Scheduler =\n typeof workflows === 'function'\n ? workflows(automationsLogger)\n : (workflows ?? new InlineAutomationEngine(automationsLogger));\n const pubsubOption = options?.adapters?.pubsub;\n const pubsub: PubSub | null =\n typeof pubsubOption === 'function'\n ? pubsubOption(logger.child({ name: 'pubsub' }))\n : (pubsubOption ?? null);\n const mailer = new Mailer(templates, email, {\n ...(config.emailBranding ?? { brandName: 'Headless LMS', baseUrl: config.adminAppUrl }),\n studentPortalUrl: config.studentPortalUrl,\n });\n\n const fileUrls = createFileUrls(config.authBaseURL, (key) => storage.publicUrl(key));\n const assets = new AssetsServiceImpl({\n storage,\n repo: new DrizzleAssetsRepository(db, assetsLogger),\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n assets: new DrizzleAssetsRepository(tx, assetsLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n memberImageMaxBytes: config.memberImageMaxBytes,\n memberFileMaxBytes: config.memberFileMaxBytes,\n downloadLinkExpirySeconds: config.deliveryExpirySeconds,\n fileUrls,\n logger: assetsLogger,\n });\n\n /*\n * Identity\n */\n const identityUoW = new DrizzleUnitOfWork(db, (tx) => ({\n identity: new DrizzleIdentityRepository(tx, logger.child({ name: 'identity' })),\n outbox: new DrizzleOutboxAppender(tx, logger.child({ name: 'identity' })),\n }));\n const identity = new IdentityServiceImpl({\n repo: new DrizzleIdentityRepository(db, logger.child({ name: 'identity_repo' })),\n logger: logger.child({ name: 'identity' }),\n uow: identityUoW,\n mailer,\n images: assets,\n fileUrls,\n });\n\n /*\n * Organization\n */\n const organizationsRepo = new DrizzleOrganizationsRepository(db, logger.child({ name: 'org' }));\n const organizationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n organizations: new DrizzleOrganizationsRepository(tx, logger.child({ name: 'org' })),\n roles: new DrizzleRolesRepository(tx),\n outbox: new DrizzleOutboxAppender(tx, logger.child({ name: 'org' })),\n }));\n const orgAdminRef: { current: BetterAuth | undefined } = { current: undefined };\n const platform = config.platform ? platformKeyCheck(config.platform) : null;\n const organizations: OrganizationServiceImpl = new OrganizationServiceImpl({\n repo: organizationsRepo,\n roles: new DrizzleRolesRepository(db),\n ...(options?.roles && { defaultRoles: options.roles }),\n apiKeys: (): ApiKeys => {\n if (!orgAdminRef.current) {\n throw new Error('api keys not initialised');\n }\n return orgAdminRef.current;\n },\n accounts: () => {\n if (!orgAdminRef.current) {\n throw new Error('accounts not initialised');\n }\n return orgAdminRef.current;\n },\n ...(config.platform && {\n platform: {\n email: config.platform.accountEmail,\n name: config.platform.accountName ?? 'Platform',\n },\n }),\n membersRepo: new DrizzleMembersRepository(db, logger.child({ name: 'org' })),\n people: identity,\n images: assets,\n uow: organizationsUow,\n orgAdmin: () => {\n if (!orgAdminRef.current) {\n throw new Error('orgAdmin not initialised');\n }\n return orgAdminRef.current;\n },\n sessions: () => {\n if (!orgAdminRef.current) {\n throw new Error('sessions not initialised');\n }\n return orgAdminRef.current;\n },\n logger: logger.child({ name: 'org' }),\n mailer,\n inviteUrls: { studentPortalUrl: config.studentPortalUrl, adminAppUrl: config.adminAppUrl },\n grants: (): GrantReader => entitlements,\n });\n\n /*\n * Settings\n */\n const settings = new SettingsService(\n new DrizzleSettingsRepository(db, logger.child({ name: 'settings' })),\n );\n // Content: reads on the root db; course writes + outbox append in one tx.\n const contentUow = new DrizzleUnitOfWork(db, (tx) => ({\n content: new DrizzleContentRepository(tx, contentLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const content = new ContentService({\n repo: new DrizzleContentRepository(db, contentLogger),\n uow: contentUow,\n libraryAssets: assets,\n files: assets,\n fileUrls,\n scheduler: automationEngine,\n logger: contentLogger,\n });\n const pushConfig = options?.adapters?.push ?? {};\n const pushSender = configuredPushSender(pushConfig, options?.adapters?.pushSender);\n const notificationRegistries = buildNotificationRegistries({\n declarations: NOTIFICATION_TYPES,\n defaults: { community: DEFAULT_COMMUNITY_CATEGORIES, org: DEFAULT_ORG_CATEGORIES },\n mailer: options?.adapters?.email ? mailer : null,\n emailOf: async (_orgId, userId) => (await identity.getUserById(userId))?.email ?? null,\n push: pushSender\n ? {\n sender: pushSender,\n activeDevices: (orgId, userId, since) => devices.activeDevices(orgId, userId, since),\n }\n : null,\n channels: options?.adapters?.notificationChannels ?? [],\n });\n const notificationCategories = {\n community: new CommunityNotificationCategories({\n communities: new DrizzleContentRepository(db, contentLogger),\n settings,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n logger: contentLogger,\n }),\n org: new OrgNotificationCategories({\n settings,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n logger: logger.child({ name: 'org' }),\n }),\n };\n const notificationCatalog = new NotificationCatalog({\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n });\n const notificationCategoryLists: NotificationCategories = {\n listFor: async (orgId, communityId) =>\n (communityId\n ? await notificationCategories.community.get(communityId, organizations.systemActor(orgId))\n : await notificationCategories.org.get(orgId, organizations.systemActor(orgId))\n ).categories,\n };\n // Entitlements: reads on the root db; writes + outbox append in one tx.\n const entitlementsUow = new DrizzleUnitOfWork(db, (tx) => ({\n entitlements: new DrizzleEntitlementsRepository(tx, entitlementsLogger),\n spaces: new DrizzleContentRepository(tx, contentLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const authors: AuthorPermissionReader = organizations;\n const entitlementsRepo = new DrizzleEntitlementsRepository(db, entitlementsLogger);\n const entitlements = new EntitlementsServiceImpl({\n repo: entitlementsRepo,\n content,\n permissions: organizations,\n uow: entitlementsUow,\n logger: entitlementsLogger,\n });\n const identityLogger = logger.child({ name: 'identity' });\n const notificationSettings = new NotificationSettingsServiceImpl({\n repo: new DrizzleNotificationSettingsRepository(db, identityLogger),\n categories: {\n communityList: async (orgId, communityId) =>\n (await notificationCategories.community.get(communityId, organizations.systemActor(orgId)))\n .categories,\n orgList: async (orgId) =>\n (await notificationCategories.org.get(orgId, organizations.systemActor(orgId))).categories,\n },\n access: {\n canOpen: (orgId, actor, communityId) => entitlements.isCommunityMember(communityId, actor),\n },\n channels: notificationRegistries.channels,\n logger: identityLogger,\n });\n const devices = new DevicesServiceImpl({\n repo: new DrizzleDevicesRepository(db, identityLogger),\n push: pushConfig,\n logger: identityLogger,\n });\n // Progress: report writes + outbox append in one tx; content supplies the\n // structure and completion rules the service evaluates against.\n const progressUow = new DrizzleUnitOfWork(db, (tx) => ({\n progress: new DrizzleProgressRepository(tx, progressLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const progress = new ProgressServiceImpl({\n repo: new DrizzleProgressRepository(db, progressLogger),\n content,\n access: entitlements,\n system: organizations,\n uow: progressUow,\n logger: progressLogger,\n });\n\n const postReads = new DrizzlePostRepository(db, discussionLogger);\n const discussionReads = new DrizzleDiscussionRepository(db, discussionLogger);\n const chatReads = new DrizzleChatRepository(db, discussionLogger);\n const subscriptionReads = new DrizzleSubscriptionsRepository(db, discussionLogger);\n const mentionReads = new DrizzleMentionsRepository(db, discussionLogger);\n const posts = new PostServiceImpl({\n repo: postReads,\n access: entitlements,\n content,\n authors,\n profiles: discussionReads,\n comments: discussionReads,\n subscriptions: subscriptionReads,\n mentions: mentionReads,\n images: assets,\n fileUrls,\n bodies,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n posts: new DrizzlePostRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n scheduler: automationEngine,\n logger: discussionLogger,\n });\n const chats = new ChatServiceImpl({\n repo: chatReads,\n access: entitlements,\n content,\n authors,\n profiles: discussionReads,\n subscriptions: subscriptionReads,\n mentions: mentionReads,\n images: assets,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n chats: new DrizzleChatRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n ...(pubsub && { pubsub }),\n ...(options?.live && { live: options.live }),\n logger: discussionLogger,\n });\n const discussion = new DiscussionServiceImpl({\n repo: discussionReads,\n access: entitlements,\n content: content,\n posts,\n spaces: entitlements,\n authors,\n mentions: mentionReads,\n fileUrls,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n discussion: new DrizzleDiscussionRepository(tx, discussionLogger),\n mentions: new DrizzleMentionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n settings,\n logger: discussionLogger,\n });\n\n const subscriptions = new SubscriptionServiceImpl({\n repo: subscriptionReads,\n access: entitlements,\n posts,\n chats,\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n subscriptions: new DrizzleSubscriptionsRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n logger: discussionLogger,\n });\n\n const digests = new DigestServiceImpl({\n schedules: { communitiesWithDigest: () => content.communitiesWithDigest() },\n runs: new DrizzleDigestRunRepository(db, discussionLogger),\n uow: new DrizzleUnitOfWork(db, (tx) => ({\n digests: new DrizzleDigestRunRepository(tx, discussionLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n })),\n logger: discussionLogger,\n });\n\n const notificationsRepo = new DrizzleNotificationsRepository(db);\n const notificationList = new NotificationsServiceImpl({\n repo: notificationsRepo,\n logger: automationsLogger,\n });\n const itemTitles = {\n discussion: new DiscussionItemTitles({\n posts,\n chats,\n comments: discussionReads,\n messages: chatReads,\n profiles: discussionReads,\n activities: content,\n access: entitlements,\n actors: organizations,\n }),\n content: new ContentItemTitles({ content, access: entitlements, organizations }),\n };\n\n const reporting = {\n admin: new AdminReportServiceImpl({\n repo: new DrizzleAdminRepository(db, reportingLogger),\n permissions: organizations,\n spaces: entitlements,\n events: content,\n fileUrls,\n }),\n member: new MemberReportServiceImpl({\n repo: new DrizzleMemberRepository(db, reportingLogger),\n content,\n progress,\n files: assets,\n fileUrls,\n access: entitlements,\n people: organizations,\n notifications: notificationList,\n titles: itemTitles,\n subscriptions,\n notificationCategories,\n notificationSettings,\n channels: notificationRegistries.channels,\n studentPortalUrl: config.studentPortalUrl,\n }),\n };\n\n const credentialStore = new DrizzleCredentialStore(\n db,\n config.credentialStoreKey,\n integrationsLogger,\n );\n // The integrations this deployment supports: the installation's declared\n // plugins folder (directory name = integration id), loaded at startup.\n // Connect/configure reject undeclared ids and validate config with the\n // integration's own schema. No folder → no integrations.\n const integrationsRegistry = await loadIntegrations(options?.pluginsDir, integrationsLogger);\n // Integrations: credential + connection writes + outbox append in one tx\n // (a tx-bound credential store instance shares the scope's transaction).\n const integrationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n connections: new DrizzleConnectionsRepository(tx, integrationsLogger),\n credentials: new DrizzleCredentialStore(tx, config.credentialStoreKey, integrationsLogger),\n outbox: new DrizzleOutboxAppender(tx, outboxLogger),\n }));\n const integrations = new IntegrationsServiceImpl({\n registry: integrationsRegistry,\n repo: new DrizzleConnectionsRepository(db, integrationsLogger),\n uow: integrationsUow,\n credentials: credentialStore,\n logger: integrationsLogger,\n });\n\n // Automations: run writes + outbox append in one tx; the engine drives\n // execution (register below), integrations supplies the loaded\n // integrations' actions for `available`.\n const automationsUow = new DrizzleUnitOfWork(db, (tx) => ({\n automations: new DrizzleAutomationsRepository(tx, automationsLogger),\n runs: new DrizzleAutomationRunsRepository(tx, automationsLogger),\n credentials: new DrizzleCredentialStore(tx, config.credentialStoreKey, automationsLogger),\n outbox: new DrizzleOutboxAppender(tx, automationsLogger),\n }));\n // Emails triggered by row-shaped events resolve their recipient and content\n // details at send time — the event carries ids only.\n const mailerLookups: MailerLookups = {\n orgUserPerson: async (orgId, orgUserId) => {\n const orgUser = await organizationsRepo.findOrgUserById(orgId, orgUserId);\n const user = orgUser ? await identity.getUserById(orgUser.userId) : null;\n return user ? { userId: user.id, email: user.email } : null;\n },\n contentInfo: async (orgId, contentId) => {\n const system = organizations.systemActor(orgId);\n const course = await content.findCourse(contentId, system);\n if (course) {\n return { id: course.id, title: course.title, type: course.type };\n }\n const download = await content.findDownload(contentId, system);\n if (download) {\n return { id: download.id, title: download.title, type: download.type };\n }\n const community = await content.findCommunity(contentId, system);\n if (community) {\n return { id: community.id, title: community.title, type: community.type };\n }\n const event = await content.findEvent(contentId, system);\n return event ? { id: event.id, title: event.title, type: event.type } : null;\n },\n };\n\n const runsRepo = new DrizzleAutomationRunsRepository(db, automationsLogger);\n const wired = communityMail({\n content,\n entitlements,\n organizations,\n identity,\n posts,\n subscriptions,\n discussion,\n chats,\n bodies,\n });\n const recipientReads = notificationReads({\n content,\n entitlements,\n organizations,\n identity,\n posts,\n subscriptions,\n discussion,\n chats,\n bodies,\n settings: notificationSettings,\n });\n const notificationProcessor = new NotificationProcessor({\n rules: RECIPIENT_RULES,\n reads: recipientReads,\n types: notificationRegistries.types,\n channels: notificationRegistries.channels,\n categories: notificationCategoryLists,\n settings: notificationSettings,\n devices,\n repo: notificationsRepo,\n engine: automationEngine,\n debounce: new SchedulerChatDebounce(automationEngine),\n logger: automationsLogger,\n });\n const automations = new AutomationsServiceImpl({\n repo: new DrizzleAutomationsRepository(db, automationsLogger),\n runsRepo,\n uow: automationsUow,\n engine: automationEngine,\n mailer,\n lookups: mailerLookups,\n integrations,\n systemActor: (orgId) => organizations.systemActor(orgId),\n credentials: credentialStore,\n webhooks:\n options?.adapters?.webhooks ??\n new FetchWebhookSender({ allowPrivateNetwork: config.webhookAllowPrivateNetwork }),\n mail: wired.mail,\n grants: wired.grants,\n delivery: new NotificationDelivery({\n repo: notificationsRepo,\n channels: notificationRegistries.channels,\n devices,\n logger: automationsLogger,\n }),\n processor: notificationProcessor,\n logger: automationsLogger,\n });\n automationEngine.register(automations);\n automationEngine.registerJobs(\n [\n ...discussionJobs({ posts, digests, logger: discussionLogger }),\n ...contentJobs({ events: content, logger: contentLogger }),\n ...entitlementsJobs({ grants: entitlements }),\n ...assetsJobs({ assets, orgs: organizations, logger: assetsLogger }),\n ...notificationJobs({\n processor: notificationProcessor,\n reads: recipientReads,\n logger: automationsLogger,\n }),\n ],\n [...discussionRecurringJobs, ...entitlementsRecurringJobs, ...assetsRecurringJobs],\n );\n\n const eventBusOption = options?.adapters?.eventBus;\n const eventBus: EventBus =\n typeof eventBusOption === 'function'\n ? eventBusOption(logger.child({ name: 'events' }))\n : (eventBusOption ?? new InMemoryEventBus());\n if (pubsub) {\n new ChatLiveForwarder({ pubsub, logger: discussionLogger }).attach(eventBus);\n }\n eventBus.subscribeAll((event) => automations.handle(event));\n const outboxConfig = resolveOutboxConfig(config.outbox);\n const outboxRelay = new PollingOutboxRelay(\n new DrizzleOutboxStore(db, outboxLogger),\n eventBus,\n outboxConfig,\n outboxLogger,\n );\n\n /*\n * Auth Adapter\n */\n const authLogger = logger.child({ name: 'auth' });\n // Hook failures otherwise surface through Better Auth's route as an opaque\n // 500 — log which hook died before rethrowing.\n const authHook =\n <A extends unknown[], R>(name: string, fn: (...args: A) => Promise<R>) =>\n async (...args: A): Promise<R> => {\n try {\n return await fn(...args);\n } catch (err) {\n authLogger.error(`auth hook ${name} failed`, { err });\n throw err;\n }\n };\n const mcp = mcpEndpoints(config);\n if (!mcp.oauthEnabled) {\n authLogger.warn(\n 'MCP OAuth is off: BETTER_AUTH_URL must be https or a loopback host; /mcp accepts API keys only',\n { authBaseURL: config.authBaseURL },\n );\n }\n const auth: BetterAuth = new BetterAuth({\n db,\n baseUrl: config.authBaseURL,\n mcp: {\n oauthEnabled: mcp.oauthEnabled,\n resource: mcp.resource,\n loginPage: mcp.loginPage,\n consentPage: mcp.consentPage,\n orgPickerPage: mcp.orgPickerPage,\n },\n secret: config.authSecret,\n trustedOrigins: config.trustedOrigins,\n hooks: {\n sendResetPassword: async ({ user, url }) => {\n await identity.sendPasswordReset({ email: user.email, url });\n },\n onPasswordReset: async ({ user }) => {\n await identity.sendPasswordChanged({ email: user.email });\n },\n sendMagicLink: authHook('sendMagicLink', async ({ email, url }) => {\n await identity.sendMagicLink({\n email,\n url,\n });\n }),\n beforeUserCreate: authHook('beforeUserCreate', async (user) => {\n const { email, name } = user;\n const [firstName, lastName] = name.split(' ');\n // Better Auth has not minted an id yet — the id returned below becomes\n // its account id. A person provisioned at invite time is linked instead of re-created.\n const domainUser = await identity.linkOrCreateUser({\n email,\n ...(firstName !== undefined && { firstName }),\n ...(lastName !== undefined && { lastName }),\n });\n\n return {\n data: {\n ...user,\n id: domainUser.id,\n },\n };\n }),\n beforeCreateSession: authHook('beforeCreateSession', async (session) => {\n const person = await identity.getUserByExternalId(session.userId);\n if (!person) {\n return;\n }\n if (organizations.isPlatformAccount(person.email)) {\n authLogger.warn('sign-in refused for the platform account', { userId: person.id });\n return false;\n }\n const orgs = await organizations.getOrgUsersForUser(person.id);\n if (orgs.length !== 1) {\n return;\n }\n // TODO - fix, why default to the first?\n const org = await organizations.getById(orgs[0]!.orgId);\n if (!org) {\n return;\n }\n\n return { data: { ...session, activeOrganizationId: org.id } };\n }),\n /*\n * We're making sure the domain org creates and using the same ID for better-auth org ID.\n */\n beforeCreateOrganization: authHook(\n 'beforeCreateOrganization',\n async ({ organization: org, user: baUser }) => {\n const domainOrg = await organizations.provisionOrganization({\n ownerId: baUser.id,\n ...org,\n logo: org.logo ?? undefined,\n });\n return { data: { ...org, id: domainOrg.id } };\n },\n ),\n beforeDeleteOrganization: authHook('beforeDeleteOrganization', async ({ organization }) => {\n await organizations.deleteOrganization(organization.id);\n }),\n beforeAddMember: authHook('beforeAddMember', async ({ member }) => {\n await organizations.addOrgUser({\n orgId: member.organizationId,\n userId: member.userId,\n });\n }),\n beforeRemoveMember: authHook('beforeRemoveMember', async ({ member }) => {\n await organizations.removeOrgUser(member.organizationId, member.userId);\n }),\n },\n logger: authLogger,\n cookieDomain: config.cookieDomain,\n secureCookies: config.secureCookies,\n });\n orgAdminRef.current = auth;\n\n return {\n auth,\n mcp,\n platform,\n identity,\n organizations,\n // Better Auth owns org writes; the same instance fulfils the OrgAdmin port.\n orgProvider: auth,\n content,\n entitlements,\n progress,\n discussion,\n posts,\n chats,\n subscriptions,\n assets,\n integrations,\n automations,\n notificationTypes: notificationRegistries.types,\n notificationChannels: notificationRegistries.channels,\n notificationCategories,\n notificationCatalog,\n notificationCategoryLists,\n notificationSettings,\n devices,\n notificationList,\n settings,\n reporting,\n storage,\n mailer,\n credentials: credentialStore,\n outboxRelay,\n eventBus,\n automationEngine,\n scheduler: automationEngine,\n pubsub,\n chatHub: chats.hub,\n logger,\n loggerInstance,\n requestContext: requestLogContext,\n // Better Auth owns the session store; the same instance fulfils SessionAdmin.\n sessions: auth,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2NA,MAAa,kBAA4C;CACvD,SAAS;CACT,gBAAgB;CAChB,WAAW;CACX,aAAa;AACf;AAEA,SAAgB,oBAAoB,SAAuB,CAAC,GAA6B;CACvF,MAAM,YAAY,OAAO,YACvB,OAAO,QAAQ,MAAM,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CAClE;CACA,OAAO;EAAE,GAAG;EAAiB,GAAG;CAAU;AAC5C;AAQA,MAAa,mBAA4C,EAAE,OAAO,OAAO;AAEzE,SAAgB,qBAAqB,SAAwB,CAAC,GAA4B;CACxF,OAAO,EAAE,OAAO,OAAO,SAAS,iBAAiB,MAAM;AACzD;AAyEA,eAAsB,eACpB,QACA,SACoB;CACpB,MAAM,EAAE,UAAU,gBAAgB,WAAW,iBAC3C,qBAAqB,OAAO,OAAO,CAAC,CAAC,KACvC;CAGA,MAAM,gBAAgB,OAAO,MAAM,EAAE,MAAM,UAAU,CAAC;CACtD,MAAM,qBAAqB,OAAO,MAAM,EAAE,MAAM,eAAe,CAAC;CAChE,MAAM,iBAAiB,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC;CACxD,MAAM,mBAAmB,OAAO,MAAM,EAAE,MAAM,aAAa,CAAC;CAC5D,MAAM,eAAe,OAAO,MAAM,EAAE,MAAM,SAAS,CAAC;CACpD,MAAM,qBAAqB,OAAO,MAAM,EAAE,MAAM,eAAe,CAAC;CAChE,MAAM,oBAAoB,OAAO,MAAM,EAAE,MAAM,cAAc,CAAC;CAC9D,MAAM,kBAAkB,OAAO,MAAM,EAAE,MAAM,YAAY,CAAC;CAC1D,MAAM,eAAe,OAAO,MAAM,EAAE,MAAM,SAAS,CAAC;CAGpD,MAAM,KAAK,SAAS,OAAO,aAAa,OAAO,MAAM,EAAE,MAAM,KAAK,CAAC,GAAG,OAAO,YAAY;CACzF,MAAM,QAAQ,SAAS,UAAU,SAAS,IAAI,aAAa,OAAO,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAC;CAC1F,MAAM,UACJ,SAAS,UAAU,WAAW,IAAI,eAAe,OAAO,MAAM,EAAE,MAAM,UAAU,CAAC,CAAC;CACpF,MAAM,YACJ,SAAS,UAAU,aAAa,IAAI,qBAAqB,OAAO,MAAM,EAAE,MAAM,QAAQ,CAAC,CAAC;CAC1F,MAAM,SAAS,SAAS,UAAU,cAAc,IAAI,gBAAgB;CACpE,MAAM,YAAY,SAAS,UAAU;CACrC,MAAM,mBACJ,OAAO,cAAc,aACjB,UAAU,iBAAiB,IAC1B,aAAa,IAAI,uBAAuB,iBAAiB;CAChE,MAAM,eAAe,SAAS,UAAU;CACxC,MAAM,SACJ,OAAO,iBAAiB,aACpB,aAAa,OAAO,MAAM,EAAE,MAAM,SAAS,CAAC,CAAC,IAC5C,gBAAgB;CACvB,MAAM,SAAS,IAAI,OAAO,WAAW,OAAO;EAC1C,GAAI,OAAO,iBAAiB;GAAE,WAAW;GAAgB,SAAS,OAAO;EAAY;EACrF,kBAAkB,OAAO;CAC3B,CAAC;CAED,MAAM,WAAW,eAAe,OAAO,cAAc,QAAQ,QAAQ,UAAU,GAAG,CAAC;CACnF,MAAM,SAAS,IAAI,kBAAkB;EACnC;EACA,MAAM,IAAI,wBAAwB,IAAI,YAAY;EAClD,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,QAAQ,IAAI,wBAAwB,IAAI,YAAY;GACpD,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF,qBAAqB,OAAO;EAC5B,oBAAoB,OAAO;EAC3B,2BAA2B,OAAO;EAClC;EACA,QAAQ;CACV,CAAC;CAKD,MAAM,cAAc,IAAI,kBAAkB,KAAK,QAAQ;EACrD,UAAU,IAAI,0BAA0B,IAAI,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC,CAAC;EAC9E,QAAQ,IAAI,sBAAsB,IAAI,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC,CAAC;CAC1E,EAAE;CACF,MAAM,WAAW,IAAI,oBAAoB;EACvC,MAAM,IAAI,0BAA0B,IAAI,OAAO,MAAM,EAAE,MAAM,gBAAgB,CAAC,CAAC;EAC/E,QAAQ,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC;EACzC,KAAK;EACL;EACA,QAAQ;EACR;CACF,CAAC;CAKD,MAAM,oBAAoB,IAAI,+BAA+B,IAAI,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC;CAC9F,MAAM,mBAAmB,IAAI,kBAAkB,KAAK,QAAQ;EAC1D,eAAe,IAAI,+BAA+B,IAAI,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC;EACnF,OAAO,IAAI,uBAAuB,EAAE;EACpC,QAAQ,IAAI,sBAAsB,IAAI,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC;CACrE,EAAE;CACF,MAAM,cAAmD,EAAE,SAAS,KAAA,EAAU;CAC9E,MAAM,WAAW,OAAO,WAAW,iBAAiB,OAAO,QAAQ,IAAI;CACvE,MAAM,gBAAyC,IAAI,wBAAwB;EACzE,MAAM;EACN,OAAO,IAAI,uBAAuB,EAAE;EACpC,GAAI,SAAS,SAAS,EAAE,cAAc,QAAQ,MAAM;EACpD,eAAwB;GACtB,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,0BAA0B;GAE5C,OAAO,YAAY;EACrB;EACA,gBAAgB;GACd,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,0BAA0B;GAE5C,OAAO,YAAY;EACrB;EACA,GAAI,OAAO,YAAY,EACrB,UAAU;GACR,OAAO,OAAO,SAAS;GACvB,MAAM,OAAO,SAAS,eAAe;EACvC,EACF;EACA,aAAa,IAAI,yBAAyB,IAAI,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC;EAC3E,QAAQ;EACR,QAAQ;EACR,KAAK;EACL,gBAAgB;GACd,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,0BAA0B;GAE5C,OAAO,YAAY;EACrB;EACA,gBAAgB;GACd,IAAI,CAAC,YAAY,SACf,MAAM,IAAI,MAAM,0BAA0B;GAE5C,OAAO,YAAY;EACrB;EACA,QAAQ,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC;EACpC;EACA,YAAY;GAAE,kBAAkB,OAAO;GAAkB,aAAa,OAAO;EAAY;EACzF,cAA2B;CAC7B,CAAC;CAKD,MAAM,WAAW,IAAI,gBACnB,IAAI,0BAA0B,IAAI,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC,CAAC,CACtE;CAEA,MAAM,aAAa,IAAI,kBAAkB,KAAK,QAAQ;EACpD,SAAS,IAAI,yBAAyB,IAAI,aAAa;EACvD,QAAQ,IAAI,sBAAsB,IAAI,YAAY;CACpD,EAAE;CACF,MAAM,UAAU,IAAI,eAAe;EACjC,MAAM,IAAI,yBAAyB,IAAI,aAAa;EACpD,KAAK;EACL,eAAe;EACf,OAAO;EACP;EACA,WAAW;EACX,QAAQ;CACV,CAAC;CACD,MAAM,aAAa,SAAS,UAAU,QAAQ,CAAC;CAC/C,MAAM,aAAa,qBAAqB,YAAY,SAAS,UAAU,UAAU;CACjF,MAAM,yBAAyB,4BAA4B;EACzD,cAAc;EACd,UAAU;GAAE,WAAW;GAA8B,KAAK;EAAuB;EACjF,QAAQ,SAAS,UAAU,QAAQ,SAAS;EAC5C,SAAS,OAAO,QAAQ,YAAY,MAAM,SAAS,YAAY,MAAM,EAAA,EAAI,SAAS;EAClF,MAAM,aACF;GACE,QAAQ;GACR,gBAAgB,OAAO,QAAQ,UAAU,QAAQ,cAAc,OAAO,QAAQ,KAAK;EACrF,IACA;EACJ,UAAU,SAAS,UAAU,wBAAwB,CAAC;CACxD,CAAC;CACD,MAAM,yBAAyB;EAC7B,WAAW,IAAI,gCAAgC;GAC7C,aAAa,IAAI,yBAAyB,IAAI,aAAa;GAC3D;GACA,OAAO,uBAAuB;GAC9B,UAAU,uBAAuB;GACjC,QAAQ;EACV,CAAC;EACD,KAAK,IAAI,0BAA0B;GACjC;GACA,OAAO,uBAAuB;GAC9B,UAAU,uBAAuB;GACjC,QAAQ,OAAO,MAAM,EAAE,MAAM,MAAM,CAAC;EACtC,CAAC;CACH;CACA,MAAM,sBAAsB,IAAI,oBAAoB;EAClD,OAAO,uBAAuB;EAC9B,UAAU,uBAAuB;CACnC,CAAC;CACD,MAAM,4BAAoD,EACxD,SAAS,OAAO,OAAO,iBACpB,cACG,MAAM,uBAAuB,UAAU,IAAI,aAAa,cAAc,YAAY,KAAK,CAAC,IACxF,MAAM,uBAAuB,IAAI,IAAI,OAAO,cAAc,YAAY,KAAK,CAAC,EAAA,CAC9E,WACN;CAEA,MAAM,kBAAkB,IAAI,kBAAkB,KAAK,QAAQ;EACzD,cAAc,IAAI,8BAA8B,IAAI,kBAAkB;EACtE,QAAQ,IAAI,yBAAyB,IAAI,aAAa;EACtD,QAAQ,IAAI,sBAAsB,IAAI,YAAY;CACpD,EAAE;CACF,MAAM,UAAkC;CAExC,MAAM,eAAe,IAAI,wBAAwB;EAC/C,MAAM,IAFqB,8BAA8B,IAAI,kBAExC;EACrB;EACA,aAAa;EACb,KAAK;EACL,QAAQ;CACV,CAAC;CACD,MAAM,iBAAiB,OAAO,MAAM,EAAE,MAAM,WAAW,CAAC;CACxD,MAAM,uBAAuB,IAAI,gCAAgC;EAC/D,MAAM,IAAI,sCAAsC,IAAI,cAAc;EAClE,YAAY;GACV,eAAe,OAAO,OAAO,iBAC1B,MAAM,uBAAuB,UAAU,IAAI,aAAa,cAAc,YAAY,KAAK,CAAC,EAAA,CACtF;GACL,SAAS,OAAO,WACb,MAAM,uBAAuB,IAAI,IAAI,OAAO,cAAc,YAAY,KAAK,CAAC,EAAA,CAAG;EACpF;EACA,QAAQ,EACN,UAAU,OAAO,OAAO,gBAAgB,aAAa,kBAAkB,aAAa,KAAK,EAC3F;EACA,UAAU,uBAAuB;EACjC,QAAQ;CACV,CAAC;CACD,MAAM,UAAU,IAAI,mBAAmB;EACrC,MAAM,IAAI,yBAAyB,IAAI,cAAc;EACrD,MAAM;EACN,QAAQ;CACV,CAAC;CAGD,MAAM,cAAc,IAAI,kBAAkB,KAAK,QAAQ;EACrD,UAAU,IAAI,0BAA0B,IAAI,cAAc;EAC1D,QAAQ,IAAI,sBAAsB,IAAI,YAAY;CACpD,EAAE;CACF,MAAM,WAAW,IAAI,oBAAoB;EACvC,MAAM,IAAI,0BAA0B,IAAI,cAAc;EACtD;EACA,QAAQ;EACR,QAAQ;EACR,KAAK;EACL,QAAQ;CACV,CAAC;CAED,MAAM,YAAY,IAAI,sBAAsB,IAAI,gBAAgB;CAChE,MAAM,kBAAkB,IAAI,4BAA4B,IAAI,gBAAgB;CAC5E,MAAM,YAAY,IAAI,sBAAsB,IAAI,gBAAgB;CAChE,MAAM,oBAAoB,IAAI,+BAA+B,IAAI,gBAAgB;CACjF,MAAM,eAAe,IAAI,0BAA0B,IAAI,gBAAgB;CACvE,MAAM,QAAQ,IAAI,gBAAgB;EAChC,MAAM;EACN,QAAQ;EACR;EACA;EACA,UAAU;EACV,UAAU;EACV,eAAe;EACf,UAAU;EACV,QAAQ;EACR;EACA;EACA,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,OAAO,IAAI,sBAAsB,IAAI,gBAAgB;GACrD,UAAU,IAAI,0BAA0B,IAAI,gBAAgB;GAC5D,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF,WAAW;EACX,QAAQ;CACV,CAAC;CACD,MAAM,QAAQ,IAAI,gBAAgB;EAChC,MAAM;EACN,QAAQ;EACR;EACA;EACA,UAAU;EACV,eAAe;EACf,UAAU;EACV,QAAQ;EACR;EACA,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,OAAO,IAAI,sBAAsB,IAAI,gBAAgB;GACrD,UAAU,IAAI,0BAA0B,IAAI,gBAAgB;GAC5D,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF,GAAI,UAAU,EAAE,OAAO;EACvB,GAAI,SAAS,QAAQ,EAAE,MAAM,QAAQ,KAAK;EAC1C,QAAQ;CACV,CAAC;CACD,MAAM,aAAa,IAAI,sBAAsB;EAC3C,MAAM;EACN,QAAQ;EACC;EACT;EACA,QAAQ;EACR;EACA,UAAU;EACV;EACA,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,YAAY,IAAI,4BAA4B,IAAI,gBAAgB;GAChE,UAAU,IAAI,0BAA0B,IAAI,gBAAgB;GAC5D,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF;EACA,QAAQ;CACV,CAAC;CAED,MAAM,gBAAgB,IAAI,wBAAwB;EAChD,MAAM;EACN,QAAQ;EACR;EACA;EACA,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,eAAe,IAAI,+BAA+B,IAAI,gBAAgB;GACtE,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF,QAAQ;CACV,CAAC;CAED,MAAM,UAAU,IAAI,kBAAkB;EACpC,WAAW,EAAE,6BAA6B,QAAQ,sBAAsB,EAAE;EAC1E,MAAM,IAAI,2BAA2B,IAAI,gBAAgB;EACzD,KAAK,IAAI,kBAAkB,KAAK,QAAQ;GACtC,SAAS,IAAI,2BAA2B,IAAI,gBAAgB;GAC5D,QAAQ,IAAI,sBAAsB,IAAI,YAAY;EACpD,EAAE;EACF,QAAQ;CACV,CAAC;CAED,MAAM,oBAAoB,IAAI,+BAA+B,EAAE;CAC/D,MAAM,mBAAmB,IAAI,yBAAyB;EACpD,MAAM;EACN,QAAQ;CACV,CAAC;CACD,MAAM,aAAa;EACjB,YAAY,IAAI,qBAAqB;GACnC;GACA;GACA,UAAU;GACV,UAAU;GACV,UAAU;GACV,YAAY;GACZ,QAAQ;GACR,QAAQ;EACV,CAAC;EACD,SAAS,IAAI,kBAAkB;GAAE;GAAS,QAAQ;GAAc;EAAc,CAAC;CACjF;CAEA,MAAM,YAAY;EAChB,OAAO,IAAI,uBAAuB;GAChC,MAAM,IAAI,uBAAuB,IAAI,eAAe;GACpD,aAAa;GACb,QAAQ;GACR,QAAQ;GACR;EACF,CAAC;EACD,QAAQ,IAAI,wBAAwB;GAClC,MAAM,IAAI,wBAAwB,IAAI,eAAe;GACrD;GACA;GACA,OAAO;GACP;GACA,QAAQ;GACR,QAAQ;GACR,eAAe;GACf,QAAQ;GACR;GACA;GACA;GACA,UAAU,uBAAuB;GACjC,kBAAkB,OAAO;EAC3B,CAAC;CACH;CAEA,MAAM,kBAAkB,IAAI,uBAC1B,IACA,OAAO,oBACP,kBACF;CAKA,MAAM,uBAAuB,MAAM,iBAAiB,SAAS,YAAY,kBAAkB;CAG3F,MAAM,kBAAkB,IAAI,kBAAkB,KAAK,QAAQ;EACzD,aAAa,IAAI,6BAA6B,IAAI,kBAAkB;EACpE,aAAa,IAAI,uBAAuB,IAAI,OAAO,oBAAoB,kBAAkB;EACzF,QAAQ,IAAI,sBAAsB,IAAI,YAAY;CACpD,EAAE;CACF,MAAM,eAAe,IAAI,wBAAwB;EAC/C,UAAU;EACV,MAAM,IAAI,6BAA6B,IAAI,kBAAkB;EAC7D,KAAK;EACL,aAAa;EACb,QAAQ;CACV,CAAC;CAKD,MAAM,iBAAiB,IAAI,kBAAkB,KAAK,QAAQ;EACxD,aAAa,IAAI,6BAA6B,IAAI,iBAAiB;EACnE,MAAM,IAAI,gCAAgC,IAAI,iBAAiB;EAC/D,aAAa,IAAI,uBAAuB,IAAI,OAAO,oBAAoB,iBAAiB;EACxF,QAAQ,IAAI,sBAAsB,IAAI,iBAAiB;CACzD,EAAE;CAGF,MAAM,gBAA+B;EACnC,eAAe,OAAO,OAAO,cAAc;GACzC,MAAM,UAAU,MAAM,kBAAkB,gBAAgB,OAAO,SAAS;GACxE,MAAM,OAAO,UAAU,MAAM,SAAS,YAAY,QAAQ,MAAM,IAAI;GACpE,OAAO,OAAO;IAAE,QAAQ,KAAK;IAAI,OAAO,KAAK;GAAM,IAAI;EACzD;EACA,aAAa,OAAO,OAAO,cAAc;GACvC,MAAM,SAAS,cAAc,YAAY,KAAK;GAC9C,MAAM,SAAS,MAAM,QAAQ,WAAW,WAAW,MAAM;GACzD,IAAI,QACF,OAAO;IAAE,IAAI,OAAO;IAAI,OAAO,OAAO;IAAO,MAAM,OAAO;GAAK;GAEjE,MAAM,WAAW,MAAM,QAAQ,aAAa,WAAW,MAAM;GAC7D,IAAI,UACF,OAAO;IAAE,IAAI,SAAS;IAAI,OAAO,SAAS;IAAO,MAAM,SAAS;GAAK;GAEvE,MAAM,YAAY,MAAM,QAAQ,cAAc,WAAW,MAAM;GAC/D,IAAI,WACF,OAAO;IAAE,IAAI,UAAU;IAAI,OAAO,UAAU;IAAO,MAAM,UAAU;GAAK;GAE1E,MAAM,QAAQ,MAAM,QAAQ,UAAU,WAAW,MAAM;GACvD,OAAO,QAAQ;IAAE,IAAI,MAAM;IAAI,OAAO,MAAM;IAAO,MAAM,MAAM;GAAK,IAAI;EAC1E;CACF;CAEA,MAAM,WAAW,IAAI,gCAAgC,IAAI,iBAAiB;CAC1E,MAAM,QAAQ,cAAc;EAC1B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,MAAM,iBAAiB,kBAAkB;EACvC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,UAAU;CACZ,CAAC;CACD,MAAM,wBAAwB,IAAI,sBAAsB;EACtD,OAAO;EACP,OAAO;EACP,OAAO,uBAAuB;EAC9B,UAAU,uBAAuB;EACjC,YAAY;EACZ,UAAU;EACV;EACA,MAAM;EACN,QAAQ;EACR,UAAU,IAAI,sBAAsB,gBAAgB;EACpD,QAAQ;CACV,CAAC;CACD,MAAM,cAAc,IAAI,uBAAuB;EAC7C,MAAM,IAAI,6BAA6B,IAAI,iBAAiB;EAC5D;EACA,KAAK;EACL,QAAQ;EACR;EACA,SAAS;EACT;EACA,cAAc,UAAU,cAAc,YAAY,KAAK;EACvD,aAAa;EACb,UACE,SAAS,UAAU,YACnB,IAAI,mBAAmB,EAAE,qBAAqB,OAAO,2BAA2B,CAAC;EACnF,MAAM,MAAM;EACZ,QAAQ,MAAM;EACd,UAAU,IAAI,qBAAqB;GACjC,MAAM;GACN,UAAU,uBAAuB;GACjC;GACA,QAAQ;EACV,CAAC;EACD,WAAW;EACX,QAAQ;CACV,CAAC;CACD,iBAAiB,SAAS,WAAW;CACrC,iBAAiB,aACf;EACE,GAAG,eAAe;GAAE;GAAO;GAAS,QAAQ;EAAiB,CAAC;EAC9D,GAAG,YAAY;GAAE,QAAQ;GAAS,QAAQ;EAAc,CAAC;EACzD,GAAG,iBAAiB,EAAE,QAAQ,aAAa,CAAC;EAC5C,GAAG,WAAW;GAAE;GAAQ,MAAM;GAAe,QAAQ;EAAa,CAAC;EACnE,GAAG,iBAAiB;GAClB,WAAW;GACX,OAAO;GACP,QAAQ;EACV,CAAC;CACH,GACA;EAAC,GAAG;EAAyB,GAAG;EAA2B,GAAG;CAAmB,CACnF;CAEA,MAAM,iBAAiB,SAAS,UAAU;CAC1C,MAAM,WACJ,OAAO,mBAAmB,aACtB,eAAe,OAAO,MAAM,EAAE,MAAM,SAAS,CAAC,CAAC,IAC9C,kBAAkB,IAAI,iBAAiB;CAC9C,IAAI,QACF,IAAI,kBAAkB;EAAE;EAAQ,QAAQ;CAAiB,CAAC,CAAC,CAAC,OAAO,QAAQ;CAE7E,SAAS,cAAc,UAAU,YAAY,OAAO,KAAK,CAAC;CAC1D,MAAM,eAAe,oBAAoB,OAAO,MAAM;CACtD,MAAM,cAAc,IAAI,mBACtB,IAAI,mBAAmB,IAAI,YAAY,GACvC,UACA,cACA,YACF;CAKA,MAAM,aAAa,OAAO,MAAM,EAAE,MAAM,OAAO,CAAC;CAGhD,MAAM,YACqB,MAAc,OACvC,OAAO,GAAG,SAAwB;EAChC,IAAI;GACF,OAAO,MAAM,GAAG,GAAG,IAAI;EACzB,SAAS,KAAK;GACZ,WAAW,MAAM,aAAa,KAAK,UAAU,EAAE,IAAI,CAAC;GACpD,MAAM;EACR;CACF;CACF,MAAM,MAAM,aAAa,MAAM;CAC/B,IAAI,CAAC,IAAI,cACP,WAAW,KACT,kGACA,EAAE,aAAa,OAAO,YAAY,CACpC;CAEF,MAAM,OAAmB,IAAI,WAAW;EACtC;EACA,SAAS,OAAO;EAChB,KAAK;GACH,cAAc,IAAI;GAClB,UAAU,IAAI;GACd,WAAW,IAAI;GACf,aAAa,IAAI;GACjB,eAAe,IAAI;EACrB;EACA,QAAQ,OAAO;EACf,gBAAgB,OAAO;EACvB,OAAO;GACL,mBAAmB,OAAO,EAAE,MAAM,UAAU;IAC1C,MAAM,SAAS,kBAAkB;KAAE,OAAO,KAAK;KAAO;IAAI,CAAC;GAC7D;GACA,iBAAiB,OAAO,EAAE,WAAW;IACnC,MAAM,SAAS,oBAAoB,EAAE,OAAO,KAAK,MAAM,CAAC;GAC1D;GACA,eAAe,SAAS,iBAAiB,OAAO,EAAE,OAAO,UAAU;IACjE,MAAM,SAAS,cAAc;KAC3B;KACA;IACF,CAAC;GACH,CAAC;GACD,kBAAkB,SAAS,oBAAoB,OAAO,SAAS;IAC7D,MAAM,EAAE,OAAO,SAAS;IACxB,MAAM,CAAC,WAAW,YAAY,KAAK,MAAM,GAAG;IAG5C,MAAM,aAAa,MAAM,SAAS,iBAAiB;KACjD;KACA,GAAI,cAAc,KAAA,KAAa,EAAE,UAAU;KAC3C,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;IAC3C,CAAC;IAED,OAAO,EACL,MAAM;KACJ,GAAG;KACH,IAAI,WAAW;IACjB,EACF;GACF,CAAC;GACD,qBAAqB,SAAS,uBAAuB,OAAO,YAAY;IACtE,MAAM,SAAS,MAAM,SAAS,oBAAoB,QAAQ,MAAM;IAChE,IAAI,CAAC,QACH;IAEF,IAAI,cAAc,kBAAkB,OAAO,KAAK,GAAG;KACjD,WAAW,KAAK,4CAA4C,EAAE,QAAQ,OAAO,GAAG,CAAC;KACjF,OAAO;IACT;IACA,MAAM,OAAO,MAAM,cAAc,mBAAmB,OAAO,EAAE;IAC7D,IAAI,KAAK,WAAW,GAClB;IAGF,MAAM,MAAM,MAAM,cAAc,QAAQ,KAAK,EAAE,CAAE,KAAK;IACtD,IAAI,CAAC,KACH;IAGF,OAAO,EAAE,MAAM;KAAE,GAAG;KAAS,sBAAsB,IAAI;IAAG,EAAE;GAC9D,CAAC;GAID,0BAA0B,SACxB,4BACA,OAAO,EAAE,cAAc,KAAK,MAAM,aAAa;IAC7C,MAAM,YAAY,MAAM,cAAc,sBAAsB;KAC1D,SAAS,OAAO;KAChB,GAAG;KACH,MAAM,IAAI,QAAQ,KAAA;IACpB,CAAC;IACD,OAAO,EAAE,MAAM;KAAE,GAAG;KAAK,IAAI,UAAU;IAAG,EAAE;GAC9C,CACF;GACA,0BAA0B,SAAS,4BAA4B,OAAO,EAAE,mBAAmB;IACzF,MAAM,cAAc,mBAAmB,aAAa,EAAE;GACxD,CAAC;GACD,iBAAiB,SAAS,mBAAmB,OAAO,EAAE,aAAa;IACjE,MAAM,cAAc,WAAW;KAC7B,OAAO,OAAO;KACd,QAAQ,OAAO;IACjB,CAAC;GACH,CAAC;GACD,oBAAoB,SAAS,sBAAsB,OAAO,EAAE,aAAa;IACvE,MAAM,cAAc,cAAc,OAAO,gBAAgB,OAAO,MAAM;GACxE,CAAC;EACH;EACA,QAAQ;EACR,cAAc,OAAO;EACrB,eAAe,OAAO;CACxB,CAAC;CACD,YAAY,UAAU;CAEtB,OAAO;EACL;EACA;EACA;EACA;EACA;EAEA,aAAa;EACb;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,mBAAmB,uBAAuB;EAC1C,sBAAsB,uBAAuB;EAC7C;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,aAAa;EACb;EACA;EACA;EACA,WAAW;EACX;EACA,SAAS,MAAM;EACf;EACA;EACA,gBAAgB;EAEhB,UAAU;CACZ;AACF"}
@@ -66,7 +66,7 @@ const communityTools = [
66
66
  defineTool({
67
67
  name: "update_space",
68
68
  title: "Update space",
69
- description: "Change a space name, description, access rule, capacity or settings. settings merges into the current settings. Fields left out stay as they are.",
69
+ description: "Change a space name, description, access rule, capacity, settings, cover, background or icon. settings merges into the current settings. Each image must be a ready image from list_media; null clears it. Fields left out stay as they are.",
70
70
  kind: "write",
71
71
  idempotent: true,
72
72
  openWorld: false,
@@ -1 +1 @@
1
- {"version":3,"file":"communities.js","names":[],"sources":["../../../../src/http/mcp/tools/communities.ts"],"sourcesContent":["import {\n CommunitiesQuery,\n CommunityIdParam,\n CommunitySpaceParam,\n CreateCommunity,\n CreateSpace,\n OrderedIds,\n PatchDmSettings,\n SetSpaceTopics,\n SpaceLinkBody,\n SpaceLinkParam,\n UpdateCommunity,\n UpdateSpace,\n} from '../../schemas/index.js';\nimport { defineTool, type Tool } from '../tool.js';\n\nexport const communityTools: Tool[] = [\n defineTool({\n name: 'list_communities',\n title: 'List communities',\n description:\n 'Search and page through the communities, drafts included, each with its status and direct-message settings. Filter by status, search by title.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: CommunitiesQuery,\n call: (input, { container, caller }) => container.content.listCommunities(input, caller.actor),\n }),\n defineTool({\n name: 'list_spaces',\n title: 'List spaces',\n description:\n 'The spaces of a community, in order, each with its access rule, topics and links.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: CommunityIdParam,\n call: ({ communityId }, { container, caller }) =>\n container.content.listSpaces(communityId, caller.actor),\n }),\n defineTool({\n name: 'create_community',\n title: 'Create community',\n description:\n 'Create a draft community with the default direct-message settings and no spaces. Add spaces with create_space and publish it with update_community.',\n kind: 'write',\n idempotent: false,\n openWorld: false,\n input: CreateCommunity,\n call: (input, { container, caller }) => container.content.createCommunity(input, caller.actor),\n }),\n defineTool({\n name: 'update_community',\n title: 'Update community',\n description:\n 'Change a community title, description or cover image, or publish and unpublish it with status. The cover must be a ready image from list_media; null clears it. Fields left out stay as they are.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: UpdateCommunity.extend(CommunityIdParam.shape),\n call: ({ communityId, ...input }, { container, caller }) =>\n container.content.updateCommunity(communityId, input, caller.actor),\n }),\n defineTool({\n name: 'update_community_messaging',\n title: 'Update community messaging',\n description:\n 'Change the direct-message settings of a community: who may message whom from now on. Settings left out stay as they are.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: PatchDmSettings.extend(CommunityIdParam.shape),\n call: ({ communityId, ...input }, { container, caller }) =>\n container.content.patchCommunitySettings(communityId, input, caller.actor),\n }),\n defineTool({\n name: 'create_space',\n title: 'Create space',\n description:\n 'Add a space at the end of a community. Its access rule decides who can open it: all members, by request or by invite. A space open to all members cannot have a capacity.',\n kind: 'write',\n idempotent: false,\n openWorld: false,\n input: CreateSpace.extend(CommunityIdParam.shape),\n call: ({ communityId, ...input }, { container, caller }) =>\n container.content.createSpace(communityId, input, caller.actor),\n }),\n defineTool({\n name: 'update_space',\n title: 'Update space',\n description:\n 'Change a space name, description, access rule, capacity or settings. settings merges into the current settings. Fields left out stay as they are.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: UpdateSpace.extend(CommunitySpaceParam.shape),\n call: ({ communityId, spaceId, ...input }, { container, caller }) =>\n container.content.updateSpace(communityId, spaceId, input, caller.actor),\n }),\n defineTool({\n name: 'reorder_spaces',\n title: 'Reorder spaces',\n description:\n 'Set the space order of a community. Pass every space id of the community in the new order.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: OrderedIds.extend(CommunityIdParam.shape),\n call: ({ communityId, ids }, { container, caller }) =>\n container.content.reorderSpaces(communityId, ids, caller.actor),\n }),\n defineTool({\n name: 'set_space_topics',\n title: 'Set space topics',\n description:\n 'Replace the topics of a space. An entry with an id keeps and renames that topic, one without adds a topic, and a topic left out is deleted. A topic that posts use cannot be deleted.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: SetSpaceTopics.extend(CommunitySpaceParam.shape),\n call: ({ communityId, spaceId, ...input }, { container, caller }) =>\n container.content.setSpaceTopics(communityId, spaceId, input, caller.actor),\n }),\n defineTool({\n name: 'add_space_link',\n title: 'Add space link',\n description:\n 'Link a course, download or community into a space, after its other links. Returns the space.',\n kind: 'write',\n idempotent: false,\n openWorld: false,\n input: SpaceLinkBody.extend(CommunitySpaceParam.shape),\n call: ({ communityId, spaceId, contentId }, { container, caller }) =>\n container.content.addSpaceLink(communityId, spaceId, contentId, caller.actor),\n }),\n defineTool({\n name: 'reorder_space_links',\n title: 'Reorder space links',\n description: 'Set the link order of a space. Pass every link id of the space in the new order.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: OrderedIds.extend(CommunitySpaceParam.shape),\n call: ({ communityId, spaceId, ids }, { container, caller }) =>\n container.content.reorderSpaceLinks(communityId, spaceId, ids, caller.actor),\n }),\n defineTool({\n name: 'remove_space_link',\n title: 'Remove space link',\n description:\n 'Take a link out of a space. The linked content itself is kept. Returns the space.',\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: SpaceLinkParam,\n call: ({ communityId, spaceId, linkId }, { container, caller }) =>\n container.content.removeSpaceLink(communityId, spaceId, linkId, caller.actor),\n }),\n defineTool({\n name: 'delete_space',\n title: 'Delete space',\n description: 'Permanently delete a space with its posts, comments and members.',\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: CommunitySpaceParam,\n call: ({ communityId, spaceId }, { container, caller }) =>\n container.content.deleteSpace(communityId, spaceId, caller.actor),\n }),\n defineTool({\n name: 'delete_community',\n title: 'Delete community',\n description:\n 'Permanently delete a community with its spaces. Links to it from other spaces are removed.',\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: CommunityIdParam,\n call: ({ communityId }, { container, caller }) =>\n container.content.deleteCommunity(communityId, caller.actor),\n }),\n defineTool({\n name: 'get_community_analytics',\n title: 'Get community analytics',\n description: 'Membership, activity and moderation counts for one community.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: CommunityIdParam,\n call: ({ communityId }, { container, caller }) =>\n container.reporting.admin.communityAnalytics(communityId, caller.actor),\n }),\n];\n"],"mappings":"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"}
1
+ {"version":3,"file":"communities.js","names":[],"sources":["../../../../src/http/mcp/tools/communities.ts"],"sourcesContent":["import {\n CommunitiesQuery,\n CommunityIdParam,\n CommunitySpaceParam,\n CreateCommunity,\n CreateSpace,\n OrderedIds,\n PatchDmSettings,\n SetSpaceTopics,\n SpaceLinkBody,\n SpaceLinkParam,\n UpdateCommunity,\n UpdateSpace,\n} from '../../schemas/index.js';\nimport { defineTool, type Tool } from '../tool.js';\n\nexport const communityTools: Tool[] = [\n defineTool({\n name: 'list_communities',\n title: 'List communities',\n description:\n 'Search and page through the communities, drafts included, each with its status and direct-message settings. Filter by status, search by title.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: CommunitiesQuery,\n call: (input, { container, caller }) => container.content.listCommunities(input, caller.actor),\n }),\n defineTool({\n name: 'list_spaces',\n title: 'List spaces',\n description:\n 'The spaces of a community, in order, each with its access rule, topics and links.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: CommunityIdParam,\n call: ({ communityId }, { container, caller }) =>\n container.content.listSpaces(communityId, caller.actor),\n }),\n defineTool({\n name: 'create_community',\n title: 'Create community',\n description:\n 'Create a draft community with the default direct-message settings and no spaces. Add spaces with create_space and publish it with update_community.',\n kind: 'write',\n idempotent: false,\n openWorld: false,\n input: CreateCommunity,\n call: (input, { container, caller }) => container.content.createCommunity(input, caller.actor),\n }),\n defineTool({\n name: 'update_community',\n title: 'Update community',\n description:\n 'Change a community title, description or cover image, or publish and unpublish it with status. The cover must be a ready image from list_media; null clears it. Fields left out stay as they are.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: UpdateCommunity.extend(CommunityIdParam.shape),\n call: ({ communityId, ...input }, { container, caller }) =>\n container.content.updateCommunity(communityId, input, caller.actor),\n }),\n defineTool({\n name: 'update_community_messaging',\n title: 'Update community messaging',\n description:\n 'Change the direct-message settings of a community: who may message whom from now on. Settings left out stay as they are.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: PatchDmSettings.extend(CommunityIdParam.shape),\n call: ({ communityId, ...input }, { container, caller }) =>\n container.content.patchCommunitySettings(communityId, input, caller.actor),\n }),\n defineTool({\n name: 'create_space',\n title: 'Create space',\n description:\n 'Add a space at the end of a community. Its access rule decides who can open it: all members, by request or by invite. A space open to all members cannot have a capacity.',\n kind: 'write',\n idempotent: false,\n openWorld: false,\n input: CreateSpace.extend(CommunityIdParam.shape),\n call: ({ communityId, ...input }, { container, caller }) =>\n container.content.createSpace(communityId, input, caller.actor),\n }),\n defineTool({\n name: 'update_space',\n title: 'Update space',\n description:\n 'Change a space name, description, access rule, capacity, settings, cover, background or icon. settings merges into the current settings. Each image must be a ready image from list_media; null clears it. Fields left out stay as they are.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: UpdateSpace.extend(CommunitySpaceParam.shape),\n call: ({ communityId, spaceId, ...input }, { container, caller }) =>\n container.content.updateSpace(communityId, spaceId, input, caller.actor),\n }),\n defineTool({\n name: 'reorder_spaces',\n title: 'Reorder spaces',\n description:\n 'Set the space order of a community. Pass every space id of the community in the new order.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: OrderedIds.extend(CommunityIdParam.shape),\n call: ({ communityId, ids }, { container, caller }) =>\n container.content.reorderSpaces(communityId, ids, caller.actor),\n }),\n defineTool({\n name: 'set_space_topics',\n title: 'Set space topics',\n description:\n 'Replace the topics of a space. An entry with an id keeps and renames that topic, one without adds a topic, and a topic left out is deleted. A topic that posts use cannot be deleted.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: SetSpaceTopics.extend(CommunitySpaceParam.shape),\n call: ({ communityId, spaceId, ...input }, { container, caller }) =>\n container.content.setSpaceTopics(communityId, spaceId, input, caller.actor),\n }),\n defineTool({\n name: 'add_space_link',\n title: 'Add space link',\n description:\n 'Link a course, download or community into a space, after its other links. Returns the space.',\n kind: 'write',\n idempotent: false,\n openWorld: false,\n input: SpaceLinkBody.extend(CommunitySpaceParam.shape),\n call: ({ communityId, spaceId, contentId }, { container, caller }) =>\n container.content.addSpaceLink(communityId, spaceId, contentId, caller.actor),\n }),\n defineTool({\n name: 'reorder_space_links',\n title: 'Reorder space links',\n description: 'Set the link order of a space. Pass every link id of the space in the new order.',\n kind: 'write',\n idempotent: true,\n openWorld: false,\n input: OrderedIds.extend(CommunitySpaceParam.shape),\n call: ({ communityId, spaceId, ids }, { container, caller }) =>\n container.content.reorderSpaceLinks(communityId, spaceId, ids, caller.actor),\n }),\n defineTool({\n name: 'remove_space_link',\n title: 'Remove space link',\n description:\n 'Take a link out of a space. The linked content itself is kept. Returns the space.',\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: SpaceLinkParam,\n call: ({ communityId, spaceId, linkId }, { container, caller }) =>\n container.content.removeSpaceLink(communityId, spaceId, linkId, caller.actor),\n }),\n defineTool({\n name: 'delete_space',\n title: 'Delete space',\n description: 'Permanently delete a space with its posts, comments and members.',\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: CommunitySpaceParam,\n call: ({ communityId, spaceId }, { container, caller }) =>\n container.content.deleteSpace(communityId, spaceId, caller.actor),\n }),\n defineTool({\n name: 'delete_community',\n title: 'Delete community',\n description:\n 'Permanently delete a community with its spaces. Links to it from other spaces are removed.',\n kind: 'destructive',\n idempotent: true,\n openWorld: false,\n input: CommunityIdParam,\n call: ({ communityId }, { container, caller }) =>\n container.content.deleteCommunity(communityId, caller.actor),\n }),\n defineTool({\n name: 'get_community_analytics',\n title: 'Get community analytics',\n description: 'Membership, activity and moderation counts for one community.',\n kind: 'read',\n idempotent: true,\n openWorld: false,\n input: CommunityIdParam,\n call: ({ communityId }, { container, caller }) =>\n container.reporting.admin.communityAnalytics(communityId, caller.actor),\n }),\n];\n"],"mappings":"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"}
@@ -119,7 +119,7 @@ async function accessRoutes(app, container) {
119
119
  operationId: "createApiKey",
120
120
  tags,
121
121
  summary: "Create an API key",
122
- description: `Requires \`api_keys:write\`. The key acts as you in this organization with the permissions of the role given, never beyond what you hold at the time of each request. Send it in the \`x-api-key\` header or as \`Authorization: Bearer <key>\`; every route under /api and /mcp accepts it. 403 when the role holds a permission you hold nowhere; 404 when the organization has no such role. The key is returned once.`,
122
+ description: `Requires \`api_keys:write\` and a signed-in person: a request made with an API key or an app token gets 403. The key acts as you in this organization with the permissions of the role given, never beyond what you hold at the time of each request. Send it in the \`x-api-key\` header or as \`Authorization: Bearer <key>\`; every route under /api and /mcp accepts it. 403 when the role holds a permission you hold nowhere; 404 when the organization has no such role. The key is returned once.`,
123
123
  body: CreateApiKey,
124
124
  response: {
125
125
  201: CreatedApiKey,
@@ -1 +1 @@
1
- {"version":3,"file":"access.js","names":[],"sources":["../../../src/http/routes/access.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../app/container.js';\nimport {\n ApiKeyIdParam,\n ApiKeyList,\n ApiKeyRoleList,\n ClientIdParam,\n ConnectedAppList,\n CreateApiKey,\n CreatedApiKey,\n PermissionDefinitionList,\n ViewerPermissions,\n refusals,\n} from '../schemas/index.js';\nimport { resolveActor } from '../scope.js';\n\nexport async function accessRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Access'];\n const organizations = container.organizations;\n\n r.route({\n method: 'GET',\n url: '/api/access/permissions/list',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listPermissions',\n tags,\n summary: 'Every permission, with its label, description and level',\n description: `Any active member of the organization. For role screens, the key-creation screen and the consent screen.`,\n response: { 200: PermissionDefinitionList, ...refusals(403) },\n },\n handler: async (req) => {\n await resolveActor(container, req);\n return organizations.permissionList();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/permissions',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMyPermissions',\n tags,\n summary: 'Your effective permissions in this organization',\n description: `Any active member of the organization. Your org role, whether you own the organization, the permissions you hold on the organization, and the ones you hold on each content item.`,\n response: { 200: ViewerPermissions, ...refusals(403) },\n },\n handler: async (req) => organizations.viewerPermissions(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/apps',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listConnectedApps',\n tags,\n summary: 'Apps connected to this organization as you',\n description: `Any active member of the organization. The apps you connected to this organization, each with the scopes you granted and when.`,\n response: { 200: ConnectedAppList, ...refusals(403) },\n },\n handler: async (req) => organizations.listConnectedApps(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/access/apps/:clientId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'revokeConnectedApp',\n tags,\n summary: 'Disconnect an app',\n description: `Any active member of the organization. Revokes your consent and tokens for the app in this organization; its next call is refused.`,\n params: ClientIdParam,\n response: { 204: z.void(), ...refusals(403) },\n },\n handler: async (req, reply) => {\n await organizations.revokeConnectedApp(\n req.params.clientId,\n await resolveActor(container, req),\n );\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/api-keys',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listApiKeys',\n tags,\n summary: 'Your API keys for this organization',\n description: `Requires \\`api_keys:read\\`. Your keys for this organization with their role and expiry. The key itself is never returned again after it is created.`,\n response: { 200: ApiKeyList, ...refusals(403) },\n },\n handler: async (req) => organizations.listApiKeys(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/api-keys/roles',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listApiKeyRoles',\n tags,\n summary: 'The roles you can put on an API key',\n description: `Requires \\`api_keys:write\\`. The organization's roles whose every permission you hold somewhere.`,\n response: { 200: ApiKeyRoleList, ...refusals(403) },\n },\n handler: async (req) => organizations.listApiKeyRoles(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'POST',\n url: '/api/access/api-keys',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createApiKey',\n tags,\n summary: 'Create an API key',\n description: `Requires \\`api_keys:write\\`. The key acts as you in this organization with the permissions of the role given, never beyond what you hold at the time of each request. Send it in the \\`x-api-key\\` header or as \\`Authorization: Bearer <key>\\`; every route under /api and /mcp accepts it. 403 when the role holds a permission you hold nowhere; 404 when the organization has no such role. The key is returned once.`,\n body: CreateApiKey,\n response: { 201: CreatedApiKey, ...refusals(400, 403, 404) },\n },\n handler: async (req, reply) => {\n const created = await organizations.createApiKey(\n req.body,\n await resolveActor(container, req),\n );\n return reply.code(201).send(created);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/access/api-keys/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteApiKey',\n tags,\n summary: 'Revoke an API key',\n description: `Requires \\`api_keys:delete\\`. 404 when the key is not one of yours in this organization.`,\n params: ApiKeyIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n await organizations.deleteApiKey(req.params.id, await resolveActor(container, req));\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"access.js","names":[],"sources":["../../../src/http/routes/access.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../app/container.js';\nimport {\n ApiKeyIdParam,\n ApiKeyList,\n ApiKeyRoleList,\n ClientIdParam,\n ConnectedAppList,\n CreateApiKey,\n CreatedApiKey,\n PermissionDefinitionList,\n ViewerPermissions,\n refusals,\n} from '../schemas/index.js';\nimport { resolveActor } from '../scope.js';\n\nexport async function accessRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const tags = ['Access'];\n const organizations = container.organizations;\n\n r.route({\n method: 'GET',\n url: '/api/access/permissions/list',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listPermissions',\n tags,\n summary: 'Every permission, with its label, description and level',\n description: `Any active member of the organization. For role screens, the key-creation screen and the consent screen.`,\n response: { 200: PermissionDefinitionList, ...refusals(403) },\n },\n handler: async (req) => {\n await resolveActor(container, req);\n return organizations.permissionList();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/permissions',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'getMyPermissions',\n tags,\n summary: 'Your effective permissions in this organization',\n description: `Any active member of the organization. Your org role, whether you own the organization, the permissions you hold on the organization, and the ones you hold on each content item.`,\n response: { 200: ViewerPermissions, ...refusals(403) },\n },\n handler: async (req) => organizations.viewerPermissions(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/apps',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listConnectedApps',\n tags,\n summary: 'Apps connected to this organization as you',\n description: `Any active member of the organization. The apps you connected to this organization, each with the scopes you granted and when.`,\n response: { 200: ConnectedAppList, ...refusals(403) },\n },\n handler: async (req) => organizations.listConnectedApps(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/access/apps/:clientId',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'revokeConnectedApp',\n tags,\n summary: 'Disconnect an app',\n description: `Any active member of the organization. Revokes your consent and tokens for the app in this organization; its next call is refused.`,\n params: ClientIdParam,\n response: { 204: z.void(), ...refusals(403) },\n },\n handler: async (req, reply) => {\n await organizations.revokeConnectedApp(\n req.params.clientId,\n await resolveActor(container, req),\n );\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/api-keys',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listApiKeys',\n tags,\n summary: 'Your API keys for this organization',\n description: `Requires \\`api_keys:read\\`. Your keys for this organization with their role and expiry. The key itself is never returned again after it is created.`,\n response: { 200: ApiKeyList, ...refusals(403) },\n },\n handler: async (req) => organizations.listApiKeys(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'GET',\n url: '/api/access/api-keys/roles',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'listApiKeyRoles',\n tags,\n summary: 'The roles you can put on an API key',\n description: `Requires \\`api_keys:write\\`. The organization's roles whose every permission you hold somewhere.`,\n response: { 200: ApiKeyRoleList, ...refusals(403) },\n },\n handler: async (req) => organizations.listApiKeyRoles(await resolveActor(container, req)),\n });\n\n r.route({\n method: 'POST',\n url: '/api/access/api-keys',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'createApiKey',\n tags,\n summary: 'Create an API key',\n description: `Requires \\`api_keys:write\\` and a signed-in person: a request made with an API key or an app token gets 403. The key acts as you in this organization with the permissions of the role given, never beyond what you hold at the time of each request. Send it in the \\`x-api-key\\` header or as \\`Authorization: Bearer <key>\\`; every route under /api and /mcp accepts it. 403 when the role holds a permission you hold nowhere; 404 when the organization has no such role. The key is returned once.`,\n body: CreateApiKey,\n response: { 201: CreatedApiKey, ...refusals(400, 403, 404) },\n },\n handler: async (req, reply) => {\n const created = await organizations.createApiKey(\n req.body,\n await resolveActor(container, req),\n );\n return reply.code(201).send(created);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/access/api-keys/:id',\n preHandler: app.requireOrgSession,\n schema: {\n operationId: 'deleteApiKey',\n tags,\n summary: 'Revoke an API key',\n description: `Requires \\`api_keys:delete\\`. 404 when the key is not one of yours in this organization.`,\n params: ApiKeyIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n await organizations.deleteApiKey(req.params.id, await resolveActor(container, req));\n return reply.code(204).send();\n },\n });\n}\n"],"mappings":"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"}
@@ -350,7 +350,7 @@ async function communitiesRoutes(app, container) {
350
350
  operationId: "updateSpace",
351
351
  tags,
352
352
  summary: "Update a space",
353
- description: `Requires \`communities:write\`. Changes only the fields sent; \`settings\` merges into the current settings. An \`all_members\` space cannot have a capacity (409).`,
353
+ description: `Requires \`communities:write\`. Changes only the fields sent; \`settings\` merges into the current settings. An \`all_members\` space cannot have a capacity (409). \`coverAssetId\`, \`backgroundAssetId\` and \`iconAssetId\` take ready images from the media library, or null to clear one; without \`media:read\`, only images uploaded for the community (409 or 403 otherwise). In a published community the images are public.`,
354
354
  params: CommunitySpaceParam,
355
355
  body: UpdateSpace,
356
356
  response: {
@@ -1 +1 @@
1
- {"version":3,"file":"communities.js","names":[],"sources":["../../../src/http/routes/communities.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../app/container.js';\nimport { resolveActor } from '../scope.js';\nimport {\n Community,\n CommunityAnalytics,\n CommunityIdParam,\n CommunitySpaceParam,\n CommunitiesPage,\n CommunitiesQuery,\n CreateCommunity,\n CreateSpace,\n MessageQueue,\n NotificationCategoryList,\n OrderedIds,\n PatchCommunitySettings,\n ReplaceNotificationCategories,\n SetSpaceTopics,\n SpaceDetail,\n SpaceLinkBody,\n SpaceLinkParam,\n UpdateCommunity,\n UpdateSpace,\n refusals,\n} from '../schemas/index.js';\n\nexport async function communitiesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const content = container.content;\n const tags = ['Communities'];\n\n r.route({\n method: 'GET',\n url: '/api/communities',\n schema: {\n operationId: 'listCommunities',\n tags,\n summary: 'List communities',\n description: `Requires one of \\`communities:read\\`, \\`communities:moderate\\`. Pages every community in the organization, drafts included, filtered by \\`status\\` and searched by title.`,\n querystring: CommunitiesQuery,\n response: { 200: CommunitiesPage, ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.listCommunities(req.query, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities',\n schema: {\n operationId: 'createCommunity',\n tags,\n summary: 'Create a community',\n description: `Requires \\`communities:write\\`. The community starts as a draft with the default direct-message settings and no spaces. Members see it once it is published and they hold it.`,\n body: CreateCommunity,\n response: { 201: Community, ...refusals(403, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const community = await content.createCommunity(req.body, actor);\n req.log.info({ communityId: community.id }, 'community created');\n return reply.code(201).send(community);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId',\n schema: {\n operationId: 'getCommunity',\n tags,\n summary: 'Get a community',\n description: `Requires one of \\`communities:read\\`, \\`communities:moderate\\`. Any community of the organization, drafts included.`,\n params: CommunityIdParam,\n response: { 200: Community, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.getCommunity(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/communities/:communityId',\n schema: {\n operationId: 'updateCommunity',\n tags,\n summary: 'Update a community',\n description: `Requires \\`communities:write\\`. Changes only the fields sent. \\`thumbnailAssetId\\` must be a ready library image (409 otherwise); null clears it. A file it does not already use must be uploaded for it, or the caller needs \\`media:read\\` (403 otherwise).`,\n params: CommunityIdParam,\n body: UpdateCommunity,\n response: { 200: Community, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.updateCommunity(req.params.communityId, req.body, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId',\n schema: {\n operationId: 'deleteCommunity',\n tags,\n summary: 'Delete a community',\n description: `Requires \\`communities:delete\\`. Links to the community from other spaces are removed with it.`,\n params: CommunityIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await content.deleteCommunity(req.params.communityId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/communities/:communityId/settings',\n schema: {\n operationId: 'patchCommunitySettings',\n tags,\n summary: 'Change the direct-message and digest settings of a community',\n description: `Requires \\`communities:write\\`. Changes only the settings sent; the rest keep their values. The direct-message settings decide who may message whom from then on. \\`digest\\` sets when the community digest goes out, or \\`null\\` stops it; the first digest after a change goes out at the next slot.`,\n params: CommunityIdParam,\n body: PatchCommunitySettings,\n response: { 200: Community, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.patchCommunitySettings(req.params.communityId, req.body, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/notification-categories',\n schema: {\n operationId: 'getCommunityNotificationCategories',\n tags: ['Notifications'],\n summary: \"Get a community's notification categories\",\n description: `Requires \\`communities:read\\`. On the community. The list in use for the community, with \\`source\\` saying whether it is the default or a stored list.`,\n params: CommunityIdParam,\n response: { 200: NotificationCategoryList, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.notificationCategories.community.get(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/notification-categories',\n schema: {\n operationId: 'replaceCommunityNotificationCategories',\n tags: ['Notifications'],\n summary: \"Replace a community's notification categories\",\n description: `Requires \\`communities:write\\`. On the community. Stores the whole list for this community; other communities keep theirs. 422 when a key repeats, a type cannot be in a community list, a reason is not allowed for its type, a channel is not configured, \\`perSpace\\` is set on a type that cannot occur in a space, \\`spaceDefaults\\` appears without \\`perSpace\\` or names a channel the category does not offer, or earlier categories already cover everything a category covers.`,\n params: CommunityIdParam,\n body: ReplaceNotificationCategories,\n response: { 200: NotificationCategoryList, ...refusals(400, 403, 404, 422) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.notificationCategories.community.replace(\n req.params.communityId,\n req.body.categories,\n actor,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId/notification-categories',\n schema: {\n operationId: 'resetCommunityNotificationCategories',\n tags: ['Notifications'],\n summary: 'Go back to the default notification categories for a community',\n description: `Requires \\`communities:write\\`. On the community. Removes the stored list; the default applies again.`,\n params: CommunityIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await container.notificationCategories.community.reset(req.params.communityId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/publish',\n schema: {\n operationId: 'publishCommunity',\n tags,\n summary: 'Publish a community',\n description: `Requires \\`communities:write\\`. Makes the community visible to the people who hold it.`,\n params: CommunityIdParam,\n response: { 200: Community, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.publishCommunity(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/unpublish',\n schema: {\n operationId: 'unpublishCommunity',\n tags,\n summary: 'Unpublish a community',\n description: `Requires \\`communities:write\\`. Hides the community from members again; nothing in it is deleted.`,\n params: CommunityIdParam,\n response: { 200: Community, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.unpublishCommunity(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/analytics',\n schema: {\n operationId: 'getCommunityAnalytics',\n tags,\n summary: 'Analytics for a community',\n description: `Requires \\`communities:read\\`. Membership, activity and moderation counts for one community.`,\n params: CommunityIdParam,\n response: { 200: CommunityAnalytics, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.reporting.admin.communityAnalytics(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/message-queue',\n schema: {\n operationId: 'getCommunityMessageQueue',\n tags,\n summary: 'Open message flags in a community',\n description: `Requires \\`communities:moderate\\`. The messages in the community's chats with open flags, with the flags and the chat each is in. Holds at most the 100 oldest open flags; working the queue brings in the next ones.`,\n params: CommunityIdParam,\n response: { 200: MessageQueue, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.chats.messageQueue(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/spaces',\n schema: {\n operationId: 'listCommunitySpaces',\n tags,\n summary: 'List the spaces in a community',\n description: `Requires one of \\`communities:read\\`, \\`communities:moderate\\`. Every space in order, with its topics and links.`,\n params: CommunityIdParam,\n response: { 200: z.array(SpaceDetail), ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.listSpaces(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces',\n schema: {\n operationId: 'createSpace',\n tags,\n summary: 'Create a space',\n description: `Requires \\`communities:write\\`. The space is added last. An \\`all_members\\` space cannot have a capacity (409).`,\n params: CommunityIdParam,\n body: CreateSpace,\n response: { 201: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const space = await content.createSpace(req.params.communityId, req.body, actor);\n return reply.code(201).send(space);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/spaces/order',\n schema: {\n operationId: 'reorderSpaces',\n tags,\n summary: 'Reorder the spaces in a community',\n description: `Requires \\`communities:write\\`. Send every space id of the community exactly once (409 otherwise).`,\n params: CommunityIdParam,\n body: OrderedIds,\n response: { 200: z.array(SpaceDetail), ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.reorderSpaces(req.params.communityId, req.body.ids, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/spaces/:spaceId',\n schema: {\n operationId: 'getSpace',\n tags,\n summary: 'Get a space',\n description: `Requires \\`communities:read\\`. The space with its topics and links.`,\n params: CommunitySpaceParam,\n response: { 200: SpaceDetail, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.getCommunitySpace(req.params.communityId, req.params.spaceId, actor);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/communities/:communityId/spaces/:spaceId',\n schema: {\n operationId: 'updateSpace',\n tags,\n summary: 'Update a space',\n description: `Requires \\`communities:write\\`. Changes only the fields sent; \\`settings\\` merges into the current settings. An \\`all_members\\` space cannot have a capacity (409).`,\n params: CommunitySpaceParam,\n body: UpdateSpace,\n response: { 200: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.updateSpace(req.params.communityId, req.params.spaceId, req.body, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId/spaces/:spaceId',\n schema: {\n operationId: 'deleteSpace',\n tags,\n summary: 'Delete a space',\n description: `Requires \\`communities:write\\`. The space and everything in it go.`,\n params: CommunitySpaceParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await content.deleteSpace(req.params.communityId, req.params.spaceId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/spaces/:spaceId/topics',\n schema: {\n operationId: 'setSpaceTopics',\n tags,\n summary: 'Replace the topics on a space',\n description: `Requires \\`communities:write\\`. The list replaces the current topics: an entry with an \\`id\\` keeps and renames that topic, one without adds a topic, and a topic left out is deleted. Names must be unique (409), and a topic that posts use cannot be deleted (409).`,\n params: CommunitySpaceParam,\n body: SetSpaceTopics,\n response: { 200: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.setSpaceTopics(req.params.communityId, req.params.spaceId, req.body, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces/:spaceId/links',\n schema: {\n operationId: 'addSpaceLink',\n tags,\n summary: 'Link a course, download, or community into a space',\n description: `Requires \\`communities:write\\`. The link is added last. Linking the same content twice is a 409.`,\n params: CommunitySpaceParam,\n body: SpaceLinkBody,\n response: { 201: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const space = await content.addSpaceLink(\n req.params.communityId,\n req.params.spaceId,\n req.body.contentId,\n actor,\n );\n return reply.code(201).send(space);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId/spaces/:spaceId/links/:linkId',\n schema: {\n operationId: 'removeSpaceLink',\n tags,\n summary: 'Remove a link from a space',\n description: `Requires \\`communities:write\\`. The linked content itself is untouched.`,\n params: SpaceLinkParam,\n response: { 200: SpaceDetail, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.removeSpaceLink(\n req.params.communityId,\n req.params.spaceId,\n req.params.linkId,\n actor,\n );\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/spaces/:spaceId/links/order',\n schema: {\n operationId: 'reorderSpaceLinks',\n tags,\n summary: 'Reorder the links in a space',\n description: `Requires \\`communities:write\\`. Send every link id of the space exactly once (409 otherwise).`,\n params: CommunitySpaceParam,\n body: OrderedIds,\n response: { 200: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.reorderSpaceLinks(\n req.params.communityId,\n req.params.spaceId,\n req.body.ids,\n actor,\n );\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"communities.js","names":[],"sources":["../../../src/http/routes/communities.ts"],"sourcesContent":["import type { FastifyInstance } from 'fastify';\nimport type { ZodTypeProvider } from 'fastify-type-provider-zod';\nimport { z } from 'zod';\nimport type { Container } from '../../app/container.js';\nimport { resolveActor } from '../scope.js';\nimport {\n Community,\n CommunityAnalytics,\n CommunityIdParam,\n CommunitySpaceParam,\n CommunitiesPage,\n CommunitiesQuery,\n CreateCommunity,\n CreateSpace,\n MessageQueue,\n NotificationCategoryList,\n OrderedIds,\n PatchCommunitySettings,\n ReplaceNotificationCategories,\n SetSpaceTopics,\n SpaceDetail,\n SpaceLinkBody,\n SpaceLinkParam,\n UpdateCommunity,\n UpdateSpace,\n refusals,\n} from '../schemas/index.js';\n\nexport async function communitiesRoutes(app: FastifyInstance, container: Container): Promise<void> {\n const r = app.withTypeProvider<ZodTypeProvider>();\n const content = container.content;\n const tags = ['Communities'];\n\n r.route({\n method: 'GET',\n url: '/api/communities',\n schema: {\n operationId: 'listCommunities',\n tags,\n summary: 'List communities',\n description: `Requires one of \\`communities:read\\`, \\`communities:moderate\\`. Pages every community in the organization, drafts included, filtered by \\`status\\` and searched by title.`,\n querystring: CommunitiesQuery,\n response: { 200: CommunitiesPage, ...refusals(403) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.listCommunities(req.query, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities',\n schema: {\n operationId: 'createCommunity',\n tags,\n summary: 'Create a community',\n description: `Requires \\`communities:write\\`. The community starts as a draft with the default direct-message settings and no spaces. Members see it once it is published and they hold it.`,\n body: CreateCommunity,\n response: { 201: Community, ...refusals(403, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const community = await content.createCommunity(req.body, actor);\n req.log.info({ communityId: community.id }, 'community created');\n return reply.code(201).send(community);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId',\n schema: {\n operationId: 'getCommunity',\n tags,\n summary: 'Get a community',\n description: `Requires one of \\`communities:read\\`, \\`communities:moderate\\`. Any community of the organization, drafts included.`,\n params: CommunityIdParam,\n response: { 200: Community, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.getCommunity(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/communities/:communityId',\n schema: {\n operationId: 'updateCommunity',\n tags,\n summary: 'Update a community',\n description: `Requires \\`communities:write\\`. Changes only the fields sent. \\`thumbnailAssetId\\` must be a ready library image (409 otherwise); null clears it. A file it does not already use must be uploaded for it, or the caller needs \\`media:read\\` (403 otherwise).`,\n params: CommunityIdParam,\n body: UpdateCommunity,\n response: { 200: Community, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.updateCommunity(req.params.communityId, req.body, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId',\n schema: {\n operationId: 'deleteCommunity',\n tags,\n summary: 'Delete a community',\n description: `Requires \\`communities:delete\\`. Links to the community from other spaces are removed with it.`,\n params: CommunityIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await content.deleteCommunity(req.params.communityId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/communities/:communityId/settings',\n schema: {\n operationId: 'patchCommunitySettings',\n tags,\n summary: 'Change the direct-message and digest settings of a community',\n description: `Requires \\`communities:write\\`. Changes only the settings sent; the rest keep their values. The direct-message settings decide who may message whom from then on. \\`digest\\` sets when the community digest goes out, or \\`null\\` stops it; the first digest after a change goes out at the next slot.`,\n params: CommunityIdParam,\n body: PatchCommunitySettings,\n response: { 200: Community, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.patchCommunitySettings(req.params.communityId, req.body, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/notification-categories',\n schema: {\n operationId: 'getCommunityNotificationCategories',\n tags: ['Notifications'],\n summary: \"Get a community's notification categories\",\n description: `Requires \\`communities:read\\`. On the community. The list in use for the community, with \\`source\\` saying whether it is the default or a stored list.`,\n params: CommunityIdParam,\n response: { 200: NotificationCategoryList, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.notificationCategories.community.get(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/notification-categories',\n schema: {\n operationId: 'replaceCommunityNotificationCategories',\n tags: ['Notifications'],\n summary: \"Replace a community's notification categories\",\n description: `Requires \\`communities:write\\`. On the community. Stores the whole list for this community; other communities keep theirs. 422 when a key repeats, a type cannot be in a community list, a reason is not allowed for its type, a channel is not configured, \\`perSpace\\` is set on a type that cannot occur in a space, \\`spaceDefaults\\` appears without \\`perSpace\\` or names a channel the category does not offer, or earlier categories already cover everything a category covers.`,\n params: CommunityIdParam,\n body: ReplaceNotificationCategories,\n response: { 200: NotificationCategoryList, ...refusals(400, 403, 404, 422) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.notificationCategories.community.replace(\n req.params.communityId,\n req.body.categories,\n actor,\n );\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId/notification-categories',\n schema: {\n operationId: 'resetCommunityNotificationCategories',\n tags: ['Notifications'],\n summary: 'Go back to the default notification categories for a community',\n description: `Requires \\`communities:write\\`. On the community. Removes the stored list; the default applies again.`,\n params: CommunityIdParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await container.notificationCategories.community.reset(req.params.communityId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/publish',\n schema: {\n operationId: 'publishCommunity',\n tags,\n summary: 'Publish a community',\n description: `Requires \\`communities:write\\`. Makes the community visible to the people who hold it.`,\n params: CommunityIdParam,\n response: { 200: Community, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.publishCommunity(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/unpublish',\n schema: {\n operationId: 'unpublishCommunity',\n tags,\n summary: 'Unpublish a community',\n description: `Requires \\`communities:write\\`. Hides the community from members again; nothing in it is deleted.`,\n params: CommunityIdParam,\n response: { 200: Community, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.unpublishCommunity(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/analytics',\n schema: {\n operationId: 'getCommunityAnalytics',\n tags,\n summary: 'Analytics for a community',\n description: `Requires \\`communities:read\\`. Membership, activity and moderation counts for one community.`,\n params: CommunityIdParam,\n response: { 200: CommunityAnalytics, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.reporting.admin.communityAnalytics(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/message-queue',\n schema: {\n operationId: 'getCommunityMessageQueue',\n tags,\n summary: 'Open message flags in a community',\n description: `Requires \\`communities:moderate\\`. The messages in the community's chats with open flags, with the flags and the chat each is in. Holds at most the 100 oldest open flags; working the queue brings in the next ones.`,\n params: CommunityIdParam,\n response: { 200: MessageQueue, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return container.chats.messageQueue(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/spaces',\n schema: {\n operationId: 'listCommunitySpaces',\n tags,\n summary: 'List the spaces in a community',\n description: `Requires one of \\`communities:read\\`, \\`communities:moderate\\`. Every space in order, with its topics and links.`,\n params: CommunityIdParam,\n response: { 200: z.array(SpaceDetail), ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.listSpaces(req.params.communityId, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces',\n schema: {\n operationId: 'createSpace',\n tags,\n summary: 'Create a space',\n description: `Requires \\`communities:write\\`. The space is added last. An \\`all_members\\` space cannot have a capacity (409).`,\n params: CommunityIdParam,\n body: CreateSpace,\n response: { 201: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const space = await content.createSpace(req.params.communityId, req.body, actor);\n return reply.code(201).send(space);\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/spaces/order',\n schema: {\n operationId: 'reorderSpaces',\n tags,\n summary: 'Reorder the spaces in a community',\n description: `Requires \\`communities:write\\`. Send every space id of the community exactly once (409 otherwise).`,\n params: CommunityIdParam,\n body: OrderedIds,\n response: { 200: z.array(SpaceDetail), ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.reorderSpaces(req.params.communityId, req.body.ids, actor);\n },\n });\n\n r.route({\n method: 'GET',\n url: '/api/communities/:communityId/spaces/:spaceId',\n schema: {\n operationId: 'getSpace',\n tags,\n summary: 'Get a space',\n description: `Requires \\`communities:read\\`. The space with its topics and links.`,\n params: CommunitySpaceParam,\n response: { 200: SpaceDetail, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.getCommunitySpace(req.params.communityId, req.params.spaceId, actor);\n },\n });\n\n r.route({\n method: 'PATCH',\n url: '/api/communities/:communityId/spaces/:spaceId',\n schema: {\n operationId: 'updateSpace',\n tags,\n summary: 'Update a space',\n description: `Requires \\`communities:write\\`. Changes only the fields sent; \\`settings\\` merges into the current settings. An \\`all_members\\` space cannot have a capacity (409). \\`coverAssetId\\`, \\`backgroundAssetId\\` and \\`iconAssetId\\` take ready images from the media library, or null to clear one; without \\`media:read\\`, only images uploaded for the community (409 or 403 otherwise). In a published community the images are public.`,\n params: CommunitySpaceParam,\n body: UpdateSpace,\n response: { 200: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.updateSpace(req.params.communityId, req.params.spaceId, req.body, actor);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId/spaces/:spaceId',\n schema: {\n operationId: 'deleteSpace',\n tags,\n summary: 'Delete a space',\n description: `Requires \\`communities:write\\`. The space and everything in it go.`,\n params: CommunitySpaceParam,\n response: { 204: z.void(), ...refusals(403, 404) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n await content.deleteSpace(req.params.communityId, req.params.spaceId, actor);\n return reply.code(204).send();\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/spaces/:spaceId/topics',\n schema: {\n operationId: 'setSpaceTopics',\n tags,\n summary: 'Replace the topics on a space',\n description: `Requires \\`communities:write\\`. The list replaces the current topics: an entry with an \\`id\\` keeps and renames that topic, one without adds a topic, and a topic left out is deleted. Names must be unique (409), and a topic that posts use cannot be deleted (409).`,\n params: CommunitySpaceParam,\n body: SetSpaceTopics,\n response: { 200: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.setSpaceTopics(req.params.communityId, req.params.spaceId, req.body, actor);\n },\n });\n\n r.route({\n method: 'POST',\n url: '/api/communities/:communityId/spaces/:spaceId/links',\n schema: {\n operationId: 'addSpaceLink',\n tags,\n summary: 'Link a course, download, or community into a space',\n description: `Requires \\`communities:write\\`. The link is added last. Linking the same content twice is a 409.`,\n params: CommunitySpaceParam,\n body: SpaceLinkBody,\n response: { 201: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req, reply) => {\n const actor = await resolveActor(container, req);\n const space = await content.addSpaceLink(\n req.params.communityId,\n req.params.spaceId,\n req.body.contentId,\n actor,\n );\n return reply.code(201).send(space);\n },\n });\n\n r.route({\n method: 'DELETE',\n url: '/api/communities/:communityId/spaces/:spaceId/links/:linkId',\n schema: {\n operationId: 'removeSpaceLink',\n tags,\n summary: 'Remove a link from a space',\n description: `Requires \\`communities:write\\`. The linked content itself is untouched.`,\n params: SpaceLinkParam,\n response: { 200: SpaceDetail, ...refusals(403, 404) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.removeSpaceLink(\n req.params.communityId,\n req.params.spaceId,\n req.params.linkId,\n actor,\n );\n },\n });\n\n r.route({\n method: 'PUT',\n url: '/api/communities/:communityId/spaces/:spaceId/links/order',\n schema: {\n operationId: 'reorderSpaceLinks',\n tags,\n summary: 'Reorder the links in a space',\n description: `Requires \\`communities:write\\`. Send every link id of the space exactly once (409 otherwise).`,\n params: CommunitySpaceParam,\n body: OrderedIds,\n response: { 200: SpaceDetail, ...refusals(403, 404, 409) },\n },\n handler: async (req) => {\n const actor = await resolveActor(container, req);\n return content.reorderSpaceLinks(\n req.params.communityId,\n req.params.spaceId,\n req.body.ids,\n actor,\n );\n },\n });\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { refusals } from "../../schemas/shared.js";
2
2
  import { resolveActor } from "../../scope.js";
3
- import { ActivityFileParams, CommunityThumbnailParams, CourseThumbnailParams, DownloadFileParams, DownloadThumbnailParams, EventFileParams, EventThumbnailParams, MessageAttachmentParams, PostAssetParams, PostCoverParams, UploadFileParams } from "../../schemas/files.js";
3
+ import { ActivityFileParams, CommunityThumbnailParams, CourseThumbnailParams, DownloadFileParams, DownloadThumbnailParams, EventFileParams, EventThumbnailParams, MessageAttachmentParams, PostAssetParams, PostCoverParams, SpaceImageParams, UploadFileParams } from "../../schemas/files.js";
4
4
  import "../../schemas/index.js";
5
5
  import { z } from "zod";
6
6
  //#region src/http/routes/member/files.ts
@@ -100,6 +100,23 @@ async function memberFileRoutes(app, container) {
100
100
  return redirect(reply, await member.thumbnailFile("community", req.params.communityId, actor));
101
101
  }
102
102
  });
103
+ r.route({
104
+ method: "GET",
105
+ url: "/api/member/spaces/:spaceId/images/:image",
106
+ preHandler: app.requireOrgSession,
107
+ schema: {
108
+ operationId: "getSpaceImage",
109
+ tags,
110
+ summary: "Redirect to a space's cover, background or icon",
111
+ description: "For a space in an unpublished community, which only a holder of `communities:read` on the community may fetch: a space in a published community carries public `coverUrl`, `backgroundUrl` and `iconUrl` instead. 404 when the space has no such image.",
112
+ params: SpaceImageParams,
113
+ response: responses
114
+ },
115
+ handler: async (req, reply) => {
116
+ const actor = await resolveActor(container, req);
117
+ return redirect(reply, await member.spaceImageFile(req.params.spaceId, req.params.image, actor));
118
+ }
119
+ });
103
120
  r.route({
104
121
  method: "GET",
105
122
  url: "/api/member/activities/:activityId/assets/:assetId",