@headless-lms/adapter-db 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/index.d.ts +12 -6
  2. package/dist/index.js +12 -6
  3. package/dist/repositories/admin.d.ts +23 -0
  4. package/dist/repositories/admin.js +393 -0
  5. package/dist/repositories/admin.js.map +1 -0
  6. package/dist/repositories/assets.d.ts +6 -3
  7. package/dist/repositories/assets.js +46 -9
  8. package/dist/repositories/assets.js.map +1 -1
  9. package/dist/repositories/automations.d.ts +5 -2
  10. package/dist/repositories/automations.js +29 -2
  11. package/dist/repositories/automations.js.map +1 -1
  12. package/dist/repositories/chats.d.ts +55 -0
  13. package/dist/repositories/chats.js +469 -0
  14. package/dist/repositories/chats.js.map +1 -0
  15. package/dist/repositories/content.d.ts +24 -1
  16. package/dist/repositories/content.js +316 -9
  17. package/dist/repositories/content.js.map +1 -1
  18. package/dist/repositories/devices.d.ts +17 -0
  19. package/dist/repositories/devices.js +75 -0
  20. package/dist/repositories/devices.js.map +1 -0
  21. package/dist/repositories/digest-runs.d.ts +14 -0
  22. package/dist/repositories/digest-runs.js +39 -0
  23. package/dist/repositories/digest-runs.js.map +1 -0
  24. package/dist/repositories/discussion.d.ts +16 -4
  25. package/dist/repositories/discussion.js +78 -18
  26. package/dist/repositories/discussion.js.map +1 -1
  27. package/dist/repositories/entitlements.d.ts +31 -9
  28. package/dist/repositories/entitlements.js +213 -17
  29. package/dist/repositories/entitlements.js.map +1 -1
  30. package/dist/repositories/grant-covers.d.ts +20 -0
  31. package/dist/repositories/grant-covers.js +55 -0
  32. package/dist/repositories/grant-covers.js.map +1 -0
  33. package/dist/repositories/identity.d.ts +4 -2
  34. package/dist/repositories/identity.js +32 -1
  35. package/dist/repositories/identity.js.map +1 -1
  36. package/dist/repositories/inserted-row.d.ts +5 -0
  37. package/dist/repositories/inserted-row.js +7 -0
  38. package/dist/repositories/inserted-row.js.map +1 -0
  39. package/dist/repositories/member.d.ts +16 -0
  40. package/dist/repositories/member.js +63 -0
  41. package/dist/repositories/member.js.map +1 -0
  42. package/dist/repositories/members.js +2 -2
  43. package/dist/repositories/members.js.map +1 -1
  44. package/dist/repositories/mentions.d.ts +17 -0
  45. package/dist/repositories/mentions.js +55 -0
  46. package/dist/repositories/mentions.js.map +1 -0
  47. package/dist/repositories/notification-settings.d.ts +19 -0
  48. package/dist/repositories/notification-settings.js +67 -0
  49. package/dist/repositories/notification-settings.js.map +1 -0
  50. package/dist/repositories/notifications.d.ts +30 -0
  51. package/dist/repositories/notifications.js +178 -0
  52. package/dist/repositories/notifications.js.map +1 -0
  53. package/dist/repositories/org-user-profile.d.ts +214 -1
  54. package/dist/repositories/org-user-profile.js +23 -2
  55. package/dist/repositories/org-user-profile.js.map +1 -1
  56. package/dist/repositories/organizations.d.ts +4 -2
  57. package/dist/repositories/organizations.js +23 -8
  58. package/dist/repositories/organizations.js.map +1 -1
  59. package/dist/repositories/pg-errors.js +3 -1
  60. package/dist/repositories/pg-errors.js.map +1 -1
  61. package/dist/repositories/posts.d.ts +35 -0
  62. package/dist/repositories/posts.js +252 -0
  63. package/dist/repositories/posts.js.map +1 -0
  64. package/dist/repositories/subscriptions.d.ts +18 -0
  65. package/dist/repositories/subscriptions.js +72 -0
  66. package/dist/repositories/subscriptions.js.map +1 -0
  67. package/dist/schema/assets.d.ts +19 -2
  68. package/dist/schema/assets.js +10 -4
  69. package/dist/schema/assets.js.map +1 -1
  70. package/dist/schema/automations.d.ts +545 -2
  71. package/dist/schema/automations.js +73 -3
  72. package/dist/schema/automations.js.map +1 -1
  73. package/dist/schema/better-auth.d.ts +1613 -238
  74. package/dist/schema/better-auth.js +107 -22
  75. package/dist/schema/better-auth.js.map +1 -1
  76. package/dist/schema/content.d.ts +642 -5
  77. package/dist/schema/content.js +119 -10
  78. package/dist/schema/content.js.map +1 -1
  79. package/dist/schema/discussion.d.ts +2182 -42
  80. package/dist/schema/discussion.js +461 -11
  81. package/dist/schema/discussion.js.map +1 -1
  82. package/dist/schema/entitlements.d.ts +432 -1
  83. package/dist/schema/entitlements.js +109 -4
  84. package/dist/schema/entitlements.js.map +1 -1
  85. package/dist/schema/identity.d.ts +517 -1
  86. package/dist/schema/identity.js +76 -2
  87. package/dist/schema/identity.js.map +1 -1
  88. package/dist/schema/index.d.ts +7 -7
  89. package/dist/schema/index.js +35 -8
  90. package/dist/schema/organizations.d.ts +4 -4
  91. package/dist/schema/organizations.js +2 -0
  92. package/dist/schema/organizations.js.map +1 -1
  93. package/drizzle/0003_community.sql +340 -0
  94. package/drizzle/0004_assets_sources.sql +1 -0
  95. package/drizzle/0005_notices_and_seed_index.sql +12 -0
  96. package/drizzle/0006_community_review_fixes.sql +24 -0
  97. package/drizzle/0007_notifications.sql +37 -0
  98. package/drizzle/0008_content_access.sql +13 -0
  99. package/drizzle/0009_community_indexes.sql +56 -0
  100. package/drizzle/0010_mcp_access.sql +114 -0
  101. package/drizzle/0011_digest_runs.sql +8 -0
  102. package/drizzle/0012_subscriptions.sql +16 -0
  103. package/drizzle/0013_mentions.sql +10 -0
  104. package/drizzle/0014_notification_settings_devices.sql +36 -0
  105. package/drizzle/0015_automations_drop_source.sql +2 -0
  106. package/drizzle/0016_notifications_automations.sql +48 -0
  107. package/drizzle/meta/0003_snapshot.json +7061 -0
  108. package/drizzle/meta/0004_snapshot.json +7068 -0
  109. package/drizzle/meta/0005_snapshot.json +7180 -0
  110. package/drizzle/meta/0006_snapshot.json +7298 -0
  111. package/drizzle/meta/0007_snapshot.json +7620 -0
  112. package/drizzle/meta/0008_snapshot.json +7666 -0
  113. package/drizzle/meta/0009_snapshot.json +8185 -0
  114. package/drizzle/meta/0010_snapshot.json +8813 -0
  115. package/drizzle/meta/0011_snapshot.json +8868 -0
  116. package/drizzle/meta/0012_snapshot.json +8873 -0
  117. package/drizzle/meta/0013_snapshot.json +8943 -0
  118. package/drizzle/meta/0014_snapshot.json +9305 -0
  119. package/drizzle/meta/0015_snapshot.json +9270 -0
  120. package/drizzle/meta/0016_snapshot.json +9230 -0
  121. package/drizzle/meta/_journal.json +99 -1
  122. package/package.json +2 -2
  123. package/dist/repositories/course-analytics.d.ts +0 -15
  124. package/dist/repositories/course-analytics.js +0 -137
  125. package/dist/repositories/course-analytics.js.map +0 -1
  126. package/dist/repositories/dashboard.d.ts +0 -14
  127. package/dist/repositories/dashboard.js +0 -57
  128. package/dist/repositories/dashboard.js.map +0 -1
  129. package/dist/repositories/learn.d.ts +0 -19
  130. package/dist/repositories/learn.js +0 -56
  131. package/dist/repositories/learn.js.map +0 -1
  132. package/dist/repositories/students.d.ts +0 -16
  133. package/dist/repositories/students.js +0 -160
  134. package/dist/repositories/students.js.map +0 -1
