@headless-lms/core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/events.d.ts +66 -3
- package/dist/assets/events.js +19 -3
- package/dist/assets/events.js.map +1 -1
- package/dist/assets/service.js +5 -3
- package/dist/assets/service.js.map +1 -1
- package/dist/automations/actions.d.ts +11 -3
- package/dist/automations/actions.js +38 -5
- package/dist/automations/actions.js.map +1 -1
- package/dist/automations/catalog.d.ts +4 -1
- package/dist/automations/catalog.js +289 -188
- package/dist/automations/catalog.js.map +1 -1
- package/dist/automations/events.d.ts +46 -24
- package/dist/automations/events.js +5 -5
- package/dist/automations/events.js.map +1 -1
- package/dist/automations/index.d.ts +5 -5
- package/dist/automations/index.js +2 -2
- package/dist/automations/model.d.ts +13 -3
- package/dist/automations/model.js +23 -3
- package/dist/automations/model.js.map +1 -1
- package/dist/automations/ports.d.ts +23 -8
- package/dist/automations/service.d.ts +21 -5
- package/dist/automations/service.js +267 -47
- package/dist/automations/service.js.map +1 -1
- package/dist/automations/types.d.ts +2 -2
- package/dist/automations/webhook-signing.d.ts +7 -0
- package/dist/automations/webhook-signing.js +22 -0
- package/dist/automations/webhook-signing.js.map +1 -0
- package/dist/content/events.d.ts +401 -25
- package/dist/content/events.js +125 -19
- package/dist/content/events.js.map +1 -1
- package/dist/content/index.d.ts +5 -5
- package/dist/content/index.js +2 -2
- package/dist/content/model.d.ts +2 -2
- package/dist/content/ports.d.ts +42 -11
- package/dist/content/service.d.ts +15 -9
- package/dist/content/service.js +153 -45
- package/dist/content/service.js.map +1 -1
- package/dist/content/types.d.ts +2 -2
- package/dist/discussion/events.d.ts +60 -4
- package/dist/discussion/events.js +30 -4
- package/dist/discussion/events.js.map +1 -1
- package/dist/discussion/model.d.ts +1 -2
- package/dist/discussion/ports.d.ts +1 -1
- package/dist/discussion/service.d.ts +1 -1
- package/dist/discussion/service.js +11 -10
- package/dist/discussion/service.js.map +1 -1
- package/dist/entitlements/events.d.ts +96 -12
- package/dist/entitlements/events.js +21 -4
- package/dist/entitlements/events.js.map +1 -1
- package/dist/entitlements/service.js +1 -0
- package/dist/entitlements/service.js.map +1 -1
- package/dist/identity/events.d.ts +17 -3
- package/dist/identity/events.js +12 -2
- package/dist/identity/events.js.map +1 -1
- package/dist/identity/ports.d.ts +3 -0
- package/dist/identity/service.d.ts +3 -0
- package/dist/identity/service.js +12 -1
- package/dist/identity/service.js.map +1 -1
- package/dist/integrations/events.d.ts +27 -3
- package/dist/integrations/events.js +15 -3
- package/dist/integrations/events.js.map +1 -1
- package/dist/integrations/service.js +1 -1
- package/dist/integrations/service.js.map +1 -1
- package/dist/organizations/events.d.ts +178 -11
- package/dist/organizations/events.js +52 -9
- package/dist/organizations/events.js.map +1 -1
- package/dist/organizations/index.d.ts +3 -3
- package/dist/organizations/members.js.map +1 -1
- package/dist/organizations/ports.d.ts +6 -10
- package/dist/organizations/roles.js.map +1 -1
- package/dist/organizations/service.d.ts +9 -4
- package/dist/organizations/service.js +131 -72
- package/dist/organizations/service.js.map +1 -1
- package/dist/organizations/types.d.ts +2 -2
- package/dist/progress/events.d.ts +34 -2
- package/dist/progress/events.js +15 -2
- package/dist/progress/events.js.map +1 -1
- package/dist/progress/service.d.ts +2 -2
- package/dist/progress/service.js +4 -2
- package/dist/progress/service.js.map +1 -1
- package/dist/reporting/courses/index.d.ts +4 -0
- package/dist/reporting/courses/index.js +2 -0
- package/dist/reporting/courses/model.d.ts +24 -0
- package/dist/reporting/courses/model.js +1 -0
- package/dist/reporting/courses/ports.d.ts +13 -0
- package/dist/reporting/courses/ports.js +1 -0
- package/dist/reporting/courses/service.d.ts +19 -0
- package/dist/reporting/courses/service.js +20 -0
- package/dist/reporting/courses/service.js.map +1 -0
- package/dist/reporting/learn/model.d.ts +2 -2
- package/dist/reporting/learn/ports.d.ts +2 -1
- package/dist/reporting/learn/service.d.ts +2 -1
- package/dist/reporting/learn/service.js +5 -0
- package/dist/reporting/learn/service.js.map +1 -1
- package/dist/reporting/students/index.d.ts +2 -2
- package/dist/reporting/students/model.d.ts +21 -1
- package/dist/reporting/students/ports.d.ts +3 -1
- package/dist/reporting/students/service.d.ts +2 -1
- package/dist/reporting/students/service.js +3 -0
- package/dist/reporting/students/service.js.map +1 -1
- package/dist/shared/events.d.ts +5 -3
- package/dist/shared/events.js +1 -0
- package/dist/shared/events.js.map +1 -1
- package/dist/shared/id.d.ts +1 -0
- package/dist/shared/id.js +1 -0
- package/dist/shared/id.js.map +1 -1
- package/dist/shared/mailer.d.ts +2 -0
- package/dist/shared/mailer.js.map +1 -1
- package/dist/shared/settings.js.map +1 -1
- package/dist/types/automations.d.ts +13 -2
- package/dist/types/content.d.ts +2 -2
- package/dist/types/discussion.d.ts +3 -2
- package/dist/types/email-templates.d.ts +3 -1
- package/dist/types/events.d.ts +3 -3
- package/dist/types/index.d.ts +6 -6
- package/dist/types/organizations.d.ts +2 -2
- package/dist/types/ports.d.ts +1 -1
- package/dist/types/schemas/assets.d.ts +3 -0
- package/dist/types/schemas/assets.js +2 -1
- package/dist/types/schemas/assets.js.map +1 -1
- package/dist/types/schemas/automations.d.ts +71 -4
- package/dist/types/schemas/automations.js +35 -4
- package/dist/types/schemas/automations.js.map +1 -1
- package/dist/types/schemas/content.d.ts +51 -18
- package/dist/types/schemas/content.js +40 -13
- package/dist/types/schemas/content.js.map +1 -1
- package/dist/types/schemas/credentials.js.map +1 -1
- package/dist/types/schemas/discussion.d.ts +10 -2
- package/dist/types/schemas/discussion.js +12 -5
- package/dist/types/schemas/discussion.js.map +1 -1
- package/dist/types/schemas/entitlements.d.ts +8 -2
- package/dist/types/schemas/entitlements.js +10 -4
- package/dist/types/schemas/entitlements.js.map +1 -1
- package/dist/types/schemas/identity.d.ts +4 -4
- package/dist/types/schemas/identity.js +5 -5
- package/dist/types/schemas/identity.js.map +1 -1
- package/dist/types/schemas/index.d.ts +6 -6
- package/dist/types/schemas/index.js +6 -6
- package/dist/types/schemas/integrations.js.map +1 -1
- package/dist/types/schemas/organizations.d.ts +19 -5
- package/dist/types/schemas/organizations.js +12 -7
- package/dist/types/schemas/organizations.js.map +1 -1
- package/dist/types/schemas/outbox.d.ts +1 -0
- package/dist/types/schemas/outbox.js +1 -0
- package/dist/types/schemas/outbox.js.map +1 -1
- package/dist/types/schemas/progress.d.ts +1 -0
- package/dist/types/schemas/progress.js +1 -0
- package/dist/types/schemas/progress.js.map +1 -1
- package/dist/types/schemas/settings.js.map +1 -1
- package/dist/types/schemas/shared.d.ts +2 -1
- package/dist/types/schemas/shared.js +3 -1
- package/dist/types/schemas/shared.js.map +1 -1
- package/dist/types/shared.d.ts +1 -1
- package/package.json +1 -1
package/dist/content/service.js
CHANGED
|
@@ -1,49 +1,29 @@
|
|
|
1
1
|
import { noopLogger } from "../shared/logger.js";
|
|
2
2
|
import { contentEvents } from "./events.js";
|
|
3
|
-
import "../shared/settings.js";
|
|
4
3
|
import { ConflictError, NotFoundError } from "../shared/errors.js";
|
|
5
4
|
//#region src/content/service.ts
|
|
6
5
|
/** URL-safe slug derived from a title. Domain rule owned by the service. */
|
|
7
6
|
function slugify(title) {
|
|
8
7
|
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
|
|
9
8
|
}
|
|
10
|
-
|
|
11
|
-
const settingsDefaults = { transcriptDownloads: false };
|
|
12
|
-
function toCourseSettings(stored) {
|
|
13
|
-
return {
|
|
14
|
-
...settingsDefaults,
|
|
15
|
-
...stored
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
var ContentServiceImpl = class {
|
|
9
|
+
var ContentService = class {
|
|
19
10
|
repo;
|
|
20
11
|
uow;
|
|
21
|
-
settings;
|
|
22
12
|
logger;
|
|
23
13
|
constructor(params) {
|
|
24
14
|
this.repo = params.repo;
|
|
25
15
|
this.uow = params.uow;
|
|
26
|
-
this.settings = params.settings;
|
|
27
16
|
this.logger = params.logger ?? noopLogger;
|
|
28
17
|
}
|
|
29
|
-
|
|
30
|
-
return this.repo.
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if (!course) return null;
|
|
35
|
-
const settings = await this.settings.get(orgId, "content", id);
|
|
36
|
-
return {
|
|
37
|
-
...course,
|
|
38
|
-
settings: {
|
|
39
|
-
...toCourseSettings(course.settings),
|
|
40
|
-
...settings
|
|
41
|
-
}
|
|
42
|
-
};
|
|
18
|
+
listCourses(orgId, query) {
|
|
19
|
+
return this.repo.listCourses(orgId, query);
|
|
20
|
+
}
|
|
21
|
+
getCourse(orgId, id) {
|
|
22
|
+
return this.repo.findCourseById(orgId, id);
|
|
43
23
|
}
|
|
44
24
|
async createCourse(orgId, input) {
|
|
45
25
|
const course = await this.uow.run(async ({ content, outbox }) => {
|
|
46
|
-
const created = await content.
|
|
26
|
+
const created = await content.createCourse(orgId, input, slugify(input.title));
|
|
47
27
|
await outbox.append([contentEvents.courseCreated.make({
|
|
48
28
|
orgId,
|
|
49
29
|
data: created
|
|
@@ -59,7 +39,7 @@ var ContentServiceImpl = class {
|
|
|
59
39
|
}
|
|
60
40
|
async updateCourse(orgId, id, patch) {
|
|
61
41
|
const course = await this.uow.run(async ({ content, outbox }) => {
|
|
62
|
-
const updated = await content.
|
|
42
|
+
const updated = await content.updateCourse(orgId, id, patch);
|
|
63
43
|
if (!updated) {
|
|
64
44
|
this.logger.warn("course update rejected — not found", {
|
|
65
45
|
orgId,
|
|
@@ -80,29 +60,36 @@ var ContentServiceImpl = class {
|
|
|
80
60
|
});
|
|
81
61
|
return course;
|
|
82
62
|
}
|
|
83
|
-
async
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
63
|
+
async patchCourseSettings(orgId, id, value) {
|
|
64
|
+
const merged = await this.uow.run(async ({ content, outbox }) => {
|
|
65
|
+
const course = await content.findCourseById(orgId, id);
|
|
66
|
+
if (!course) {
|
|
67
|
+
this.logger.warn("course settings patch rejected — not found", {
|
|
68
|
+
orgId,
|
|
69
|
+
courseId: id
|
|
70
|
+
});
|
|
71
|
+
throw new NotFoundError("Course", id);
|
|
72
|
+
}
|
|
73
|
+
const settings = await content.patchCourseSettings(orgId, id, value);
|
|
74
|
+
await outbox.append([contentEvents.courseUpdated.make({
|
|
87
75
|
orgId,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
76
|
+
data: {
|
|
77
|
+
...course,
|
|
78
|
+
settings
|
|
79
|
+
}
|
|
80
|
+
})]);
|
|
81
|
+
return settings;
|
|
82
|
+
});
|
|
93
83
|
this.logger.info("course settings patched", {
|
|
94
84
|
orgId,
|
|
95
85
|
courseId: id,
|
|
96
86
|
value
|
|
97
87
|
});
|
|
98
|
-
return
|
|
99
|
-
...toCourseSettings(course.settings),
|
|
100
|
-
...merged
|
|
101
|
-
};
|
|
88
|
+
return merged;
|
|
102
89
|
}
|
|
103
90
|
async deleteCourse(orgId, id) {
|
|
104
91
|
await this.uow.run(async ({ content, outbox }) => {
|
|
105
|
-
const course = await content.
|
|
92
|
+
const course = await content.findCourseById(orgId, id);
|
|
106
93
|
if (!course) {
|
|
107
94
|
this.logger.warn("course delete rejected — not found", {
|
|
108
95
|
orgId,
|
|
@@ -110,7 +97,7 @@ var ContentServiceImpl = class {
|
|
|
110
97
|
});
|
|
111
98
|
throw new NotFoundError("Course", id);
|
|
112
99
|
}
|
|
113
|
-
if (!await content.
|
|
100
|
+
if (!await content.deleteCourse(orgId, id)) throw new NotFoundError("Course", id);
|
|
114
101
|
await outbox.append([contentEvents.courseDeleted.make({
|
|
115
102
|
orgId,
|
|
116
103
|
data: course
|
|
@@ -122,11 +109,14 @@ var ContentServiceImpl = class {
|
|
|
122
109
|
});
|
|
123
110
|
}
|
|
124
111
|
listCourseModules(orgId, courseId) {
|
|
125
|
-
return this.repo.
|
|
112
|
+
return this.repo.listCourseModules(orgId, courseId);
|
|
126
113
|
}
|
|
127
114
|
listCourseActivities(orgId, courseId) {
|
|
128
115
|
return this.repo.listActivitiesForCourse(orgId, courseId);
|
|
129
116
|
}
|
|
117
|
+
listCourseActivityAssets(orgId, courseId) {
|
|
118
|
+
return this.repo.listActivityAssetsForCourse(orgId, courseId);
|
|
119
|
+
}
|
|
130
120
|
getActivity(orgId, activityId) {
|
|
131
121
|
return this.repo.findActivity(orgId, activityId);
|
|
132
122
|
}
|
|
@@ -472,8 +462,126 @@ var ContentServiceImpl = class {
|
|
|
472
462
|
});
|
|
473
463
|
return assets;
|
|
474
464
|
}
|
|
465
|
+
listBundles(orgId, query) {
|
|
466
|
+
return this.repo.listBundles(orgId, query);
|
|
467
|
+
}
|
|
468
|
+
getBundle(orgId, id) {
|
|
469
|
+
return this.repo.findBundleById(orgId, id);
|
|
470
|
+
}
|
|
471
|
+
async createBundle(orgId, input) {
|
|
472
|
+
const { bundle, items } = await this.uow.run(async ({ content, outbox }) => {
|
|
473
|
+
const created = await content.createBundle(orgId, input);
|
|
474
|
+
const initial = input.contentIds?.length ? await content.listBundleItems(orgId, created.id) : [];
|
|
475
|
+
await outbox.append([contentEvents.bundleCreated.make({
|
|
476
|
+
orgId,
|
|
477
|
+
data: created
|
|
478
|
+
}), ...initial.length > 0 ? [contentEvents.bundleItemAdded.make({
|
|
479
|
+
orgId,
|
|
480
|
+
data: {
|
|
481
|
+
bundleId: created.id,
|
|
482
|
+
items: initial
|
|
483
|
+
}
|
|
484
|
+
})] : []]);
|
|
485
|
+
return {
|
|
486
|
+
bundle: created,
|
|
487
|
+
items: initial
|
|
488
|
+
};
|
|
489
|
+
});
|
|
490
|
+
this.logger.info("bundle created", {
|
|
491
|
+
orgId,
|
|
492
|
+
bundleId: bundle.id,
|
|
493
|
+
name: bundle.name,
|
|
494
|
+
itemCount: items.length
|
|
495
|
+
});
|
|
496
|
+
return bundle;
|
|
497
|
+
}
|
|
498
|
+
async updateBundle(orgId, id, patch) {
|
|
499
|
+
const bundle = await this.uow.run(async ({ content, outbox }) => {
|
|
500
|
+
const updated = await content.updateBundle(orgId, id, patch);
|
|
501
|
+
if (!updated) {
|
|
502
|
+
this.logger.warn("bundle update rejected — not found", {
|
|
503
|
+
orgId,
|
|
504
|
+
bundleId: id
|
|
505
|
+
});
|
|
506
|
+
throw new NotFoundError("Bundle", id);
|
|
507
|
+
}
|
|
508
|
+
await outbox.append([contentEvents.bundleUpdated.make({
|
|
509
|
+
orgId,
|
|
510
|
+
data: updated
|
|
511
|
+
})]);
|
|
512
|
+
return updated;
|
|
513
|
+
});
|
|
514
|
+
this.logger.info("bundle updated", {
|
|
515
|
+
orgId,
|
|
516
|
+
bundleId: id,
|
|
517
|
+
fields: Object.keys(patch)
|
|
518
|
+
});
|
|
519
|
+
return bundle;
|
|
520
|
+
}
|
|
521
|
+
async deleteBundle(orgId, id) {
|
|
522
|
+
await this.uow.run(async ({ content, outbox }) => {
|
|
523
|
+
const bundle = await content.findBundleById(orgId, id);
|
|
524
|
+
if (!bundle) {
|
|
525
|
+
this.logger.warn("bundle delete rejected — not found", {
|
|
526
|
+
orgId,
|
|
527
|
+
bundleId: id
|
|
528
|
+
});
|
|
529
|
+
throw new NotFoundError("Bundle", id);
|
|
530
|
+
}
|
|
531
|
+
if (!await content.deleteBundle(orgId, id)) throw new NotFoundError("Bundle", id);
|
|
532
|
+
await outbox.append([contentEvents.bundleDeleted.make({
|
|
533
|
+
orgId,
|
|
534
|
+
data: bundle
|
|
535
|
+
})]);
|
|
536
|
+
});
|
|
537
|
+
this.logger.info("bundle deleted", {
|
|
538
|
+
orgId,
|
|
539
|
+
bundleId: id
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
listBundleItems(orgId, bundleId) {
|
|
543
|
+
return this.repo.listBundleItems(orgId, bundleId);
|
|
544
|
+
}
|
|
545
|
+
async addBundleItem(orgId, bundleId, contentId) {
|
|
546
|
+
const items = await this.uow.run(async ({ content, outbox }) => {
|
|
547
|
+
const all = await content.addBundleItem(orgId, bundleId, contentId);
|
|
548
|
+
await outbox.append([contentEvents.bundleItemAdded.make({
|
|
549
|
+
orgId,
|
|
550
|
+
data: {
|
|
551
|
+
bundleId,
|
|
552
|
+
items: all
|
|
553
|
+
}
|
|
554
|
+
})]);
|
|
555
|
+
return all;
|
|
556
|
+
});
|
|
557
|
+
this.logger.info("bundle item added", {
|
|
558
|
+
orgId,
|
|
559
|
+
bundleId,
|
|
560
|
+
contentId
|
|
561
|
+
});
|
|
562
|
+
return items;
|
|
563
|
+
}
|
|
564
|
+
async removeBundleItem(orgId, bundleId, contentId) {
|
|
565
|
+
const items = await this.uow.run(async ({ content, outbox }) => {
|
|
566
|
+
const all = await content.removeBundleItem(orgId, bundleId, contentId);
|
|
567
|
+
await outbox.append([contentEvents.bundleItemRemoved.make({
|
|
568
|
+
orgId,
|
|
569
|
+
data: {
|
|
570
|
+
bundleId,
|
|
571
|
+
items: all
|
|
572
|
+
}
|
|
573
|
+
})]);
|
|
574
|
+
return all;
|
|
575
|
+
});
|
|
576
|
+
this.logger.info("bundle item removed", {
|
|
577
|
+
orgId,
|
|
578
|
+
bundleId,
|
|
579
|
+
contentId
|
|
580
|
+
});
|
|
581
|
+
return items;
|
|
582
|
+
}
|
|
475
583
|
};
|
|
476
584
|
//#endregion
|
|
477
|
-
export {
|
|
585
|
+
export { ContentService };
|
|
478
586
|
|
|
479
587
|
//# sourceMappingURL=service.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":[],"sources":["../../src/content/service.ts"],"sourcesContent":["import type {\n Activity,\n Course,\n CourseSettings,\n Download,\n DownloadAsset,\n Module,\n SaveActivityInput,\n} from './model.js';\nimport type { ContentService, ContentRepository, ContentUnitOfWork } from './ports.js';\nimport type {\n AddDownloadAssetInput,\n CreateCourseInput,\n CreateDownloadInput,\n ListCoursesQuery,\n ListDownloadsQuery,\n Page,\n UpdateCourseInput,\n UpdateDownloadInput,\n} from './types.js';\nimport { contentEvents } from './events.js';\nimport type { Logger } from '../shared/ports.js';\nimport { SettingsNamespace, type SettingsService } from '../shared/settings.js';\nimport { NotFoundError, ConflictError } from '../shared/errors.js';\nimport { noopLogger } from '../shared/logger.js';\n\n/** URL-safe slug derived from a title. Domain rule owned by the service. */\nfunction slugify(title: string): string {\n return title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '');\n}\n\n/** Defaults for keys the stored blob doesn't carry (rows predating a setting). */\nconst settingsDefaults: CourseSettings = { transcriptDownloads: false };\n\nfunction toCourseSettings(stored: unknown): CourseSettings {\n return { ...settingsDefaults, ...(stored as Partial<CourseSettings> | null) };\n}\n\nexport type ContentServiceParams = {\n repo: ContentRepository;\n uow: ContentUnitOfWork;\n settings: SettingsService;\n logger?: Logger;\n};\n\nexport class ContentServiceImpl implements ContentService {\n private readonly repo: ContentRepository;\n private readonly uow: ContentUnitOfWork;\n private readonly settings: SettingsService;\n private readonly logger: Logger;\n\n constructor(params: ContentServiceParams) {\n this.repo = params.repo;\n this.uow = params.uow;\n this.settings = params.settings;\n this.logger = params.logger ?? noopLogger;\n }\n\n list(orgId: string, query: ListCoursesQuery): Promise<Page<Course>> {\n return this.repo.list(orgId, query);\n }\n\n async getCourse(orgId: string, id: string): Promise<Course | null> {\n const course = await this.repo.findById(orgId, id);\n if (!course) {\n return null;\n }\n const settings = await this.settings.get<Partial<CourseSettings>>(\n orgId,\n SettingsNamespace.content,\n id,\n );\n\n return {\n ...course,\n settings: {\n ...toCourseSettings(course.settings),\n ...settings,\n } as Course['settings'],\n };\n }\n\n async createCourse(orgId: string, input: CreateCourseInput): Promise<Course> {\n const course = await this.uow.run(async ({ content, outbox }) => {\n const created = await content.create(orgId, input, slugify(input.title));\n await outbox.append([\n contentEvents.courseCreated.make({ orgId, data: created }),\n ]);\n return created;\n });\n this.logger.info('course created', { orgId, courseId: course.id, title: course.title });\n return course;\n }\n\n async updateCourse(orgId: string, id: string, patch: UpdateCourseInput): Promise<Course> {\n const course = await this.uow.run(async ({ content, outbox }) => {\n const updated = await content.update(orgId, id, patch);\n if (!updated) {\n this.logger.warn('course update rejected — not found', { orgId, courseId: id });\n throw new NotFoundError('Course', id);\n }\n await outbox.append([\n contentEvents.courseUpdated.make({ orgId, data: updated }),\n ]);\n return updated;\n });\n this.logger.info('course updated', { orgId, courseId: id, fields: Object.keys(patch) });\n return course;\n }\n\n async patchSettings(\n orgId: string,\n id: string,\n value: Partial<CourseSettings>,\n ): Promise<CourseSettings> {\n const course = await this.repo.findById(orgId, id);\n if (!course) {\n this.logger.warn('course settings patch rejected — not found', { orgId, courseId: id });\n throw new NotFoundError('Course', id);\n }\n const merged = await this.settings.patch<Partial<CourseSettings>>(\n orgId,\n SettingsNamespace.content,\n id,\n value,\n );\n this.logger.info('course settings patched', { orgId, courseId: id, value });\n return { ...toCourseSettings(course.settings), ...merged };\n }\n\n async deleteCourse(orgId: string, id: string): Promise<void> {\n await this.uow.run(async ({ content, outbox }) => {\n // Snapshot before the delete — the event carries the last known state.\n const course = await content.findById(orgId, id);\n if (!course) {\n this.logger.warn('course delete rejected — not found', { orgId, courseId: id });\n throw new NotFoundError('Course', id);\n }\n const ok = await content.delete(orgId, id);\n if (!ok) {\n throw new NotFoundError('Course', id);\n }\n await outbox.append([contentEvents.courseDeleted.make({ orgId, data: course })]);\n });\n this.logger.info('course deleted', { orgId, courseId: id });\n }\n\n // --- modules & activities ------------------------------------------------\n\n listCourseModules(orgId: string, courseId: string): Promise<Module[]> {\n return this.repo.listForCourse(orgId, courseId);\n }\n listCourseActivities(orgId: string, courseId: string): Promise<Activity[]> {\n return this.repo.listActivitiesForCourse(orgId, courseId);\n }\n getActivity(orgId: string, activityId: string): Promise<Activity | null> {\n return this.repo.findActivity(orgId, activityId);\n }\n getModule(orgId: string, moduleId: string): Promise<Module | null> {\n return this.repo.findModule(orgId, moduleId);\n }\n\n async reorderModules(orgId: string, courseId: string, orderedIds: string[]): Promise<Module[]> {\n const modules = await this.uow.run(async ({ content, outbox }) => {\n const reordered = await content.reorderModules(orgId, courseId, orderedIds);\n await outbox.append([\n contentEvents.modulesReordered.make({ orgId, data: reordered }),\n ]);\n return reordered;\n });\n this.logger.debug('modules reordered', {\n orgId,\n courseId,\n moduleIds: modules.map((m) => m.id),\n });\n return modules;\n }\n\n async createModule(orgId: string, courseId: string, title: string): Promise<Module[]> {\n const { modules, created } = await this.uow.run(async ({ content, outbox }) => {\n const all = await content.createModule(orgId, courseId, title);\n // The list is ordered by seq and the new module is appended at max(seq)+1.\n const module = all.at(-1);\n if (!module) {\n throw new Error('failed to load created module');\n }\n await outbox.append([contentEvents.moduleCreated.make({ orgId, data: module })]);\n return { modules: all, created: module };\n });\n this.logger.info('module created', { orgId, courseId, moduleId: created.id, title });\n return modules;\n }\n\n async updateModule(\n orgId: string,\n courseId: string,\n moduleId: string,\n title: string,\n ): Promise<Module[]> {\n const modules = await this.uow.run(async ({ content, outbox }) => {\n const existing = await content.findModule(orgId, moduleId);\n if (!existing || existing.courseId !== courseId) {\n this.logger.warn('module update rejected — not found in course', {\n orgId,\n courseId,\n moduleId,\n });\n throw new NotFoundError('Module', moduleId);\n }\n const all = await content.updateModule(orgId, courseId, moduleId, title);\n const module = all.find((m) => m.id === moduleId);\n if (!module) {\n throw new NotFoundError('Module', moduleId);\n }\n await outbox.append([contentEvents.moduleUpdated.make({ orgId, data: module })]);\n return all;\n });\n this.logger.info('module updated', { orgId, courseId, moduleId, title });\n return modules;\n }\n\n async deleteModule(orgId: string, courseId: string, moduleId: string): Promise<Module[]> {\n const modules = await this.uow.run(async ({ content, outbox }) => {\n // Snapshot before the delete — the event carries the last known state.\n const module = await content.findModule(orgId, moduleId);\n if (!module || module.courseId !== courseId) {\n this.logger.warn('module delete rejected — not found in course', {\n orgId,\n courseId,\n moduleId,\n });\n throw new NotFoundError('Module', moduleId);\n }\n const remaining = await content.deleteModule(orgId, courseId, moduleId);\n await outbox.append([contentEvents.moduleDeleted.make({ orgId, data: module })]);\n return remaining;\n });\n this.logger.info('module deleted', { orgId, courseId, moduleId });\n return modules;\n }\n\n async reorderActivities(\n orgId: string,\n courseId: string,\n moduleId: string,\n orderedIds: string[],\n ): Promise<Module[]> {\n const modules = await this.uow.run(async ({ content, outbox }) => {\n const reordered = await content.reorderActivities(orgId, courseId, moduleId, orderedIds);\n const module = reordered.find((m) => m.id === moduleId);\n if (!module) {\n throw new NotFoundError('Module', moduleId);\n }\n await outbox.append([\n contentEvents.activitiesReordered.make({ orgId, data: module }),\n ]);\n return reordered;\n });\n this.logger.debug('activities reordered', { orgId, courseId, moduleId });\n return modules;\n }\n\n async saveActivity(\n orgId: string,\n courseId: string,\n moduleId: string,\n input: SaveActivityInput,\n activityId?: string,\n ): Promise<Module[]> {\n const { modules, activity } = await this.uow.run(async ({ content, outbox }) => {\n if (activityId) {\n const existing = await content.findActivity(orgId, activityId);\n if (!existing || existing.moduleId !== moduleId || existing.courseId !== courseId) {\n this.logger.warn('activity update rejected — not found in module', {\n orgId,\n courseId,\n moduleId,\n activityId,\n });\n throw new NotFoundError('Activity', activityId);\n }\n } else {\n const module = await content.findModule(orgId, moduleId);\n if (!module || module.courseId !== courseId) {\n this.logger.warn('activity create rejected — module not found in course', {\n orgId,\n courseId,\n moduleId,\n });\n throw new NotFoundError('Module', moduleId);\n }\n }\n\n const { modules: all, activity: saved } = await content.saveActivity(\n orgId,\n courseId,\n moduleId,\n input,\n activityId,\n );\n await outbox.append([\n activityId\n ? contentEvents.activityUpdated.make({ orgId, data: saved })\n : contentEvents.activityCreated.make({ orgId, data: saved }),\n ]);\n return { modules: all, activity: saved };\n });\n this.logger.info(activityId ? 'activity updated' : 'activity created', {\n orgId,\n courseId,\n moduleId,\n activityId: activity.id,\n });\n return modules;\n }\n\n async deleteActivity(\n orgId: string,\n courseId: string,\n moduleId: string,\n activityId: string,\n ): Promise<Module[]> {\n const modules = await this.uow.run(async ({ content, outbox }) => {\n // Snapshot before the delete — the event carries the last known state.\n const activity = await content.findActivity(orgId, activityId);\n if (!activity || activity.moduleId !== moduleId || activity.courseId !== courseId) {\n this.logger.warn('activity delete rejected — not found in module', {\n orgId,\n courseId,\n moduleId,\n activityId,\n });\n throw new NotFoundError('Activity', activityId);\n }\n const remaining = await content.deleteActivity(orgId, courseId, moduleId, activityId);\n await outbox.append([\n contentEvents.activityDeleted.make({ orgId, data: activity }),\n ]);\n return remaining;\n });\n this.logger.info('activity deleted', { orgId, courseId, moduleId, activityId });\n return modules;\n }\n\n // --- downloads ------------------------------------------------------------\n\n listDownloads(orgId: string, query: ListDownloadsQuery): Promise<Page<Download>> {\n return this.repo.listDownloads(orgId, query);\n }\n\n getDownload(orgId: string, id: string): Promise<Download | null> {\n return this.repo.getDownload(orgId, id);\n }\n\n async createDownload(orgId: string, input: CreateDownloadInput): Promise<Download> {\n const download = await this.uow.run(async ({ content, outbox }) => {\n const created = await content.createDownload(orgId, input, slugify(input.title));\n await outbox.append([\n contentEvents.downloadCreated.make({ orgId, data: created }),\n ]);\n return created;\n });\n this.logger.info('download created', { orgId, downloadId: download.id, title: download.title });\n return download;\n }\n\n async updateDownload(orgId: string, id: string, patch: UpdateDownloadInput): Promise<Download> {\n const download = await this.uow.run(async ({ content, outbox }) => {\n const updated = await content.updateDownload(orgId, id, patch);\n if (!updated) {\n this.logger.warn('download update rejected — not found', { orgId, downloadId: id });\n throw new NotFoundError('Download', id);\n }\n await outbox.append([\n contentEvents.downloadUpdated.make({ orgId, data: updated }),\n ]);\n return updated;\n });\n this.logger.info('download updated', { orgId, downloadId: id, fields: Object.keys(patch) });\n return download;\n }\n\n async removeDownload(orgId: string, id: string): Promise<void> {\n await this.uow.run(async ({ content, outbox }) => {\n // Snapshot before the delete — the event carries the last known state.\n const download = await content.getDownload(orgId, id);\n if (!download) {\n this.logger.warn('download delete rejected — not found', { orgId, downloadId: id });\n throw new NotFoundError('Download', id);\n }\n const ok = await content.deleteDownload(orgId, id);\n if (!ok) {\n throw new NotFoundError('Download', id);\n }\n await outbox.append([\n contentEvents.downloadDeleted.make({ orgId, data: download }),\n ]);\n });\n this.logger.info('download deleted', { orgId, downloadId: id });\n }\n\n // --- download asset links ------------------------------------------------\n\n listDownloadAssets(orgId: string, downloadId: string): Promise<DownloadAsset[]> {\n return this.repo.listDownloadAssets(orgId, downloadId);\n }\n\n async addDownloadAsset(\n orgId: string,\n downloadId: string,\n input: AddDownloadAssetInput,\n ): Promise<DownloadAsset[]> {\n const assets = await this.uow.run(async ({ content, outbox }) => {\n const assets = await content.addDownloadAsset(orgId, downloadId, input);\n await outbox.append([\n contentEvents.downloadAssetAdded.make({ orgId, data: { downloadId, assets } }),\n ]);\n return assets;\n });\n this.logger.info('download asset added', { orgId, downloadId, assetId: input.assetId });\n return assets;\n }\n\n async removeDownloadAsset(\n orgId: string,\n downloadId: string,\n assetId: string,\n ): Promise<DownloadAsset[]> {\n const assets = await this.uow.run(async ({ content, outbox }) => {\n const assets = await content.removeDownloadAsset(orgId, downloadId, assetId);\n await outbox.append([\n contentEvents.downloadAssetRemoved.make({ orgId, data: { downloadId, assets } }),\n ]);\n return assets;\n });\n this.logger.info('download asset removed', { orgId, downloadId, assetId });\n return assets;\n }\n\n async renameDownloadAsset(\n orgId: string,\n downloadId: string,\n assetId: string,\n displayName: string | null,\n ): Promise<DownloadAsset[]> {\n const assets = await this.uow.run(async ({ content, outbox }) => {\n const assets = await content.renameDownloadAsset(orgId, downloadId, assetId, displayName);\n await outbox.append([\n contentEvents.downloadAssetRenamed.make({ orgId, data: { downloadId, assets } }),\n ]);\n return assets;\n });\n this.logger.info('download asset renamed', { orgId, downloadId, assetId });\n return assets;\n }\n\n /** The caller must send the COMPLETE ordered set — a partial list would\n * silently unlink whatever it omitted. */\n async reorderDownloadAssets(\n orgId: string,\n downloadId: string,\n assetIds: string[],\n ): Promise<DownloadAsset[]> {\n const assets = await this.uow.run(async ({ content, outbox }) => {\n const current = await content.listDownloadAssets(orgId, downloadId);\n const currentIds = new Set(current.map((a) => a.assetId));\n const requestedIds = new Set(assetIds);\n if (\n requestedIds.size !== assetIds.length ||\n requestedIds.size !== currentIds.size ||\n !assetIds.every((id) => currentIds.has(id))\n ) {\n this.logger.warn('download asset reorder rejected — id set mismatch', {\n orgId,\n downloadId,\n assetIds,\n currentIds: [...currentIds],\n });\n throw new ConflictError(\"Ordered asset ids does not match the download's current assets\");\n }\n const assets = await content.reorderDownloadAssets(orgId, downloadId, assetIds);\n await outbox.append([\n contentEvents.downloadAssetsReordered.make({ orgId, data: { downloadId, assets } }),\n ]);\n return assets;\n });\n this.logger.debug('download assets reordered', { orgId, downloadId });\n return assets;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"service.js","names":[],"sources":["../../src/content/service.ts"],"sourcesContent":["import type {\n Activity,\n ActivityAsset,\n Bundle,\n BundleItem,\n Course,\n CourseSettings,\n Download,\n DownloadAsset,\n Module,\n SaveActivityInput,\n} from './model.js';\nimport type {\n BundlesService,\n CourseManagementService,\n ContentRepository,\n ContentUnitOfWork,\n DownloadablesService,\n} from './ports.js';\nimport type {\n AddDownloadAssetInput,\n CreateBundleInput,\n CreateCourseInput,\n CreateDownloadInput,\n ListBundlesQuery,\n ListCoursesQuery,\n ListDownloadsQuery,\n Page,\n UpdateBundleInput,\n UpdateCourseInput,\n UpdateDownloadInput,\n} from './types.js';\nimport { contentEvents } from './events.js';\nimport type { Logger } from '../shared/ports.js';\nimport { NotFoundError, ConflictError } from '../shared/errors.js';\nimport { noopLogger } from '../shared/logger.js';\n\n/** URL-safe slug derived from a title. Domain rule owned by the service. */\nfunction slugify(title: string): string {\n return title\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '');\n}\n\nexport type ContentServiceParams = {\n repo: ContentRepository;\n uow: ContentUnitOfWork;\n logger?: Logger;\n};\n\nexport class ContentService\n implements CourseManagementService, DownloadablesService, BundlesService\n{\n private readonly repo: ContentRepository;\n private readonly uow: ContentUnitOfWork;\n private readonly logger: Logger;\n\n constructor(params: ContentServiceParams) {\n this.repo = params.repo;\n this.uow = params.uow;\n this.logger = params.logger ?? noopLogger;\n }\n\n listCourses(orgId: string, query: ListCoursesQuery): Promise<Page<Course>> {\n return this.repo.listCourses(orgId, query);\n }\n\n getCourse(orgId: string, id: string): Promise<Course | null> {\n return this.repo.findCourseById(orgId, id);\n }\n\n async createCourse(orgId: string, input: CreateCourseInput): Promise<Course> {\n const course = await this.uow.run(async ({ content, outbox }) => {\n const created = await content.createCourse(orgId, input, slugify(input.title));\n await outbox.append([contentEvents.courseCreated.make({ orgId, data: created })]);\n return created;\n });\n this.logger.info('course created', { orgId, courseId: course.id, title: course.title });\n return course;\n }\n\n async updateCourse(orgId: string, id: string, patch: UpdateCourseInput): Promise<Course> {\n const course = await this.uow.run(async ({ content, outbox }) => {\n const updated = await content.updateCourse(orgId, id, patch);\n if (!updated) {\n this.logger.warn('course update rejected — not found', { orgId, courseId: id });\n throw new NotFoundError('Course', id);\n }\n await outbox.append([contentEvents.courseUpdated.make({ orgId, data: updated })]);\n return updated;\n });\n this.logger.info('course updated', { orgId, courseId: id, fields: Object.keys(patch) });\n return course;\n }\n\n async patchCourseSettings(\n orgId: string,\n id: string,\n value: Partial<CourseSettings>,\n ): Promise<CourseSettings> {\n const merged = await this.uow.run(async ({ content, outbox }) => {\n const course = await content.findCourseById(orgId, id);\n if (!course) {\n this.logger.warn('course settings patch rejected — not found', { orgId, courseId: id });\n throw new NotFoundError('Course', id);\n }\n const settings = await content.patchCourseSettings(orgId, id, value);\n await outbox.append([\n contentEvents.courseUpdated.make({ orgId, data: { ...course, settings } }),\n ]);\n return settings;\n });\n this.logger.info('course settings patched', { orgId, courseId: id, value });\n return merged;\n }\n\n async deleteCourse(orgId: string, id: string): Promise<void> {\n await this.uow.run(async ({ content, outbox }) => {\n // Snapshot before the delete — the event carries the last known state.\n const course = await content.findCourseById(orgId, id);\n if (!course) {\n this.logger.warn('course delete rejected — not found', { orgId, courseId: id });\n throw new NotFoundError('Course', id);\n }\n const ok = await content.deleteCourse(orgId, id);\n if (!ok) {\n throw new NotFoundError('Course', id);\n }\n await outbox.append([contentEvents.courseDeleted.make({ orgId, data: course })]);\n });\n this.logger.info('course deleted', { orgId, courseId: id });\n }\n\n // --- modules & activities ------------------------------------------------\n\n listCourseModules(orgId: string, courseId: string): Promise<Module[]> {\n return this.repo.listCourseModules(orgId, courseId);\n }\n listCourseActivities(orgId: string, courseId: string): Promise<Activity[]> {\n return this.repo.listActivitiesForCourse(orgId, courseId);\n }\n listCourseActivityAssets(orgId: string, courseId: string): Promise<ActivityAsset[]> {\n return this.repo.listActivityAssetsForCourse(orgId, courseId);\n }\n getActivity(orgId: string, activityId: string): Promise<Activity | null> {\n return this.repo.findActivity(orgId, activityId);\n }\n getModule(orgId: string, moduleId: string): Promise<Module | null> {\n return this.repo.findModule(orgId, moduleId);\n }\n\n async reorderModules(orgId: string, courseId: string, orderedIds: string[]): Promise<Module[]> {\n const modules = await this.uow.run(async ({ content, outbox }) => {\n const reordered = await content.reorderModules(orgId, courseId, orderedIds);\n await outbox.append([contentEvents.modulesReordered.make({ orgId, data: reordered })]);\n return reordered;\n });\n this.logger.debug('modules reordered', {\n orgId,\n courseId,\n moduleIds: modules.map((m) => m.id),\n });\n return modules;\n }\n\n async createModule(orgId: string, courseId: string, title: string): Promise<Module[]> {\n const { modules, created } = await this.uow.run(async ({ content, outbox }) => {\n const all = await content.createModule(orgId, courseId, title);\n // The list is ordered by seq and the new module is appended at max(seq)+1.\n const module = all.at(-1);\n if (!module) {\n throw new Error('failed to load created module');\n }\n await outbox.append([contentEvents.moduleCreated.make({ orgId, data: module })]);\n return { modules: all, created: module };\n });\n this.logger.info('module created', { orgId, courseId, moduleId: created.id, title });\n return modules;\n }\n\n async updateModule(\n orgId: string,\n courseId: string,\n moduleId: string,\n title: string,\n ): Promise<Module[]> {\n const modules = await this.uow.run(async ({ content, outbox }) => {\n const existing = await content.findModule(orgId, moduleId);\n if (!existing || existing.courseId !== courseId) {\n this.logger.warn('module update rejected — not found in course', {\n orgId,\n courseId,\n moduleId,\n });\n throw new NotFoundError('Module', moduleId);\n }\n const all = await content.updateModule(orgId, courseId, moduleId, title);\n const module = all.find((m) => m.id === moduleId);\n if (!module) {\n throw new NotFoundError('Module', moduleId);\n }\n await outbox.append([contentEvents.moduleUpdated.make({ orgId, data: module })]);\n return all;\n });\n this.logger.info('module updated', { orgId, courseId, moduleId, title });\n return modules;\n }\n\n async deleteModule(orgId: string, courseId: string, moduleId: string): Promise<Module[]> {\n const modules = await this.uow.run(async ({ content, outbox }) => {\n // Snapshot before the delete — the event carries the last known state.\n const module = await content.findModule(orgId, moduleId);\n if (!module || module.courseId !== courseId) {\n this.logger.warn('module delete rejected — not found in course', {\n orgId,\n courseId,\n moduleId,\n });\n throw new NotFoundError('Module', moduleId);\n }\n const remaining = await content.deleteModule(orgId, courseId, moduleId);\n await outbox.append([contentEvents.moduleDeleted.make({ orgId, data: module })]);\n return remaining;\n });\n this.logger.info('module deleted', { orgId, courseId, moduleId });\n return modules;\n }\n\n async reorderActivities(\n orgId: string,\n courseId: string,\n moduleId: string,\n orderedIds: string[],\n ): Promise<Module[]> {\n const modules = await this.uow.run(async ({ content, outbox }) => {\n const reordered = await content.reorderActivities(orgId, courseId, moduleId, orderedIds);\n const module = reordered.find((m) => m.id === moduleId);\n if (!module) {\n throw new NotFoundError('Module', moduleId);\n }\n await outbox.append([contentEvents.activitiesReordered.make({ orgId, data: module })]);\n return reordered;\n });\n this.logger.debug('activities reordered', { orgId, courseId, moduleId });\n return modules;\n }\n\n async saveActivity(\n orgId: string,\n courseId: string,\n moduleId: string,\n input: SaveActivityInput,\n activityId?: string,\n ): Promise<Module[]> {\n const { modules, activity } = await this.uow.run(async ({ content, outbox }) => {\n if (activityId) {\n const existing = await content.findActivity(orgId, activityId);\n if (!existing || existing.moduleId !== moduleId || existing.courseId !== courseId) {\n this.logger.warn('activity update rejected — not found in module', {\n orgId,\n courseId,\n moduleId,\n activityId,\n });\n throw new NotFoundError('Activity', activityId);\n }\n } else {\n const module = await content.findModule(orgId, moduleId);\n if (!module || module.courseId !== courseId) {\n this.logger.warn('activity create rejected — module not found in course', {\n orgId,\n courseId,\n moduleId,\n });\n throw new NotFoundError('Module', moduleId);\n }\n }\n\n const { modules: all, activity: saved } = await content.saveActivity(\n orgId,\n courseId,\n moduleId,\n input,\n activityId,\n );\n await outbox.append([\n activityId\n ? contentEvents.activityUpdated.make({ orgId, data: saved })\n : contentEvents.activityCreated.make({ orgId, data: saved }),\n ]);\n return { modules: all, activity: saved };\n });\n this.logger.info(activityId ? 'activity updated' : 'activity created', {\n orgId,\n courseId,\n moduleId,\n activityId: activity.id,\n });\n return modules;\n }\n\n async deleteActivity(\n orgId: string,\n courseId: string,\n moduleId: string,\n activityId: string,\n ): Promise<Module[]> {\n const modules = await this.uow.run(async ({ content, outbox }) => {\n // Snapshot before the delete — the event carries the last known state.\n const activity = await content.findActivity(orgId, activityId);\n if (!activity || activity.moduleId !== moduleId || activity.courseId !== courseId) {\n this.logger.warn('activity delete rejected — not found in module', {\n orgId,\n courseId,\n moduleId,\n activityId,\n });\n throw new NotFoundError('Activity', activityId);\n }\n const remaining = await content.deleteActivity(orgId, courseId, moduleId, activityId);\n await outbox.append([contentEvents.activityDeleted.make({ orgId, data: activity })]);\n return remaining;\n });\n this.logger.info('activity deleted', { orgId, courseId, moduleId, activityId });\n return modules;\n }\n\n // --- downloads ------------------------------------------------------------\n\n listDownloads(orgId: string, query: ListDownloadsQuery): Promise<Page<Download>> {\n return this.repo.listDownloads(orgId, query);\n }\n\n getDownload(orgId: string, id: string): Promise<Download | null> {\n return this.repo.getDownload(orgId, id);\n }\n\n async createDownload(orgId: string, input: CreateDownloadInput): Promise<Download> {\n const download = await this.uow.run(async ({ content, outbox }) => {\n const created = await content.createDownload(orgId, input, slugify(input.title));\n await outbox.append([contentEvents.downloadCreated.make({ orgId, data: created })]);\n return created;\n });\n this.logger.info('download created', { orgId, downloadId: download.id, title: download.title });\n return download;\n }\n\n async updateDownload(orgId: string, id: string, patch: UpdateDownloadInput): Promise<Download> {\n const download = await this.uow.run(async ({ content, outbox }) => {\n const updated = await content.updateDownload(orgId, id, patch);\n if (!updated) {\n this.logger.warn('download update rejected — not found', { orgId, downloadId: id });\n throw new NotFoundError('Download', id);\n }\n await outbox.append([contentEvents.downloadUpdated.make({ orgId, data: updated })]);\n return updated;\n });\n this.logger.info('download updated', { orgId, downloadId: id, fields: Object.keys(patch) });\n return download;\n }\n\n async removeDownload(orgId: string, id: string): Promise<void> {\n await this.uow.run(async ({ content, outbox }) => {\n // Snapshot before the delete — the event carries the last known state.\n const download = await content.getDownload(orgId, id);\n if (!download) {\n this.logger.warn('download delete rejected — not found', { orgId, downloadId: id });\n throw new NotFoundError('Download', id);\n }\n const ok = await content.deleteDownload(orgId, id);\n if (!ok) {\n throw new NotFoundError('Download', id);\n }\n await outbox.append([contentEvents.downloadDeleted.make({ orgId, data: download })]);\n });\n this.logger.info('download deleted', { orgId, downloadId: id });\n }\n\n // --- download asset links ------------------------------------------------\n\n listDownloadAssets(orgId: string, downloadId: string): Promise<DownloadAsset[]> {\n return this.repo.listDownloadAssets(orgId, downloadId);\n }\n\n async addDownloadAsset(\n orgId: string,\n downloadId: string,\n input: AddDownloadAssetInput,\n ): Promise<DownloadAsset[]> {\n const assets = await this.uow.run(async ({ content, outbox }) => {\n const assets = await content.addDownloadAsset(orgId, downloadId, input);\n await outbox.append([\n contentEvents.downloadAssetAdded.make({ orgId, data: { downloadId, assets } }),\n ]);\n return assets;\n });\n this.logger.info('download asset added', { orgId, downloadId, assetId: input.assetId });\n return assets;\n }\n\n async removeDownloadAsset(\n orgId: string,\n downloadId: string,\n assetId: string,\n ): Promise<DownloadAsset[]> {\n const assets = await this.uow.run(async ({ content, outbox }) => {\n const assets = await content.removeDownloadAsset(orgId, downloadId, assetId);\n await outbox.append([\n contentEvents.downloadAssetRemoved.make({ orgId, data: { downloadId, assets } }),\n ]);\n return assets;\n });\n this.logger.info('download asset removed', { orgId, downloadId, assetId });\n return assets;\n }\n\n async renameDownloadAsset(\n orgId: string,\n downloadId: string,\n assetId: string,\n displayName: string | null,\n ): Promise<DownloadAsset[]> {\n const assets = await this.uow.run(async ({ content, outbox }) => {\n const assets = await content.renameDownloadAsset(orgId, downloadId, assetId, displayName);\n await outbox.append([\n contentEvents.downloadAssetRenamed.make({ orgId, data: { downloadId, assets } }),\n ]);\n return assets;\n });\n this.logger.info('download asset renamed', { orgId, downloadId, assetId });\n return assets;\n }\n\n /** The caller must send the COMPLETE ordered set — a partial list would\n * silently unlink whatever it omitted. */\n async reorderDownloadAssets(\n orgId: string,\n downloadId: string,\n assetIds: string[],\n ): Promise<DownloadAsset[]> {\n const assets = await this.uow.run(async ({ content, outbox }) => {\n const current = await content.listDownloadAssets(orgId, downloadId);\n const currentIds = new Set(current.map((a) => a.assetId));\n const requestedIds = new Set(assetIds);\n if (\n requestedIds.size !== assetIds.length ||\n requestedIds.size !== currentIds.size ||\n !assetIds.every((id) => currentIds.has(id))\n ) {\n this.logger.warn('download asset reorder rejected — id set mismatch', {\n orgId,\n downloadId,\n assetIds,\n currentIds: [...currentIds],\n });\n throw new ConflictError(\"Ordered asset ids does not match the download's current assets\");\n }\n const assets = await content.reorderDownloadAssets(orgId, downloadId, assetIds);\n await outbox.append([\n contentEvents.downloadAssetsReordered.make({ orgId, data: { downloadId, assets } }),\n ]);\n return assets;\n });\n this.logger.debug('download assets reordered', { orgId, downloadId });\n return assets;\n }\n\n // --- bundles --------------------------------------------------------------\n\n listBundles(orgId: string, query: ListBundlesQuery): Promise<Page<Bundle>> {\n return this.repo.listBundles(orgId, query);\n }\n\n getBundle(orgId: string, id: string): Promise<Bundle | null> {\n return this.repo.findBundleById(orgId, id);\n }\n\n async createBundle(orgId: string, input: CreateBundleInput): Promise<Bundle> {\n const { bundle, items } = await this.uow.run(async ({ content, outbox }) => {\n // The repository writes the bundle and its initial items as one unit —\n // an unknown content id throws and nothing is created.\n const created = await content.createBundle(orgId, input);\n const initial = input.contentIds?.length\n ? await content.listBundleItems(orgId, created.id)\n : [];\n await outbox.append([\n contentEvents.bundleCreated.make({ orgId, data: created }),\n ...(initial.length > 0\n ? [\n contentEvents.bundleItemAdded.make({\n orgId,\n data: { bundleId: created.id, items: initial },\n }),\n ]\n : []),\n ]);\n return { bundle: created, items: initial };\n });\n this.logger.info('bundle created', {\n orgId,\n bundleId: bundle.id,\n name: bundle.name,\n itemCount: items.length,\n });\n return bundle;\n }\n\n async updateBundle(orgId: string, id: string, patch: UpdateBundleInput): Promise<Bundle> {\n const bundle = await this.uow.run(async ({ content, outbox }) => {\n const updated = await content.updateBundle(orgId, id, patch);\n if (!updated) {\n this.logger.warn('bundle update rejected — not found', { orgId, bundleId: id });\n throw new NotFoundError('Bundle', id);\n }\n await outbox.append([contentEvents.bundleUpdated.make({ orgId, data: updated })]);\n return updated;\n });\n this.logger.info('bundle updated', { orgId, bundleId: id, fields: Object.keys(patch) });\n return bundle;\n }\n\n async deleteBundle(orgId: string, id: string): Promise<void> {\n await this.uow.run(async ({ content, outbox }) => {\n // Snapshot before the delete — the event carries the last known state.\n const bundle = await content.findBundleById(orgId, id);\n if (!bundle) {\n this.logger.warn('bundle delete rejected — not found', { orgId, bundleId: id });\n throw new NotFoundError('Bundle', id);\n }\n const ok = await content.deleteBundle(orgId, id);\n if (!ok) {\n throw new NotFoundError('Bundle', id);\n }\n await outbox.append([contentEvents.bundleDeleted.make({ orgId, data: bundle })]);\n });\n this.logger.info('bundle deleted', { orgId, bundleId: id });\n }\n\n listBundleItems(orgId: string, bundleId: string): Promise<BundleItem[]> {\n return this.repo.listBundleItems(orgId, bundleId);\n }\n\n async addBundleItem(orgId: string, bundleId: string, contentId: string): Promise<BundleItem[]> {\n const items = await this.uow.run(async ({ content, outbox }) => {\n const all = await content.addBundleItem(orgId, bundleId, contentId);\n await outbox.append([\n contentEvents.bundleItemAdded.make({ orgId, data: { bundleId, items: all } }),\n ]);\n return all;\n });\n this.logger.info('bundle item added', { orgId, bundleId, contentId });\n return items;\n }\n\n async removeBundleItem(\n orgId: string,\n bundleId: string,\n contentId: string,\n ): Promise<BundleItem[]> {\n const items = await this.uow.run(async ({ content, outbox }) => {\n const all = await content.removeBundleItem(orgId, bundleId, contentId);\n await outbox.append([\n contentEvents.bundleItemRemoved.make({ orgId, data: { bundleId, items: all } }),\n ]);\n return all;\n });\n this.logger.info('bundle item removed', { orgId, bundleId, contentId });\n return items;\n }\n}\n"],"mappings":"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"}
|
package/dist/content/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { Page } from "../types/shared.js";
|
|
2
|
-
import { AddDownloadAssetInput, CreateCourseInput, CreateDownloadInput, ListCoursesQuery, ListDownloadsQuery, ReorderDownloadAssetsInput, UpdateCourseInput, UpdateDownloadInput } from "../types/schemas/content.js";
|
|
2
|
+
import { AddDownloadAssetInput, CreateBundleInput, CreateCourseInput, CreateDownloadInput, ListBundlesQuery, ListCoursesQuery, ListDownloadsQuery, ReorderDownloadAssetsInput, UpdateBundleInput, UpdateCourseInput, UpdateDownloadInput } from "../types/schemas/content.js";
|
|
3
3
|
import "../types/index.js";
|
|
4
|
-
export type { AddDownloadAssetInput, CreateCourseInput, CreateDownloadInput, ListCoursesQuery, ListDownloadsQuery, Page, ReorderDownloadAssetsInput, UpdateCourseInput, UpdateDownloadInput };
|
|
4
|
+
export type { AddDownloadAssetInput, CreateBundleInput, CreateCourseInput, CreateDownloadInput, ListBundlesQuery, ListCoursesQuery, ListDownloadsQuery, Page, ReorderDownloadAssetsInput, UpdateBundleInput, UpdateCourseInput, UpdateDownloadInput };
|
|
@@ -25,7 +25,22 @@ declare const discussionEvents: {
|
|
|
25
25
|
removedBy: string | null;
|
|
26
26
|
createdAt: unknown;
|
|
27
27
|
updatedAt: unknown;
|
|
28
|
-
}
|
|
28
|
+
}, import("zod").ZodObject<{
|
|
29
|
+
orgId: import("zod").ZodString;
|
|
30
|
+
id: import("zod").ZodString;
|
|
31
|
+
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
32
|
+
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
33
|
+
status: import("zod").ZodEnum<{
|
|
34
|
+
pending: "pending";
|
|
35
|
+
published: "published";
|
|
36
|
+
removed: "removed";
|
|
37
|
+
}>;
|
|
38
|
+
activityId: import("zod").ZodString;
|
|
39
|
+
orgUserId: import("zod").ZodString;
|
|
40
|
+
parentId: import("zod").ZodNullable<import("zod").ZodString>;
|
|
41
|
+
body: import("zod").ZodString;
|
|
42
|
+
removedBy: import("zod").ZodNullable<import("zod").ZodString>;
|
|
43
|
+
}, import("zod/v4/core").$strip>>;
|
|
29
44
|
commentPublished: RuntimeEventDefinition<VersionedDomainEvent<{
|
|
30
45
|
id: string;
|
|
31
46
|
orgId: string;
|
|
@@ -48,7 +63,22 @@ declare const discussionEvents: {
|
|
|
48
63
|
removedBy: string | null;
|
|
49
64
|
createdAt: unknown;
|
|
50
65
|
updatedAt: unknown;
|
|
51
|
-
}
|
|
66
|
+
}, import("zod").ZodObject<{
|
|
67
|
+
orgId: import("zod").ZodString;
|
|
68
|
+
id: import("zod").ZodString;
|
|
69
|
+
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
70
|
+
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
71
|
+
status: import("zod").ZodEnum<{
|
|
72
|
+
pending: "pending";
|
|
73
|
+
published: "published";
|
|
74
|
+
removed: "removed";
|
|
75
|
+
}>;
|
|
76
|
+
activityId: import("zod").ZodString;
|
|
77
|
+
orgUserId: import("zod").ZodString;
|
|
78
|
+
parentId: import("zod").ZodNullable<import("zod").ZodString>;
|
|
79
|
+
body: import("zod").ZodString;
|
|
80
|
+
removedBy: import("zod").ZodNullable<import("zod").ZodString>;
|
|
81
|
+
}, import("zod/v4/core").$strip>>;
|
|
52
82
|
commentRemoved: RuntimeEventDefinition<VersionedDomainEvent<{
|
|
53
83
|
id: string;
|
|
54
84
|
orgId: string;
|
|
@@ -71,7 +101,22 @@ declare const discussionEvents: {
|
|
|
71
101
|
removedBy: string | null;
|
|
72
102
|
createdAt: unknown;
|
|
73
103
|
updatedAt: unknown;
|
|
74
|
-
}
|
|
104
|
+
}, import("zod").ZodObject<{
|
|
105
|
+
orgId: import("zod").ZodString;
|
|
106
|
+
id: import("zod").ZodString;
|
|
107
|
+
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
108
|
+
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
109
|
+
status: import("zod").ZodEnum<{
|
|
110
|
+
pending: "pending";
|
|
111
|
+
published: "published";
|
|
112
|
+
removed: "removed";
|
|
113
|
+
}>;
|
|
114
|
+
activityId: import("zod").ZodString;
|
|
115
|
+
orgUserId: import("zod").ZodString;
|
|
116
|
+
parentId: import("zod").ZodNullable<import("zod").ZodString>;
|
|
117
|
+
body: import("zod").ZodString;
|
|
118
|
+
removedBy: import("zod").ZodNullable<import("zod").ZodString>;
|
|
119
|
+
}, import("zod/v4/core").$strip>>;
|
|
75
120
|
commentReported: RuntimeEventDefinition<VersionedDomainEvent<{
|
|
76
121
|
id: string;
|
|
77
122
|
orgId: string;
|
|
@@ -80,6 +125,7 @@ declare const discussionEvents: {
|
|
|
80
125
|
reason: string;
|
|
81
126
|
resolvedAt: Date | null;
|
|
82
127
|
createdAt: Date;
|
|
128
|
+
updatedAt: Date;
|
|
83
129
|
}, "discussion.comment.reported", 1>, {
|
|
84
130
|
id: string;
|
|
85
131
|
orgId: string;
|
|
@@ -88,7 +134,17 @@ declare const discussionEvents: {
|
|
|
88
134
|
reason: string;
|
|
89
135
|
resolvedAt: unknown;
|
|
90
136
|
createdAt: unknown;
|
|
91
|
-
|
|
137
|
+
updatedAt: unknown;
|
|
138
|
+
}, import("zod").ZodObject<{
|
|
139
|
+
orgId: import("zod").ZodString;
|
|
140
|
+
id: import("zod").ZodString;
|
|
141
|
+
createdAt: import("zod").ZodCoercedDate<unknown>;
|
|
142
|
+
updatedAt: import("zod").ZodCoercedDate<unknown>;
|
|
143
|
+
orgUserId: import("zod").ZodString;
|
|
144
|
+
commentId: import("zod").ZodString;
|
|
145
|
+
reason: import("zod").ZodString;
|
|
146
|
+
resolvedAt: import("zod").ZodNullable<import("zod").ZodCoercedDate<unknown>>;
|
|
147
|
+
}, import("zod/v4/core").$strip>>;
|
|
92
148
|
};
|
|
93
149
|
type CommentCreated = EventOf<typeof discussionEvents.commentCreated>;
|
|
94
150
|
type CommentPublished = EventOf<typeof discussionEvents.commentPublished>;
|
|
@@ -1,26 +1,52 @@
|
|
|
1
1
|
import { commentReportSchema, commentSchema } from "../types/schemas/discussion.js";
|
|
2
2
|
import { defineEvent } from "../shared/events.js";
|
|
3
3
|
//#region src/discussion/events.ts
|
|
4
|
+
const publicComment = commentSchema.pick({
|
|
5
|
+
id: true,
|
|
6
|
+
orgId: true,
|
|
7
|
+
activityId: true,
|
|
8
|
+
parentId: true,
|
|
9
|
+
orgUserId: true,
|
|
10
|
+
body: true,
|
|
11
|
+
status: true,
|
|
12
|
+
removedBy: true,
|
|
13
|
+
createdAt: true,
|
|
14
|
+
updatedAt: true
|
|
15
|
+
}).strip();
|
|
16
|
+
const publicCommentReport = commentReportSchema.pick({
|
|
17
|
+
id: true,
|
|
18
|
+
orgId: true,
|
|
19
|
+
commentId: true,
|
|
20
|
+
orgUserId: true,
|
|
21
|
+
reason: true,
|
|
22
|
+
resolvedAt: true,
|
|
23
|
+
createdAt: true,
|
|
24
|
+
updatedAt: true
|
|
25
|
+
}).strip();
|
|
4
26
|
const discussionEvents = {
|
|
5
27
|
commentCreated: defineEvent({
|
|
6
28
|
type: "discussion.comment.created",
|
|
7
29
|
version: 1,
|
|
8
|
-
data: commentSchema
|
|
30
|
+
data: commentSchema,
|
|
31
|
+
publicData: publicComment
|
|
9
32
|
}),
|
|
10
33
|
commentPublished: defineEvent({
|
|
11
34
|
type: "discussion.comment.published",
|
|
12
35
|
version: 1,
|
|
13
|
-
data: commentSchema
|
|
36
|
+
data: commentSchema,
|
|
37
|
+
publicData: publicComment
|
|
14
38
|
}),
|
|
15
39
|
commentRemoved: defineEvent({
|
|
16
40
|
type: "discussion.comment.removed",
|
|
17
41
|
version: 1,
|
|
18
|
-
data: commentSchema
|
|
42
|
+
data: commentSchema,
|
|
43
|
+
publicData: publicComment
|
|
19
44
|
}),
|
|
20
45
|
commentReported: defineEvent({
|
|
21
46
|
type: "discussion.comment.reported",
|
|
22
47
|
version: 1,
|
|
23
|
-
data: commentReportSchema
|
|
48
|
+
data: commentReportSchema,
|
|
49
|
+
publicData: publicCommentReport
|
|
24
50
|
})
|
|
25
51
|
};
|
|
26
52
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","names":[],"sources":["../../src/discussion/events.ts"],"sourcesContent":["import { commentReportSchema, commentSchema } from '../types/schemas/index.js';\nimport {\n defineEvent,\n type EventOf,\n type EventOfValues,\n type NewDomainEvent,\n} from '../shared/ports.js';\n\nexport const discussionEvents = {\n commentCreated: defineEvent({\n type: 'discussion.comment.created',\n version: 1,\n data: commentSchema,\n }),\n commentPublished: defineEvent({\n type: 'discussion.comment.published',\n version: 1,\n data: commentSchema,\n }),\n commentRemoved: defineEvent({\n type: 'discussion.comment.removed',\n version: 1,\n data: commentSchema,\n }),\n commentReported: defineEvent({\n type: 'discussion.comment.reported',\n version: 1,\n data: commentReportSchema,\n }),\n};\n\nexport type CommentCreated = EventOf<typeof discussionEvents.commentCreated>;\nexport type CommentPublished = EventOf<typeof discussionEvents.commentPublished>;\nexport type CommentRemoved = EventOf<typeof discussionEvents.commentRemoved>;\nexport type CommentReported = EventOf<typeof discussionEvents.commentReported>;\nexport type CommentEvent = EventOfValues<typeof discussionEvents>;\nexport type NewDiscussionEvent = NewDomainEvent<CommentEvent>;\n"],"mappings":";;;AAQA,MAAa,mBAAmB;CAC9B,gBAAgB,YAAY;EAC1B,MAAM;EACN,SAAS;EACT,MAAM;
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../../src/discussion/events.ts"],"sourcesContent":["import { commentReportSchema, commentSchema } from '../types/schemas/index.js';\nimport {\n defineEvent,\n type EventOf,\n type EventOfValues,\n type NewDomainEvent,\n} from '../shared/ports.js';\n\nconst publicComment = commentSchema\n .pick({\n id: true,\n orgId: true,\n activityId: true,\n parentId: true,\n orgUserId: true,\n body: true,\n status: true,\n removedBy: true,\n createdAt: true,\n updatedAt: true,\n })\n .strip();\n\nconst publicCommentReport = commentReportSchema\n .pick({\n id: true,\n orgId: true,\n commentId: true,\n orgUserId: true,\n reason: true,\n resolvedAt: true,\n createdAt: true,\n updatedAt: true,\n })\n .strip();\n\nexport const discussionEvents = {\n commentCreated: defineEvent({\n type: 'discussion.comment.created',\n version: 1,\n data: commentSchema,\n publicData: publicComment,\n }),\n commentPublished: defineEvent({\n type: 'discussion.comment.published',\n version: 1,\n data: commentSchema,\n publicData: publicComment,\n }),\n commentRemoved: defineEvent({\n type: 'discussion.comment.removed',\n version: 1,\n data: commentSchema,\n publicData: publicComment,\n }),\n commentReported: defineEvent({\n type: 'discussion.comment.reported',\n version: 1,\n data: commentReportSchema,\n publicData: publicCommentReport,\n }),\n};\n\nexport type CommentCreated = EventOf<typeof discussionEvents.commentCreated>;\nexport type CommentPublished = EventOf<typeof discussionEvents.commentPublished>;\nexport type CommentRemoved = EventOf<typeof discussionEvents.commentRemoved>;\nexport type CommentReported = EventOf<typeof discussionEvents.commentReported>;\nexport type CommentEvent = EventOfValues<typeof discussionEvents>;\nexport type NewDiscussionEvent = NewDomainEvent<CommentEvent>;\n"],"mappings":";;;AAQA,MAAM,gBAAgB,cACnB,KAAK;CACJ,IAAI;CACJ,OAAO;CACP,YAAY;CACZ,UAAU;CACV,WAAW;CACX,MAAM;CACN,QAAQ;CACR,WAAW;CACX,WAAW;CACX,WAAW;AACb,CAAC,CAAC,CACD,MAAM;AAET,MAAM,sBAAsB,oBACzB,KAAK;CACJ,IAAI;CACJ,OAAO;CACP,WAAW;CACX,WAAW;CACX,QAAQ;CACR,YAAY;CACZ,WAAW;CACX,WAAW;AACb,CAAC,CAAC,CACD,MAAM;AAET,MAAa,mBAAmB;CAC9B,gBAAgB,YAAY;EAC1B,MAAM;EACN,SAAS;EACT,MAAM;EACN,YAAY;CACd,CAAC;CACD,kBAAkB,YAAY;EAC5B,MAAM;EACN,SAAS;EACT,MAAM;EACN,YAAY;CACd,CAAC;CACD,gBAAgB,YAAY;EAC1B,MAAM;EACN,SAAS;EACT,MAAM;EACN,YAAY;CACd,CAAC;CACD,iBAAiB,YAAY;EAC3B,MAAM;EACN,SAAS;EACT,MAAM;EACN,YAAY;CACd,CAAC;AACH"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ActivityCommentsRule } from "../types/schemas/
|
|
2
|
-
import { CommentReaction, CommentSettings, CommentStatus } from "../types/schemas/discussion.js";
|
|
1
|
+
import { ActivityCommentsRule, CommentReaction, CommentSettings, CommentStatus } from "../types/schemas/discussion.js";
|
|
3
2
|
import { Comment, CommentAuthor, CommentReport } from "../types/discussion.js";
|
|
4
3
|
import "../types/index.js";
|
|
5
4
|
export type { ActivityCommentsRule, Comment, CommentAuthor, CommentReaction, CommentReport, CommentSettings, CommentStatus };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Page } from "../types/shared.js";
|
|
2
|
-
import { Role } from "../types/schemas/organizations.js";
|
|
3
2
|
import { Activity } from "../types/schemas/content.js";
|
|
3
|
+
import { Role } from "../types/schemas/organizations.js";
|
|
4
4
|
import { CommentListItem, CommentView, CommentsConfig, ListCommentsQuery, ReactionCounts, ReactionEmoji } from "../types/schemas/discussion.js";
|
|
5
5
|
import { Comment, CommentAuthor, CommentReport } from "../types/discussion.js";
|
|
6
6
|
import "../types/index.js";
|
|
@@ -3,9 +3,9 @@ import { Logger } from "../types/ports.js";
|
|
|
3
3
|
import { CommentListItem, CommentView, CommentsConfig, ListCommentsQuery, ReactionEmoji } from "../types/schemas/discussion.js";
|
|
4
4
|
import { Comment, CommentReport } from "../types/discussion.js";
|
|
5
5
|
import "../shared/ports.js";
|
|
6
|
-
import { SettingsService } from "../shared/settings.js";
|
|
7
6
|
import "./model.js";
|
|
8
7
|
import "./types.js";
|
|
8
|
+
import { SettingsService } from "../shared/settings.js";
|
|
9
9
|
import { ActivityComments, ActivityGetter, Actor, CommentReactions, CourseAccessReader, DiscussionRepository, DiscussionService, DiscussionUnitOfWork, PostCommentInput } from "./ports.js";
|
|
10
10
|
//#region src/discussion/service.d.ts
|
|
11
11
|
/** A course with no stored settings. Discussion is opt-in, so the common case
|