@headless-lms/server 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/app/community-mail.d.ts +5 -3
- package/dist/app/community-mail.js +60 -12
- package/dist/app/community-mail.js.map +1 -1
- package/dist/app/container.d.ts +24 -3
- package/dist/app/container.js +102 -38
- package/dist/app/container.js.map +1 -1
- package/dist/app/notification-channels.js +1 -0
- package/dist/app/notification-channels.js.map +1 -1
- package/dist/app/notification-reads.d.ts +3 -3
- package/dist/app/notification-reads.js +7 -28
- package/dist/app/notification-reads.js.map +1 -1
- package/dist/app/platform.d.ts +14 -0
- package/dist/app/platform.js +14 -0
- package/dist/app/platform.js.map +1 -0
- package/dist/http/config.d.ts +4 -0
- package/dist/http/errors.js +9 -4
- package/dist/http/errors.js.map +1 -1
- package/dist/http/fastify.d.ts +8 -0
- package/dist/http/mcp/catalogue.d.ts +11 -9
- package/dist/http/mcp/catalogue.js +15 -21
- package/dist/http/mcp/catalogue.js.map +1 -1
- package/dist/http/mcp/challenge.js +3 -3
- package/dist/http/mcp/challenge.js.map +1 -1
- package/dist/http/mcp/route.js +7 -7
- package/dist/http/mcp/route.js.map +1 -1
- package/dist/http/mcp/test-support.d.ts +6 -5
- package/dist/http/mcp/test-support.js +32 -11
- package/dist/http/mcp/test-support.js.map +1 -1
- package/dist/http/mcp/tool.d.ts +3 -9
- package/dist/http/mcp/tool.js +14 -6
- package/dist/http/mcp/tool.js.map +1 -1
- package/dist/http/mcp/tools/automations.js +10 -20
- package/dist/http/mcp/tools/automations.js.map +1 -1
- package/dist/http/mcp/tools/catalog.js +25 -42
- package/dist/http/mcp/tools/catalog.js.map +1 -1
- package/dist/http/mcp/tools/comments.js +9 -19
- package/dist/http/mcp/tools/comments.js.map +1 -1
- package/dist/http/mcp/tools/communities.js +17 -32
- package/dist/http/mcp/tools/communities.js.map +1 -1
- package/dist/http/mcp/tools/community-members.js +9 -16
- package/dist/http/mcp/tools/community-members.js.map +1 -1
- package/dist/http/mcp/tools/community-moderation.js +13 -25
- package/dist/http/mcp/tools/community-moderation.js.map +1 -1
- package/dist/http/mcp/tools/courses.js +11 -21
- package/dist/http/mcp/tools/courses.js.map +1 -1
- package/dist/http/mcp/tools/events.d.ts +6 -0
- package/dist/http/mcp/tools/events.js +170 -0
- package/dist/http/mcp/tools/events.js.map +1 -0
- package/dist/http/mcp/tools/index.d.ts +5 -1
- package/dist/http/mcp/tools/index.js +45 -10
- package/dist/http/mcp/tools/index.js.map +1 -1
- package/dist/http/mcp/tools/organization.d.ts +6 -0
- package/dist/http/mcp/tools/organization.js +214 -0
- package/dist/http/mcp/tools/organization.js.map +1 -0
- package/dist/http/mcp/tools/people.js +30 -49
- package/dist/http/mcp/tools/people.js.map +1 -1
- package/dist/http/plugins/auth.js +37 -8
- package/dist/http/plugins/auth.js.map +1 -1
- package/dist/http/plugins/error-handler.d.ts +5 -1
- package/dist/http/plugins/error-handler.js +10 -2
- package/dist/http/plugins/error-handler.js.map +1 -1
- package/dist/http/plugins/openapi.js +18 -2
- package/dist/http/plugins/openapi.js.map +1 -1
- package/dist/http/plugins/platform.d.ts +7 -0
- package/dist/http/plugins/platform.js +13 -0
- package/dist/http/plugins/platform.js.map +1 -0
- package/dist/http/routes/access.js +54 -36
- package/dist/http/routes/access.js.map +1 -1
- package/dist/http/routes/activities.js +45 -45
- package/dist/http/routes/activities.js.map +1 -1
- package/dist/http/routes/assets.js +30 -65
- package/dist/http/routes/assets.js.map +1 -1
- package/dist/http/routes/automations.js +37 -42
- package/dist/http/routes/automations.js.map +1 -1
- package/dist/http/routes/bundles.js +33 -36
- package/dist/http/routes/bundles.js.map +1 -1
- package/dist/http/routes/communities.js +93 -145
- package/dist/http/routes/communities.js.map +1 -1
- package/dist/http/routes/community-moderation.js +83 -137
- package/dist/http/routes/community-moderation.js.map +1 -1
- package/dist/http/routes/course-analytics.js +32 -15
- package/dist/http/routes/course-analytics.js.map +1 -1
- package/dist/http/routes/courses.js +25 -32
- package/dist/http/routes/courses.js.map +1 -1
- package/dist/http/routes/dashboard.js +9 -9
- package/dist/http/routes/dashboard.js.map +1 -1
- package/dist/http/routes/discussion.js +21 -39
- package/dist/http/routes/discussion.js.map +1 -1
- package/dist/http/routes/downloads.js +41 -47
- package/dist/http/routes/downloads.js.map +1 -1
- package/dist/http/routes/entitlements.js +19 -23
- package/dist/http/routes/entitlements.js.map +1 -1
- package/dist/http/routes/events.d.ts +7 -0
- package/dist/http/routes/events.js +333 -0
- package/dist/http/routes/events.js.map +1 -0
- package/dist/http/routes/integrations.js +40 -73
- package/dist/http/routes/integrations.js.map +1 -1
- package/dist/http/routes/member/assets.js +10 -13
- package/dist/http/routes/member/assets.js.map +1 -1
- package/dist/http/routes/member/chats.js +170 -47
- package/dist/http/routes/member/chats.js.map +1 -1
- package/dist/http/routes/member/comments.js +65 -26
- package/dist/http/routes/member/comments.js.map +1 -1
- package/dist/http/routes/member/community.js +45 -52
- package/dist/http/routes/member/community.js.map +1 -1
- package/dist/http/routes/member/courses.js +12 -19
- package/dist/http/routes/member/courses.js.map +1 -1
- package/dist/http/routes/member/devices.js +9 -10
- package/dist/http/routes/member/devices.js.map +1 -1
- package/dist/http/routes/member/downloads.js +6 -9
- package/dist/http/routes/member/downloads.js.map +1 -1
- package/dist/http/routes/member/events.d.ts +7 -0
- package/dist/http/routes/member/events.js +386 -0
- package/dist/http/routes/member/events.js.map +1 -0
- package/dist/http/routes/member/files.js +88 -46
- package/dist/http/routes/member/files.js.map +1 -1
- package/dist/http/routes/member/index.js +2 -0
- package/dist/http/routes/member/index.js.map +1 -1
- package/dist/http/routes/member/notification-settings.js +15 -23
- package/dist/http/routes/member/notification-settings.js.map +1 -1
- package/dist/http/routes/member/notifications.js +16 -16
- package/dist/http/routes/member/notifications.js.map +1 -1
- package/dist/http/routes/member/org.js +5 -15
- package/dist/http/routes/member/org.js.map +1 -1
- package/dist/http/routes/member/posts.js +67 -68
- package/dist/http/routes/member/posts.js.map +1 -1
- package/dist/http/routes/member/profile.js +15 -6
- package/dist/http/routes/member/profile.js.map +1 -1
- package/dist/http/routes/member/progress.js +7 -11
- package/dist/http/routes/member/progress.js.map +1 -1
- package/dist/http/routes/organizations.js +319 -135
- package/dist/http/routes/organizations.js.map +1 -1
- package/dist/http/routes/platform.d.ts +7 -0
- package/dist/http/routes/platform.js +61 -0
- package/dist/http/routes/platform.js.map +1 -0
- package/dist/http/routes/students.js +12 -75
- package/dist/http/routes/students.js.map +1 -1
- package/dist/http/routes/webhooks.js +30 -39
- package/dist/http/routes/webhooks.js.map +1 -1
- package/dist/http/routes.js +9 -0
- package/dist/http/routes.js.map +1 -1
- package/dist/http/schemas/access.d.ts +346 -64
- package/dist/http/schemas/access.js +39 -12
- package/dist/http/schemas/access.js.map +1 -1
- package/dist/http/schemas/assets.d.ts +8 -3
- package/dist/http/schemas/assets.js +6 -5
- package/dist/http/schemas/assets.js.map +1 -1
- package/dist/http/schemas/automations.d.ts +4 -4
- package/dist/http/schemas/chats.d.ts +216 -37
- package/dist/http/schemas/chats.js +13 -4
- package/dist/http/schemas/chats.js.map +1 -1
- package/dist/http/schemas/community.d.ts +105 -31
- package/dist/http/schemas/community.js +25 -9
- package/dist/http/schemas/community.js.map +1 -1
- package/dist/http/schemas/course-analytics.d.ts +51 -1
- package/dist/http/schemas/course-analytics.js +19 -1
- package/dist/http/schemas/course-analytics.js.map +1 -1
- package/dist/http/schemas/dashboard.d.ts +1 -0
- package/dist/http/schemas/dashboard.js +2 -1
- package/dist/http/schemas/dashboard.js.map +1 -1
- package/dist/http/schemas/discussion.d.ts +22 -72
- package/dist/http/schemas/discussion.js +2 -2
- package/dist/http/schemas/discussion.js.map +1 -1
- package/dist/http/schemas/entitlements.d.ts +9 -12
- package/dist/http/schemas/entitlements.js +11 -5
- package/dist/http/schemas/entitlements.js.map +1 -1
- package/dist/http/schemas/events.d.ts +527 -0
- package/dist/http/schemas/events.js +64 -0
- package/dist/http/schemas/events.js.map +1 -0
- package/dist/http/schemas/files.d.ts +24 -5
- package/dist/http/schemas/files.js +15 -2
- package/dist/http/schemas/files.js.map +1 -1
- package/dist/http/schemas/index.d.ts +11 -9
- package/dist/http/schemas/index.js +11 -9
- package/dist/http/schemas/invites.d.ts +3 -47
- package/dist/http/schemas/invites.js +4 -25
- package/dist/http/schemas/invites.js.map +1 -1
- package/dist/http/schemas/member.d.ts +94 -7
- package/dist/http/schemas/member.js +7 -3
- package/dist/http/schemas/member.js.map +1 -1
- package/dist/http/schemas/members.d.ts +329 -65
- package/dist/http/schemas/members.js +67 -25
- package/dist/http/schemas/members.js.map +1 -1
- package/dist/http/schemas/notifications.d.ts +58 -54
- package/dist/http/schemas/platform.d.ts +38 -0
- package/dist/http/schemas/platform.js +19 -0
- package/dist/http/schemas/platform.js.map +1 -0
- package/dist/http/schemas/posts.d.ts +55 -35
- package/dist/http/schemas/posts.js +4 -4
- package/dist/http/schemas/posts.js.map +1 -1
- package/dist/http/schemas/profile.d.ts +1 -1
- package/dist/http/schemas/shared.d.ts +1 -11
- package/dist/http/schemas/shared.js +1 -10
- package/dist/http/schemas/shared.js.map +1 -1
- package/dist/http/schemas/students.d.ts +22 -0
- package/dist/http/schemas/students.js +3 -1
- package/dist/http/schemas/students.js.map +1 -1
- package/dist/http/scope.d.ts +9 -13
- package/dist/http/scope.js +29 -40
- package/dist/http/scope.js.map +1 -1
- package/dist/http/server.js +9 -0
- package/dist/http/server.js.map +1 -1
- package/dist/http/shutdown.d.ts +11 -0
- package/dist/http/shutdown.js +40 -0
- package/dist/http/shutdown.js.map +1 -0
- package/dist/http/sse.d.ts +19 -0
- package/dist/http/sse.js +104 -0
- package/dist/http/sse.js.map +1 -0
- package/dist/http/test-caller.d.ts +13 -0
- package/dist/http/test-caller.js +14 -0
- package/dist/http/test-caller.js.map +1 -0
- package/dist/http/well-known.js +2 -2
- package/dist/http/well-known.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/packages/server/package.js +1 -1
- package/package.json +7 -6
- package/dist/http/routes/member/actor.d.ts +0 -14
- package/dist/http/routes/member/actor.js +0 -11
- package/dist/http/routes/member/actor.js.map +0 -1
package/dist/app/container.js
CHANGED
|
@@ -3,7 +3,8 @@ import { loadIntegrations } from "./integrations.js";
|
|
|
3
3
|
import { mcpEndpoints } from "./mcp-endpoints.js";
|
|
4
4
|
import { communityMail } from "./community-mail.js";
|
|
5
5
|
import { notificationReads } from "./notification-reads.js";
|
|
6
|
-
import {
|
|
6
|
+
import { platformKeyCheck } from "./platform.js";
|
|
7
|
+
import { DrizzleAdminRepository, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleChatRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDevicesRepository, DrizzleDigestRunRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleMemberRepository, DrizzleMembersRepository, DrizzleMentionsRepository, DrizzleNotificationSettingsRepository, DrizzleNotificationsRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzlePostRepository, DrizzleProgressRepository, DrizzleRolesRepository, DrizzleSettingsRepository, DrizzleSubscriptionsRepository, DrizzleUnitOfWork, createDb } from "@headless-lms/adapter-db";
|
|
7
8
|
import { InMemoryEventBus } from "@headless-lms/adapter-defaults/events";
|
|
8
9
|
import { PollingOutboxRelay } from "@headless-lms/adapter-defaults/events/outbox-relay";
|
|
9
10
|
import { InlineAutomationEngine } from "@headless-lms/adapter-defaults/workflows";
|
|
@@ -11,13 +12,14 @@ import { FetchWebhookSender } from "@headless-lms/adapter-defaults/webhooks";
|
|
|
11
12
|
import { EmailAdapter, StubTemplateRenderer } from "@headless-lms/adapter-defaults/email";
|
|
12
13
|
import { createRootLogger, requestLogContext } from "@headless-lms/adapter-defaults/logging";
|
|
13
14
|
import { StorageAdapter } from "@headless-lms/adapter-defaults/storage";
|
|
15
|
+
import { PlainBodyParser } from "@headless-lms/adapter-defaults/body";
|
|
14
16
|
import { BetterAuth } from "@headless-lms/adapter-auth";
|
|
15
|
-
import { CommunityNotificationCategories, ContentItemTitles, ContentService } from "@headless-lms/core/content";
|
|
17
|
+
import { CommunityNotificationCategories, ContentItemTitles, ContentService, contentJobs } from "@headless-lms/core/content";
|
|
16
18
|
import { EntitlementsServiceImpl, entitlementsJobs, entitlementsRecurringJobs } from "@headless-lms/core/entitlements";
|
|
17
19
|
import { ProgressServiceImpl } from "@headless-lms/core/progress";
|
|
18
|
-
import { ChatServiceImpl, DigestServiceImpl, DiscussionItemTitles, DiscussionServiceImpl, PostServiceImpl, SubscriptionServiceImpl, discussionJobs, discussionRecurringJobs } from "@headless-lms/core/discussion";
|
|
20
|
+
import { ChatLiveForwarder, ChatServiceImpl, DigestServiceImpl, DiscussionItemTitles, DiscussionServiceImpl, PostServiceImpl, SubscriptionServiceImpl, discussionJobs, discussionRecurringJobs } from "@headless-lms/core/discussion";
|
|
19
21
|
import { DevicesServiceImpl, IdentityServiceImpl, NotificationSettingsServiceImpl } from "@headless-lms/core/identity";
|
|
20
|
-
import { OrgNotificationCategories, OrganizationServiceImpl
|
|
22
|
+
import { NotificationCatalog, OrgNotificationCategories, OrganizationServiceImpl } from "@headless-lms/core/organizations";
|
|
21
23
|
import { AssetsServiceImpl, assetsJobs, assetsRecurringJobs, createFileUrls } from "@headless-lms/core/assets";
|
|
22
24
|
import { IntegrationsServiceImpl } from "@headless-lms/core/integrations";
|
|
23
25
|
import { AutomationsServiceImpl, DEFAULT_COMMUNITY_CATEGORIES, DEFAULT_ORG_CATEGORIES, NOTIFICATION_TYPES, NotificationDelivery, NotificationProcessor, NotificationsServiceImpl, RECIPIENT_RULES, SchedulerChatDebounce, notificationJobs } from "@headless-lms/core/automations";
|
|
@@ -54,12 +56,15 @@ async function buildContainer(config, options) {
|
|
|
54
56
|
const automationsLogger = logger.child({ name: "automations" });
|
|
55
57
|
const reportingLogger = logger.child({ name: "reporting" });
|
|
56
58
|
const outboxLogger = logger.child({ name: "outbox" });
|
|
57
|
-
const db = createDb(config.databaseUrl);
|
|
59
|
+
const db = createDb(config.databaseUrl, logger.child({ name: "db" }), config.databasePool);
|
|
58
60
|
const email = options?.adapters?.email ?? new EmailAdapter(logger.child({ name: "email" }));
|
|
59
61
|
const storage = options?.adapters?.storage ?? new StorageAdapter(logger.child({ name: "storage" }));
|
|
60
62
|
const templates = options?.adapters?.templates ?? new StubTemplateRenderer(logger.child({ name: "email" }));
|
|
63
|
+
const bodies = options?.adapters?.bodyParser ?? new PlainBodyParser();
|
|
61
64
|
const workflows = options?.adapters?.workflows;
|
|
62
65
|
const automationEngine = typeof workflows === "function" ? workflows(automationsLogger) : workflows ?? new InlineAutomationEngine(automationsLogger);
|
|
66
|
+
const pubsubOption = options?.adapters?.pubsub;
|
|
67
|
+
const pubsub = typeof pubsubOption === "function" ? pubsubOption(logger.child({ name: "pubsub" })) : pubsubOption ?? null;
|
|
63
68
|
const mailer = new Mailer(templates, email, {
|
|
64
69
|
...config.emailBranding ?? {
|
|
65
70
|
brandName: "Headless LMS",
|
|
@@ -67,7 +72,7 @@ async function buildContainer(config, options) {
|
|
|
67
72
|
},
|
|
68
73
|
studentPortalUrl: config.studentPortalUrl
|
|
69
74
|
});
|
|
70
|
-
const fileUrls = createFileUrls(config.authBaseURL);
|
|
75
|
+
const fileUrls = createFileUrls(config.authBaseURL, (key) => storage.publicUrl(key));
|
|
71
76
|
const assets = new AssetsServiceImpl({
|
|
72
77
|
storage,
|
|
73
78
|
repo: new DrizzleAssetsRepository(db, assetsLogger),
|
|
@@ -76,6 +81,7 @@ async function buildContainer(config, options) {
|
|
|
76
81
|
outbox: new DrizzleOutboxAppender(tx, outboxLogger)
|
|
77
82
|
})),
|
|
78
83
|
memberImageMaxBytes: config.memberImageMaxBytes,
|
|
84
|
+
memberFileMaxBytes: config.memberFileMaxBytes,
|
|
79
85
|
downloadLinkExpirySeconds: config.deliveryExpirySeconds,
|
|
80
86
|
fileUrls,
|
|
81
87
|
logger: assetsLogger
|
|
@@ -95,24 +101,46 @@ async function buildContainer(config, options) {
|
|
|
95
101
|
const organizationsRepo = new DrizzleOrganizationsRepository(db, logger.child({ name: "org" }));
|
|
96
102
|
const organizationsUow = new DrizzleUnitOfWork(db, (tx) => ({
|
|
97
103
|
organizations: new DrizzleOrganizationsRepository(tx, logger.child({ name: "org" })),
|
|
104
|
+
roles: new DrizzleRolesRepository(tx),
|
|
98
105
|
outbox: new DrizzleOutboxAppender(tx, logger.child({ name: "org" }))
|
|
99
106
|
}));
|
|
100
107
|
const orgAdminRef = { current: void 0 };
|
|
108
|
+
const platform = config.platform ? platformKeyCheck(config.platform) : null;
|
|
101
109
|
const organizations = new OrganizationServiceImpl({
|
|
102
110
|
repo: organizationsRepo,
|
|
111
|
+
roles: new DrizzleRolesRepository(db),
|
|
112
|
+
...options?.roles && { defaultRoles: options.roles },
|
|
113
|
+
apiKeys: () => {
|
|
114
|
+
if (!orgAdminRef.current) throw new Error("api keys not initialised");
|
|
115
|
+
return orgAdminRef.current;
|
|
116
|
+
},
|
|
117
|
+
accounts: () => {
|
|
118
|
+
if (!orgAdminRef.current) throw new Error("accounts not initialised");
|
|
119
|
+
return orgAdminRef.current;
|
|
120
|
+
},
|
|
121
|
+
...config.platform && { platform: {
|
|
122
|
+
email: config.platform.accountEmail,
|
|
123
|
+
name: config.platform.accountName ?? "Platform"
|
|
124
|
+
} },
|
|
103
125
|
membersRepo: new DrizzleMembersRepository(db, logger.child({ name: "org" })),
|
|
104
126
|
people: identity,
|
|
127
|
+
images: assets,
|
|
105
128
|
uow: organizationsUow,
|
|
106
129
|
orgAdmin: () => {
|
|
107
130
|
if (!orgAdminRef.current) throw new Error("orgAdmin not initialised");
|
|
108
131
|
return orgAdminRef.current;
|
|
109
132
|
},
|
|
133
|
+
sessions: () => {
|
|
134
|
+
if (!orgAdminRef.current) throw new Error("sessions not initialised");
|
|
135
|
+
return orgAdminRef.current;
|
|
136
|
+
},
|
|
110
137
|
logger: logger.child({ name: "org" }),
|
|
111
138
|
mailer,
|
|
112
139
|
inviteUrls: {
|
|
113
140
|
studentPortalUrl: config.studentPortalUrl,
|
|
114
141
|
adminAppUrl: config.adminAppUrl
|
|
115
|
-
}
|
|
142
|
+
},
|
|
143
|
+
grants: () => entitlements
|
|
116
144
|
});
|
|
117
145
|
const settings = new SettingsService(new DrizzleSettingsRepository(db, logger.child({ name: "settings" })));
|
|
118
146
|
const contentUow = new DrizzleUnitOfWork(db, (tx) => ({
|
|
@@ -125,6 +153,7 @@ async function buildContainer(config, options) {
|
|
|
125
153
|
libraryAssets: assets,
|
|
126
154
|
files: assets,
|
|
127
155
|
fileUrls,
|
|
156
|
+
scheduler: automationEngine,
|
|
128
157
|
logger: contentLogger
|
|
129
158
|
});
|
|
130
159
|
const pushConfig = options?.adapters?.push ?? {};
|
|
@@ -158,24 +187,21 @@ async function buildContainer(config, options) {
|
|
|
158
187
|
logger: logger.child({ name: "org" })
|
|
159
188
|
})
|
|
160
189
|
};
|
|
161
|
-
const
|
|
190
|
+
const notificationCatalog = new NotificationCatalog({
|
|
191
|
+
types: notificationRegistries.types,
|
|
192
|
+
channels: notificationRegistries.channels
|
|
193
|
+
});
|
|
194
|
+
const notificationCategoryLists = { listFor: async (orgId, communityId) => (communityId ? await notificationCategories.community.get(communityId, organizations.systemActor(orgId)) : await notificationCategories.org.get(orgId, organizations.systemActor(orgId))).categories };
|
|
162
195
|
const entitlementsUow = new DrizzleUnitOfWork(db, (tx) => ({
|
|
163
196
|
entitlements: new DrizzleEntitlementsRepository(tx, entitlementsLogger),
|
|
164
197
|
spaces: new DrizzleContentRepository(tx, contentLogger),
|
|
165
198
|
outbox: new DrizzleOutboxAppender(tx, outboxLogger)
|
|
166
199
|
}));
|
|
167
|
-
const authors =
|
|
168
|
-
roleOf: async (orgId, orgUserId) => {
|
|
169
|
-
return (await organizationsRepo.findOrgUserById(orgId, orgUserId))?.role ?? null;
|
|
170
|
-
},
|
|
171
|
-
rolesOf: async (orgId, orgUserIds) => {
|
|
172
|
-
const rows = await organizationsRepo.findOrgUsersByIds(orgId, orgUserIds);
|
|
173
|
-
return Object.fromEntries(rows.map((row) => [row.id, row.role]));
|
|
174
|
-
}
|
|
175
|
-
};
|
|
200
|
+
const authors = organizations;
|
|
176
201
|
const entitlements = new EntitlementsServiceImpl({
|
|
177
202
|
repo: new DrizzleEntitlementsRepository(db, entitlementsLogger),
|
|
178
203
|
content,
|
|
204
|
+
permissions: organizations,
|
|
179
205
|
uow: entitlementsUow,
|
|
180
206
|
logger: entitlementsLogger
|
|
181
207
|
});
|
|
@@ -183,10 +209,10 @@ async function buildContainer(config, options) {
|
|
|
183
209
|
const notificationSettings = new NotificationSettingsServiceImpl({
|
|
184
210
|
repo: new DrizzleNotificationSettingsRepository(db, identityLogger),
|
|
185
211
|
categories: {
|
|
186
|
-
communityList: async (orgId, communityId) => (await notificationCategories.community.get(
|
|
187
|
-
orgList: async (orgId) => (await notificationCategories.org.get(orgId, orgId)).categories
|
|
212
|
+
communityList: async (orgId, communityId) => (await notificationCategories.community.get(communityId, organizations.systemActor(orgId))).categories,
|
|
213
|
+
orgList: async (orgId) => (await notificationCategories.org.get(orgId, organizations.systemActor(orgId))).categories
|
|
188
214
|
},
|
|
189
|
-
access: { canOpen: (orgId, actor, communityId) => entitlements.isCommunityMember(
|
|
215
|
+
access: { canOpen: (orgId, actor, communityId) => entitlements.isCommunityMember(communityId, actor) },
|
|
190
216
|
channels: notificationRegistries.channels,
|
|
191
217
|
logger: identityLogger
|
|
192
218
|
});
|
|
@@ -203,6 +229,7 @@ async function buildContainer(config, options) {
|
|
|
203
229
|
repo: new DrizzleProgressRepository(db, progressLogger),
|
|
204
230
|
content,
|
|
205
231
|
access: entitlements,
|
|
232
|
+
system: organizations,
|
|
206
233
|
uow: progressUow,
|
|
207
234
|
logger: progressLogger
|
|
208
235
|
});
|
|
@@ -222,6 +249,7 @@ async function buildContainer(config, options) {
|
|
|
222
249
|
mentions: mentionReads,
|
|
223
250
|
images: assets,
|
|
224
251
|
fileUrls,
|
|
252
|
+
bodies,
|
|
225
253
|
uow: new DrizzleUnitOfWork(db, (tx) => ({
|
|
226
254
|
posts: new DrizzlePostRepository(tx, discussionLogger),
|
|
227
255
|
mentions: new DrizzleMentionsRepository(tx, discussionLogger),
|
|
@@ -245,6 +273,8 @@ async function buildContainer(config, options) {
|
|
|
245
273
|
mentions: new DrizzleMentionsRepository(tx, discussionLogger),
|
|
246
274
|
outbox: new DrizzleOutboxAppender(tx, outboxLogger)
|
|
247
275
|
})),
|
|
276
|
+
...pubsub && { pubsub },
|
|
277
|
+
...options?.live && { live: options.live },
|
|
248
278
|
logger: discussionLogger
|
|
249
279
|
});
|
|
250
280
|
const discussion = new DiscussionServiceImpl({
|
|
@@ -253,6 +283,7 @@ async function buildContainer(config, options) {
|
|
|
253
283
|
content,
|
|
254
284
|
posts,
|
|
255
285
|
spaces: entitlements,
|
|
286
|
+
authors,
|
|
256
287
|
mentions: mentionReads,
|
|
257
288
|
fileUrls,
|
|
258
289
|
uow: new DrizzleUnitOfWork(db, (tx) => ({
|
|
@@ -268,6 +299,10 @@ async function buildContainer(config, options) {
|
|
|
268
299
|
access: entitlements,
|
|
269
300
|
posts,
|
|
270
301
|
chats,
|
|
302
|
+
uow: new DrizzleUnitOfWork(db, (tx) => ({
|
|
303
|
+
subscriptions: new DrizzleSubscriptionsRepository(tx, discussionLogger),
|
|
304
|
+
outbox: new DrizzleOutboxAppender(tx, outboxLogger)
|
|
305
|
+
})),
|
|
271
306
|
logger: discussionLogger
|
|
272
307
|
});
|
|
273
308
|
const digests = new DigestServiceImpl({
|
|
@@ -292,15 +327,23 @@ async function buildContainer(config, options) {
|
|
|
292
327
|
messages: chatReads,
|
|
293
328
|
profiles: discussionReads,
|
|
294
329
|
activities: content,
|
|
295
|
-
access: entitlements
|
|
330
|
+
access: entitlements,
|
|
331
|
+
actors: organizations
|
|
296
332
|
}),
|
|
297
333
|
content: new ContentItemTitles({
|
|
298
334
|
content,
|
|
299
|
-
access: entitlements
|
|
335
|
+
access: entitlements,
|
|
336
|
+
organizations
|
|
300
337
|
})
|
|
301
338
|
};
|
|
302
339
|
const reporting = {
|
|
303
|
-
admin: new AdminReportServiceImpl({
|
|
340
|
+
admin: new AdminReportServiceImpl({
|
|
341
|
+
repo: new DrizzleAdminRepository(db, reportingLogger),
|
|
342
|
+
permissions: organizations,
|
|
343
|
+
spaces: entitlements,
|
|
344
|
+
events: content,
|
|
345
|
+
fileUrls
|
|
346
|
+
}),
|
|
304
347
|
member: new MemberReportServiceImpl({
|
|
305
348
|
repo: new DrizzleMemberRepository(db, reportingLogger),
|
|
306
349
|
content,
|
|
@@ -308,12 +351,14 @@ async function buildContainer(config, options) {
|
|
|
308
351
|
files: assets,
|
|
309
352
|
fileUrls,
|
|
310
353
|
access: entitlements,
|
|
354
|
+
people: organizations,
|
|
311
355
|
notifications: notificationList,
|
|
312
356
|
titles: itemTitles,
|
|
313
357
|
subscriptions,
|
|
314
358
|
notificationCategories,
|
|
315
359
|
notificationSettings,
|
|
316
|
-
channels: notificationRegistries.channels
|
|
360
|
+
channels: notificationRegistries.channels,
|
|
361
|
+
studentPortalUrl: config.studentPortalUrl
|
|
317
362
|
})
|
|
318
363
|
};
|
|
319
364
|
const credentialStore = new DrizzleCredentialStore(db, config.credentialStoreKey, integrationsLogger);
|
|
@@ -346,23 +391,30 @@ async function buildContainer(config, options) {
|
|
|
346
391
|
} : null;
|
|
347
392
|
},
|
|
348
393
|
contentInfo: async (orgId, contentId) => {
|
|
349
|
-
const
|
|
394
|
+
const system = organizations.systemActor(orgId);
|
|
395
|
+
const course = await content.findCourse(contentId, system);
|
|
350
396
|
if (course) return {
|
|
351
397
|
id: course.id,
|
|
352
398
|
title: course.title,
|
|
353
399
|
type: course.type
|
|
354
400
|
};
|
|
355
|
-
const download = await content.
|
|
401
|
+
const download = await content.findDownload(contentId, system);
|
|
356
402
|
if (download) return {
|
|
357
403
|
id: download.id,
|
|
358
404
|
title: download.title,
|
|
359
405
|
type: download.type
|
|
360
406
|
};
|
|
361
|
-
const community = await content.
|
|
362
|
-
|
|
407
|
+
const community = await content.findCommunity(contentId, system);
|
|
408
|
+
if (community) return {
|
|
363
409
|
id: community.id,
|
|
364
410
|
title: community.title,
|
|
365
411
|
type: community.type
|
|
412
|
+
};
|
|
413
|
+
const event = await content.findEvent(contentId, system);
|
|
414
|
+
return event ? {
|
|
415
|
+
id: event.id,
|
|
416
|
+
title: event.title,
|
|
417
|
+
type: event.type
|
|
366
418
|
} : null;
|
|
367
419
|
}
|
|
368
420
|
};
|
|
@@ -375,7 +427,8 @@ async function buildContainer(config, options) {
|
|
|
375
427
|
posts,
|
|
376
428
|
subscriptions,
|
|
377
429
|
discussion,
|
|
378
|
-
chats
|
|
430
|
+
chats,
|
|
431
|
+
bodies
|
|
379
432
|
});
|
|
380
433
|
const recipientReads = notificationReads({
|
|
381
434
|
content,
|
|
@@ -386,6 +439,7 @@ async function buildContainer(config, options) {
|
|
|
386
439
|
subscriptions,
|
|
387
440
|
discussion,
|
|
388
441
|
chats,
|
|
442
|
+
bodies,
|
|
389
443
|
settings: notificationSettings
|
|
390
444
|
});
|
|
391
445
|
const notificationProcessor = new NotificationProcessor({
|
|
@@ -409,6 +463,7 @@ async function buildContainer(config, options) {
|
|
|
409
463
|
mailer,
|
|
410
464
|
lookups: mailerLookups,
|
|
411
465
|
integrations,
|
|
466
|
+
systemActor: (orgId) => organizations.systemActor(orgId),
|
|
412
467
|
credentials: credentialStore,
|
|
413
468
|
webhooks: options?.adapters?.webhooks ?? new FetchWebhookSender({ allowPrivateNetwork: config.webhookAllowPrivateNetwork }),
|
|
414
469
|
mail: wired.mail,
|
|
@@ -429,6 +484,10 @@ async function buildContainer(config, options) {
|
|
|
429
484
|
digests,
|
|
430
485
|
logger: discussionLogger
|
|
431
486
|
}),
|
|
487
|
+
...contentJobs({
|
|
488
|
+
events: content,
|
|
489
|
+
logger: contentLogger
|
|
490
|
+
}),
|
|
432
491
|
...entitlementsJobs({ grants: entitlements }),
|
|
433
492
|
...assetsJobs({
|
|
434
493
|
assets,
|
|
@@ -446,6 +505,10 @@ async function buildContainer(config, options) {
|
|
|
446
505
|
...assetsRecurringJobs
|
|
447
506
|
]);
|
|
448
507
|
const eventBus = new InMemoryEventBus();
|
|
508
|
+
if (pubsub) new ChatLiveForwarder({
|
|
509
|
+
pubsub,
|
|
510
|
+
logger: discussionLogger
|
|
511
|
+
}).attach(eventBus);
|
|
449
512
|
eventBus.subscribeAll((event) => automations.handle(event));
|
|
450
513
|
const outboxConfig = resolveOutboxConfig(config.outbox);
|
|
451
514
|
const outboxRelay = new PollingOutboxRelay(new DrizzleOutboxStore(db, outboxLogger), eventBus, outboxConfig, outboxLogger);
|
|
@@ -504,6 +567,10 @@ async function buildContainer(config, options) {
|
|
|
504
567
|
beforeCreateSession: authHook("beforeCreateSession", async (session) => {
|
|
505
568
|
const person = await identity.getUserByExternalId(session.userId);
|
|
506
569
|
if (!person) return;
|
|
570
|
+
if (organizations.isPlatformAccount(person.email)) {
|
|
571
|
+
authLogger.warn("sign-in refused for the platform account", { userId: person.id });
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
507
574
|
const orgs = await organizations.getOrgUsersForUser(person.id);
|
|
508
575
|
if (orgs.length !== 1) return;
|
|
509
576
|
const org = await organizations.getById(orgs[0].orgId);
|
|
@@ -514,7 +581,7 @@ async function buildContainer(config, options) {
|
|
|
514
581
|
} };
|
|
515
582
|
}),
|
|
516
583
|
beforeCreateOrganization: authHook("beforeCreateOrganization", async ({ organization: org, user: baUser }) => {
|
|
517
|
-
const domainOrg = await organizations.
|
|
584
|
+
const domainOrg = await organizations.provisionOrganization({
|
|
518
585
|
ownerId: baUser.id,
|
|
519
586
|
...org,
|
|
520
587
|
logo: org.logo ?? void 0
|
|
@@ -524,22 +591,15 @@ async function buildContainer(config, options) {
|
|
|
524
591
|
id: domainOrg.id
|
|
525
592
|
} };
|
|
526
593
|
}),
|
|
527
|
-
beforeUpdateOrganization: authHook("beforeUpdateOrganization", async ({ organization }) => {
|
|
528
|
-
await organizations.updateOrganization(organization.id, organization);
|
|
529
|
-
}),
|
|
530
594
|
beforeDeleteOrganization: authHook("beforeDeleteOrganization", async ({ organization }) => {
|
|
531
595
|
await organizations.deleteOrganization(organization.id);
|
|
532
596
|
}),
|
|
533
597
|
beforeAddMember: authHook("beforeAddMember", async ({ member }) => {
|
|
534
598
|
await organizations.addOrgUser({
|
|
535
599
|
orgId: member.organizationId,
|
|
536
|
-
userId: member.userId
|
|
537
|
-
role: parseRole(member.role)
|
|
600
|
+
userId: member.userId
|
|
538
601
|
});
|
|
539
602
|
}),
|
|
540
|
-
beforeUpdateMemberRole: authHook("beforeUpdateMemberRole", async ({ member, newRole }) => {
|
|
541
|
-
await organizations.updateOrgUser(member.organizationId, member.userId, { role: parseRole(newRole) });
|
|
542
|
-
}),
|
|
543
603
|
beforeRemoveMember: authHook("beforeRemoveMember", async ({ member }) => {
|
|
544
604
|
await organizations.removeOrgUser(member.organizationId, member.userId);
|
|
545
605
|
})
|
|
@@ -552,6 +612,7 @@ async function buildContainer(config, options) {
|
|
|
552
612
|
return {
|
|
553
613
|
auth,
|
|
554
614
|
mcp,
|
|
615
|
+
platform,
|
|
555
616
|
identity,
|
|
556
617
|
organizations,
|
|
557
618
|
orgProvider: auth,
|
|
@@ -568,6 +629,7 @@ async function buildContainer(config, options) {
|
|
|
568
629
|
notificationTypes: notificationRegistries.types,
|
|
569
630
|
notificationChannels: notificationRegistries.channels,
|
|
570
631
|
notificationCategories,
|
|
632
|
+
notificationCatalog,
|
|
571
633
|
notificationCategoryLists,
|
|
572
634
|
notificationSettings,
|
|
573
635
|
devices,
|
|
@@ -580,6 +642,8 @@ async function buildContainer(config, options) {
|
|
|
580
642
|
outboxRelay,
|
|
581
643
|
automationEngine,
|
|
582
644
|
scheduler: automationEngine,
|
|
645
|
+
pubsub,
|
|
646
|
+
chatHub: chats.hub,
|
|
583
647
|
logger,
|
|
584
648
|
loggerInstance,
|
|
585
649
|
requestContext: requestLogContext,
|
|
@@ -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 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 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 { BetterAuth } from '@headless-lms/adapter-auth';\n\nimport {\n CommunityNotificationCategories,\n ContentItemTitles,\n ContentService,\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 ChatServiceImpl,\n DigestServiceImpl,\n DiscussionItemTitles,\n DiscussionServiceImpl,\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 OrgAdmin,\n OrganizationServiceImpl,\n OrgNotificationCategories,\n parseRole,\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';\n\nimport type {\n ChannelRegistry,\n CredentialStore,\n EmailSender,\n Logger,\n NotificationChannel,\n NotificationTypeRegistry,\n ObjectStorage,\n OutboxRelay,\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}\n\nexport interface BuildContainerOptions {\n /** Installation's plugins folder, scanned by loadIntegrations. Absent → no integrations. */\n pluginsDir?: string;\n adapters?: AdapterOverrides;\n}\n\nexport interface Config {\n databaseUrl: string;\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 /** Let the default webhook sender reach loopback and private network addresses. Local development only. */\n webhookAllowPrivateNetwork?: boolean;\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 // 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 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 /** 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);\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 workflows = options?.adapters?.workflows;\n const automationEngine: AutomationEngine & Scheduler =\n typeof workflows === 'function'\n ? workflows(automationsLogger)\n : (workflows ?? new InlineAutomationEngine(automationsLogger));\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);\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 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 outbox: new DrizzleOutboxAppender(tx, logger.child({ name: 'org' })),\n }));\n const orgAdminRef: { current: OrgAdmin | undefined } = { current: undefined };\n const organizations = new OrganizationServiceImpl({\n repo: organizationsRepo,\n membersRepo: new DrizzleMembersRepository(db, logger.child({ name: 'org' })),\n people: identity,\n uow: organizationsUow,\n orgAdmin: () => {\n if (!orgAdminRef.current) {\n throw new Error('orgAdmin 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 });\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 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 notificationCategoryLists: NotificationCategories = {\n listFor: async (orgId, communityId) =>\n (communityId\n ? await notificationCategories.community.get(orgId, communityId)\n : await notificationCategories.org.get(orgId, 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 = {\n roleOf: async (orgId: string, orgUserId: string) => {\n const row = await organizationsRepo.findOrgUserById(orgId, orgUserId);\n return row?.role ?? null;\n },\n rolesOf: async (orgId: string, orgUserIds: string[]) => {\n const rows = await organizationsRepo.findOrgUsersByIds(orgId, orgUserIds);\n return Object.fromEntries(rows.map((row) => [row.id, row.role]));\n },\n };\n const entitlementsRepo = new DrizzleEntitlementsRepository(db, entitlementsLogger);\n const entitlements = new EntitlementsServiceImpl({\n repo: entitlementsRepo,\n content,\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(orgId, communityId)).categories,\n orgList: async (orgId) => (await notificationCategories.org.get(orgId, orgId)).categories,\n },\n access: {\n canOpen: (orgId, actor, communityId) =>\n entitlements.isCommunityMember(orgId, actor, communityId),\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 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 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 logger: discussionLogger,\n });\n const discussion = new DiscussionServiceImpl({\n repo: discussionReads,\n access: entitlements,\n content: content,\n posts,\n spaces: entitlements,\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 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 }),\n content: new ContentItemTitles({ content, access: entitlements }),\n };\n\n const reporting = {\n admin: new AdminReportServiceImpl({\n repo: new DrizzleAdminRepository(db, reportingLogger),\n }),\n member: new MemberReportServiceImpl({\n repo: new DrizzleMemberRepository(db, reportingLogger),\n content,\n progress,\n files: assets,\n fileUrls,\n access: entitlements,\n notifications: notificationList,\n titles: itemTitles,\n subscriptions,\n notificationCategories,\n notificationSettings,\n channels: notificationRegistries.channels,\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 course = await content.getCourse(orgId, contentId);\n if (course) {\n return { id: course.id, title: course.title, type: course.type };\n }\n const download = await content.getDownload(orgId, contentId);\n if (download) {\n return { id: download.id, title: download.title, type: download.type };\n }\n const community = await content.getCommunity(orgId, contentId);\n return community ? { id: community.id, title: community.title, type: community.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 });\n const recipientReads = notificationReads({\n content,\n entitlements,\n organizations,\n identity,\n posts,\n subscriptions,\n discussion,\n chats,\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 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 ...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 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 = 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 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.createOrganization({\n ownerId: baUser.id,\n ...org,\n logo: org.logo ?? undefined,\n });\n return { data: { ...org, id: domainOrg.id } };\n },\n ),\n beforeUpdateOrganization: authHook('beforeUpdateOrganization', async ({ organization }) => {\n await organizations.updateOrganization(organization.id, organization);\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 role: parseRole(member.role),\n });\n }),\n beforeUpdateMemberRole: authHook('beforeUpdateMemberRole', async ({ member, newRole }) => {\n await organizations.updateOrgUser(member.organizationId, member.userId, {\n role: parseRole(newRole),\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 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 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 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 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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"}
|