@@ -1,15 +1,62 @@
1
- import { activities, activityAssets, bundleItems, bundles, contentItems, courses, downloadAssets, downloads, modules } from "../schema/content.js";
1
+ import { activities, activityAssets, bundleItems, bundles, communities, contentItems, courses, downloadAssets, downloads, modules, spaceLinks, spaceTopics, spaces } from "../schema/content.js";
2
2
  import { settings } from "../schema/settings.js";
3
- import { isUniqueViolation, translateDbErrors } from "./pg-errors.js";
3
+ import { posts } from "../schema/discussion.js";
4
+ import { isUniqueViolation, pgErrorFields, translateDbErrors } from "./pg-errors.js";
4
5
  import { deepMerge } from "./settings.js";
5
6
  import { and, asc, count, desc, eq, ilike, inArray, or, sql } from "drizzle-orm";
6
7
  import { genId } from "@headless-lms/core/shared/id";
7
8
  import { ConflictError, NotFoundError } from "@headless-lms/core/shared/errors";
8
9
  import { noopLogger } from "@headless-lms/core/shared/logger";
9
- import { courseSettingsDefaults } from "@headless-lms/core/schemas";
10
+ import { communitySettingsDefaults, courseSettingsDefaults, digestScheduleSchema, spaceSettingsDefaults } from "@headless-lms/core/schemas";
10
11
  //#region src/repositories/content.ts
12
+ const LIBRARY_ONLY = "An activity can only use ready files from the media library";
13
+ const MISSING_ASSET_DETAIL = /^Key \(org_id, asset_id\)=\((?:[^,]+), (.+?)\) is not present/;
14
+ function refusedLink(err) {
15
+ const pg = pgErrorFields(err);
16
+ if (pg?.code !== "23503") return;
17
+ const missing = pg.detail?.match(MISSING_ASSET_DETAIL)?.[1];
18
+ const message = missing ? `${LIBRARY_ONLY}. Not available: ${missing}` : LIBRARY_ONLY;
19
+ return Object.assign(new ConflictError(message), { cause: err });
20
+ }
11
21
  const SETTINGS_NAMESPACE = "content";
12
22
  const courseSettingsJoin = and(eq(settings.orgId, courses.orgId), eq(settings.namespace, SETTINGS_NAMESPACE), eq(settings.scopeId, courses.id));
23
+ const communitySettingsJoin = and(eq(settings.orgId, communities.orgId), eq(settings.namespace, SETTINGS_NAMESPACE), eq(settings.scopeId, communities.id));
24
+ function storedDigest(value) {
25
+ const parsed = digestScheduleSchema.safeParse(value);
26
+ return parsed.success ? parsed.data : null;
27
+ }
28
+ function toCommunity(row, storedSettings) {
29
+ const raw = storedSettings?.dm;
30
+ const stored = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
31
+ return {
32
+ ...row,
33
+ status: row.status,
34
+ settings: {
35
+ dm: {
36
+ ...communitySettingsDefaults.dm,
37
+ ...stored
38
+ },
39
+ digest: storedDigest(storedSettings?.digest)
40
+ }
41
+ };
42
+ }
43
+ function toSpace(row) {
44
+ return {
45
+ ...row,
46
+ accessRule: row.accessRule,
47
+ settings: {
48
+ ...spaceSettingsDefaults,
49
+ ...row.settings
50
+ }
51
+ };
52
+ }
53
+ const communitySortColumns = {
54
+ title: communities.title,
55
+ slug: communities.slug,
56
+ status: communities.status,
57
+ createdAt: communities.createdAt,
58
+ updatedAt: communities.updatedAt
59
+ };
13
60
  function toCourse(row, storedSettings) {
14
61
  return {
15
62
  ...row,
@@ -199,12 +246,16 @@ var DrizzleContentRepository = class {
199
246
  /** Replace an activity's ordered asset links with `assetIds`. */
200
247
  async replaceActivityAssets(tx, orgId, activityId, assetIds) {
201
248
  await tx.delete(activityAssets).where(and(eq(activityAssets.orgId, orgId), eq(activityAssets.activityId, activityId)));
202
- if (assetIds.length) await tx.insert(activityAssets).values(assetIds.map((assetId, i) => ({
203
- orgId,
204
- activityId,
205
- assetId,
206
- seq: i
207
- })));
249
+ if (assetIds.length) try {
250
+ await tx.insert(activityAssets).values(assetIds.map((assetId, i) => ({
251
+ orgId,
252
+ activityId,
253
+ assetId,
254
+ seq: i
255
+ })));
256
+ } catch (err) {
257
+ throw refusedLink(err) ?? err;
258
+ }
208
259
  }
209
260
  createModule(orgId, courseId, title) {
210
261
  return this.tx(async (tx) => {
@@ -492,6 +543,262 @@ var DrizzleContentRepository = class {
492
543
  await this.db.delete(bundleItems).where(and(eq(bundleItems.orgId, orgId), eq(bundleItems.bundleId, bundleId), eq(bundleItems.contentId, contentId)));
493
544
  return this.listBundleItems(orgId, bundleId);
494
545
  }
546
+ async listCommunities(orgId, query) {
547
+ const conditions = [eq(communities.orgId, orgId)];
548
+ if (query.status) conditions.push(eq(communities.status, query.status));
549
+ const search = query.search?.trim();
550
+ if (search) {
551
+ const like = `%${search}%`;
552
+ const match = or(ilike(communities.title, like), ilike(communities.description, like));
553
+ if (match) conditions.push(match);
554
+ }
555
+ const where = and(...conditions);
556
+ let sortKey = "createdAt";
557
+ let direction = "desc";
558
+ if (query.sort) {
559
+ const isDesc = query.sort.startsWith("-");
560
+ const key = isDesc ? query.sort.slice(1) : query.sort;
561
+ if (key in communitySortColumns) {
562
+ sortKey = key;
563
+ direction = isDesc ? "desc" : "asc";
564
+ }
565
+ }
566
+ const sortExpr = communitySortColumns[sortKey] ?? communities.createdAt;
567
+ const orderBy = direction === "desc" ? desc(sortExpr) : asc(sortExpr);
568
+ const rows = await this.db.select({
569
+ community: communities,
570
+ settingsValue: settings.value
571
+ }).from(communities).leftJoin(settings, communitySettingsJoin).where(where).orderBy(orderBy).limit(query.pageSize).offset((query.page - 1) * query.pageSize);
572
+ const [totalRow] = await this.db.select({ value: count() }).from(communities).where(where);
573
+ return {
574
+ rows: rows.map((row) => toCommunity(row.community, row.settingsValue)),
575
+ total: totalRow?.value ?? 0,
576
+ page: query.page,
577
+ pageSize: query.pageSize
578
+ };
579
+ }
580
+ async findCommunityById(orgId, id) {
581
+ const [row] = await this.db.select({
582
+ community: communities,
583
+ settingsValue: settings.value
584
+ }).from(communities).leftJoin(settings, communitySettingsJoin).where(and(eq(communities.orgId, orgId), eq(communities.id, id))).limit(1);
585
+ return row ? toCommunity(row.community, row.settingsValue) : null;
586
+ }
587
+ async findCommunitiesByIds(orgId, ids) {
588
+ return (await this.db.select({
589
+ community: communities,
590
+ settingsValue: settings.value
591
+ }).from(communities).leftJoin(settings, communitySettingsJoin).where(and(eq(communities.orgId, orgId), inArray(communities.id, ids))).orderBy(asc(communities.title), asc(communities.id))).map((row) => toCommunity(row.community, row.settingsValue));
592
+ }
593
+ async createCommunity(orgId, input, slug) {
594
+ const id = genId("community");
595
+ await this.db.insert(contentItems).values({
596
+ orgId,
597
+ id,
598
+ type: "community"
599
+ });
600
+ const [inserted] = await this.db.insert(communities).values({
601
+ orgId,
602
+ id,
603
+ title: input.title,
604
+ slug,
605
+ description: input.description ?? ""
606
+ }).returning({ id: communities.id });
607
+ if (!inserted) throw new Error("failed to insert community");
608
+ const created = await this.findCommunityById(orgId, inserted.id);
609
+ if (!created) throw new Error("failed to load created community");
610
+ return created;
611
+ }
612
+ async updateCommunity(orgId, id, patch) {
613
+ const set = { updatedAt: /* @__PURE__ */ new Date() };
614
+ if (patch.title !== void 0) set.title = patch.title;
615
+ if (patch.description !== void 0) set.description = patch.description;
616
+ if (patch.status !== void 0) set.status = patch.status;
617
+ if (patch.thumbnailAssetId !== void 0) set.thumbnailAssetId = patch.thumbnailAssetId;
618
+ const [updated] = await this.db.update(communities).set(set).where(and(eq(communities.orgId, orgId), eq(communities.id, id))).returning({ id: communities.id });
619
+ if (!updated) return null;
620
+ return this.findCommunityById(orgId, id);
621
+ }
622
+ async communitiesWithDigest() {
623
+ return (await this.db.select({
624
+ orgId: communities.orgId,
625
+ communityId: communities.id,
626
+ digest: sql`${settings.value} -> 'digest'`
627
+ }).from(communities).innerJoin(settings, communitySettingsJoin).where(and(eq(communities.status, "published"), sql`jsonb_typeof(${settings.value} -> 'digest') = 'object'`)).orderBy(asc(communities.orgId), asc(communities.id))).flatMap((row) => {
628
+ const digest = storedDigest(row.digest);
629
+ return digest ? [{
630
+ orgId: row.orgId,
631
+ communityId: row.communityId,
632
+ digest
633
+ }] : [];
634
+ });
635
+ }
636
+ async patchCommunitySettings(orgId, id, input) {
637
+ if (!await this.findCommunityById(orgId, id)) return null;
638
+ const { digest, ...dm } = input;
639
+ const patch = digest === void 0 ? { dm } : {
640
+ dm,
641
+ digest
642
+ };
643
+ const key = and(eq(settings.orgId, orgId), eq(settings.namespace, SETTINGS_NAMESPACE), eq(settings.scopeId, id));
644
+ await this.tx(async (tx) => {
645
+ let [existing] = await tx.select().from(settings).where(key).for("update");
646
+ const value = patch;
647
+ if (!existing) {
648
+ const [inserted] = await tx.insert(settings).values({
649
+ orgId,
650
+ namespace: SETTINGS_NAMESPACE,
651
+ scopeId: id,
652
+ value
653
+ }).onConflictDoNothing().returning();
654
+ if (inserted) return;
655
+ [existing] = await tx.select().from(settings).where(key).for("update");
656
+ if (!existing) throw new Error("community settings patch: row vanished mid-transaction");
657
+ }
658
+ await tx.update(settings).set({
659
+ value: deepMerge(existing.value, patch),
660
+ updatedAt: /* @__PURE__ */ new Date()
661
+ }).where(key);
662
+ });
663
+ return this.findCommunityById(orgId, id);
664
+ }
665
+ async deleteCommunity(orgId, id) {
666
+ return (await this.db.delete(contentItems).where(and(eq(contentItems.orgId, orgId), eq(contentItems.id, id), eq(contentItems.type, "community"))).returning({ id: contentItems.id })).length > 0;
667
+ }
668
+ async deleteCommunitySettings(orgId, id) {
669
+ await this.db.delete(settings).where(and(eq(settings.orgId, orgId), eq(settings.namespace, SETTINGS_NAMESPACE), eq(settings.scopeId, id)));
670
+ }
671
+ async listSpaces(orgId, communityId) {
672
+ const rows = await this.db.select().from(spaces).where(and(eq(spaces.orgId, orgId), eq(spaces.communityId, communityId))).orderBy(asc(spaces.seq));
673
+ return this.withSpaceParts(orgId, rows.map(toSpace));
674
+ }
675
+ async findSpace(orgId, spaceId) {
676
+ const [row] = await this.db.select().from(spaces).where(and(eq(spaces.orgId, orgId), eq(spaces.id, spaceId))).limit(1);
677
+ if (!row) return null;
678
+ const [detail] = await this.withSpaceParts(orgId, [toSpace(row)]);
679
+ return detail ?? null;
680
+ }
681
+ async lockSpace(orgId, spaceId) {
682
+ const [row] = await this.db.select().from(spaces).where(and(eq(spaces.orgId, orgId), eq(spaces.id, spaceId))).for("update").limit(1);
683
+ return row ? toSpace(row) : null;
684
+ }
685
+ async createSpace(orgId, communityId, input) {
686
+ const nextSeq = (await this.db.select({ seq: spaces.seq }).from(spaces).where(and(eq(spaces.orgId, orgId), eq(spaces.communityId, communityId)))).reduce((max, row) => Math.max(max, row.seq), -1) + 1;
687
+ const [inserted] = await this.db.insert(spaces).values({
688
+ orgId,
689
+ communityId,
690
+ name: input.name,
691
+ description: input.description ?? "",
692
+ seq: nextSeq,
693
+ accessRule: input.accessRule ?? "all_members",
694
+ capacity: input.capacity ?? null,
695
+ settings: {
696
+ ...spaceSettingsDefaults,
697
+ ...input.settings
698
+ }
699
+ }).returning({ id: spaces.id });
700
+ if (!inserted) throw new Error("failed to insert space");
701
+ const created = await this.findSpace(orgId, inserted.id);
702
+ if (!created) throw new Error("failed to load created space");
703
+ return created;
704
+ }
705
+ async updateSpace(orgId, communityId, spaceId, patch) {
706
+ const set = { updatedAt: /* @__PURE__ */ new Date() };
707
+ if (patch.name !== void 0) set.name = patch.name;
708
+ if (patch.description !== void 0) set.description = patch.description;
709
+ if (patch.accessRule !== void 0) set.accessRule = patch.accessRule;
710
+ if (patch.capacity !== void 0) set.capacity = patch.capacity;
711
+ if (patch.settings !== void 0) set.settings = {
712
+ ...spaceSettingsDefaults,
713
+ ...patch.settings
714
+ };
715
+ const [updated] = await this.db.update(spaces).set(set).where(and(eq(spaces.orgId, orgId), eq(spaces.id, spaceId), eq(spaces.communityId, communityId))).returning({ id: spaces.id });
716
+ if (!updated) return null;
717
+ return this.findSpace(orgId, spaceId);
718
+ }
719
+ async deleteSpace(orgId, communityId, spaceId) {
720
+ return (await this.db.delete(spaces).where(and(eq(spaces.orgId, orgId), eq(spaces.id, spaceId), eq(spaces.communityId, communityId))).returning({ id: spaces.id })).length > 0;
721
+ }
722
+ async reorderSpaces(orgId, communityId, orderedIds) {
723
+ for (let i = 0; i < orderedIds.length; i++) await this.db.update(spaces).set({ seq: i }).where(and(eq(spaces.orgId, orgId), eq(spaces.communityId, communityId), eq(spaces.id, orderedIds[i])));
724
+ return this.listSpaces(orgId, communityId);
725
+ }
726
+ async setSpaceTopics(orgId, spaceId, input) {
727
+ await this.tx(async (tx) => {
728
+ const existing = await tx.select({ id: spaceTopics.id }).from(spaceTopics).where(and(eq(spaceTopics.orgId, orgId), eq(spaceTopics.spaceId, spaceId))).for("update");
729
+ const kept = new Set(input.topics.flatMap((topic) => topic.id ? [topic.id] : []));
730
+ const known = new Set(existing.map((topic) => topic.id));
731
+ for (const id of kept) if (!known.has(id)) throw new NotFoundError("Topic", id);
732
+ const removed = existing.filter((topic) => !kept.has(topic.id)).map((topic) => topic.id);
733
+ if (removed.length) await tx.delete(spaceTopics).where(and(eq(spaceTopics.orgId, orgId), inArray(spaceTopics.id, removed)));
734
+ for (let seq = 0; seq < input.topics.length; seq++) {
735
+ const topic = input.topics[seq];
736
+ if (topic.id) await tx.update(spaceTopics).set({
737
+ name: topic.name,
738
+ seq,
739
+ updatedAt: /* @__PURE__ */ new Date()
740
+ }).where(and(eq(spaceTopics.orgId, orgId), eq(spaceTopics.id, topic.id)));
741
+ else await tx.insert(spaceTopics).values({
742
+ orgId,
743
+ spaceId,
744
+ name: topic.name,
745
+ seq
746
+ });
747
+ }
748
+ });
749
+ const space = await this.findSpace(orgId, spaceId);
750
+ if (!space) throw new NotFoundError("Space", spaceId);
751
+ return space;
752
+ }
753
+ async topicsInUse(orgId, spaceId, topicIds) {
754
+ if (topicIds.length === 0) return [];
755
+ await this.db.select({ id: spaceTopics.id }).from(spaceTopics).where(and(eq(spaceTopics.orgId, orgId), eq(spaceTopics.spaceId, spaceId), inArray(spaceTopics.id, topicIds))).for("update");
756
+ return (await this.db.selectDistinct({ topicId: posts.topicId }).from(posts).where(and(eq(posts.orgId, orgId), eq(posts.spaceId, spaceId), inArray(posts.topicId, topicIds)))).flatMap((row) => row.topicId ? [row.topicId] : []);
757
+ }
758
+ async addSpaceLink(orgId, spaceId, contentId) {
759
+ const [item] = await this.db.select({
760
+ id: contentItems.id,
761
+ type: contentItems.type
762
+ }).from(contentItems).where(and(eq(contentItems.orgId, orgId), eq(contentItems.id, contentId))).limit(1);
763
+ if (!item || item.type !== "course" && item.type !== "download" && item.type !== "community") throw new NotFoundError("Content", contentId);
764
+ const nextSeq = (await this.db.select({ seq: spaceLinks.seq }).from(spaceLinks).where(and(eq(spaceLinks.orgId, orgId), eq(spaceLinks.spaceId, spaceId)))).reduce((max, row) => Math.max(max, row.seq), -1) + 1;
765
+ await this.db.insert(spaceLinks).values({
766
+ orgId,
767
+ spaceId,
768
+ contentId,
769
+ seq: nextSeq
770
+ });
771
+ const space = await this.findSpace(orgId, spaceId);
772
+ if (!space) throw new NotFoundError("Space", spaceId);
773
+ return space;
774
+ }
775
+ async removeSpaceLink(orgId, spaceId, linkId) {
776
+ if ((await this.db.delete(spaceLinks).where(and(eq(spaceLinks.orgId, orgId), eq(spaceLinks.spaceId, spaceId), eq(spaceLinks.id, linkId))).returning({ id: spaceLinks.id })).length === 0) throw new NotFoundError("Link", linkId);
777
+ const space = await this.findSpace(orgId, spaceId);
778
+ if (!space) throw new NotFoundError("Space", spaceId);
779
+ return space;
780
+ }
781
+ async reorderSpaceLinks(orgId, spaceId, orderedIds) {
782
+ for (let i = 0; i < orderedIds.length; i++) await this.db.update(spaceLinks).set({ seq: i }).where(and(eq(spaceLinks.orgId, orgId), eq(spaceLinks.spaceId, spaceId), eq(spaceLinks.id, orderedIds[i])));
783
+ const space = await this.findSpace(orgId, spaceId);
784
+ if (!space) throw new NotFoundError("Space", spaceId);
785
+ return space;
786
+ }
787
+ async spacesLinkingContent(orgId, contentId) {
788
+ const rows = await this.db.select({ spaceId: spaceLinks.spaceId }).from(spaceLinks).where(and(eq(spaceLinks.orgId, orgId), eq(spaceLinks.contentId, contentId)));
789
+ return [...new Set(rows.map((row) => row.spaceId))];
790
+ }
791
+ async withSpaceParts(orgId, rows) {
792
+ if (rows.length === 0) return [];
793
+ const ids = rows.map((row) => row.id);
794
+ const topics = await this.db.select().from(spaceTopics).where(and(eq(spaceTopics.orgId, orgId), inArray(spaceTopics.spaceId, ids))).orderBy(asc(spaceTopics.seq));
795
+ const links = await this.db.select().from(spaceLinks).where(and(eq(spaceLinks.orgId, orgId), inArray(spaceLinks.spaceId, ids))).orderBy(asc(spaceLinks.seq));
796
+ return rows.map((space) => ({
797
+ ...space,
798
+ topics: topics.filter((topic) => topic.spaceId === space.id),
799
+ links: links.filter((link) => link.spaceId === space.id)
800
+ }));
801
+ }
495
802
  };
496
803
  translateDbErrors(DrizzleContentRepository);
497
804
  //#endregion