@headless-lms/adapter-db 0.2.0 → 0.3.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 (104) hide show
  1. package/dist/index.d.ts +7 -6
  2. package/dist/index.js +7 -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 -2
  7. package/dist/repositories/assets.js +50 -9
  8. package/dist/repositories/assets.js.map +1 -1
  9. package/dist/repositories/automations.d.ts +6 -2
  10. package/dist/repositories/automations.js +45 -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 +454 -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 +293 -9
  17. package/dist/repositories/content.js.map +1 -1
  18. package/dist/repositories/discussion.d.ts +10 -3
  19. package/dist/repositories/discussion.js +70 -15
  20. package/dist/repositories/discussion.js.map +1 -1
  21. package/dist/repositories/entitlements.d.ts +29 -9
  22. package/dist/repositories/entitlements.js +207 -17
  23. package/dist/repositories/entitlements.js.map +1 -1
  24. package/dist/repositories/grant-covers.d.ts +20 -0
  25. package/dist/repositories/grant-covers.js +55 -0
  26. package/dist/repositories/grant-covers.js.map +1 -0
  27. package/dist/repositories/identity.d.ts +4 -2
  28. package/dist/repositories/identity.js +32 -1
  29. package/dist/repositories/identity.js.map +1 -1
  30. package/dist/repositories/member.d.ts +16 -0
  31. package/dist/repositories/member.js +63 -0
  32. package/dist/repositories/member.js.map +1 -0
  33. package/dist/repositories/members.js +2 -2
  34. package/dist/repositories/members.js.map +1 -1
  35. package/dist/repositories/notifications.d.ts +23 -0
  36. package/dist/repositories/notifications.js +70 -0
  37. package/dist/repositories/notifications.js.map +1 -0
  38. package/dist/repositories/org-user-profile.d.ts +214 -1
  39. package/dist/repositories/org-user-profile.js +23 -2
  40. package/dist/repositories/org-user-profile.js.map +1 -1
  41. package/dist/repositories/organizations.d.ts +3 -2
  42. package/dist/repositories/organizations.js +20 -8
  43. package/dist/repositories/organizations.js.map +1 -1
  44. package/dist/repositories/pg-errors.js +3 -1
  45. package/dist/repositories/pg-errors.js.map +1 -1
  46. package/dist/repositories/posts.d.ts +39 -0
  47. package/dist/repositories/posts.js +255 -0
  48. package/dist/repositories/posts.js.map +1 -0
  49. package/dist/schema/assets.d.ts +19 -2
  50. package/dist/schema/assets.js +10 -4
  51. package/dist/schema/assets.js.map +1 -1
  52. package/dist/schema/automations.d.ts +38 -0
  53. package/dist/schema/automations.js +5 -1
  54. package/dist/schema/automations.js.map +1 -1
  55. package/dist/schema/better-auth.d.ts +1613 -238
  56. package/dist/schema/better-auth.js +107 -22
  57. package/dist/schema/better-auth.js.map +1 -1
  58. package/dist/schema/content.d.ts +642 -5
  59. package/dist/schema/content.js +119 -10
  60. package/dist/schema/content.js.map +1 -1
  61. package/dist/schema/discussion.d.ts +2045 -55
  62. package/dist/schema/discussion.js +426 -11
  63. package/dist/schema/discussion.js.map +1 -1
  64. package/dist/schema/entitlements.d.ts +432 -1
  65. package/dist/schema/entitlements.js +109 -4
  66. package/dist/schema/entitlements.js.map +1 -1
  67. package/dist/schema/identity.d.ts +517 -1
  68. package/dist/schema/identity.js +118 -2
  69. package/dist/schema/identity.js.map +1 -1
  70. package/dist/schema/index.d.ts +6 -6
  71. package/dist/schema/index.js +30 -7
  72. package/dist/schema/organizations.d.ts +4 -4
  73. package/dist/schema/organizations.js +2 -0
  74. package/dist/schema/organizations.js.map +1 -1
  75. package/drizzle/0003_community.sql +340 -0
  76. package/drizzle/0004_assets_sources.sql +1 -0
  77. package/drizzle/0005_notices_and_seed_index.sql +12 -0
  78. package/drizzle/0006_community_review_fixes.sql +24 -0
  79. package/drizzle/0007_notifications.sql +37 -0
  80. package/drizzle/0008_content_access.sql +13 -0
  81. package/drizzle/0009_community_indexes.sql +56 -0
  82. package/drizzle/0010_mcp_access.sql +114 -0
  83. package/drizzle/meta/0003_snapshot.json +7061 -0
  84. package/drizzle/meta/0004_snapshot.json +7068 -0
  85. package/drizzle/meta/0005_snapshot.json +7180 -0
  86. package/drizzle/meta/0006_snapshot.json +7298 -0
  87. package/drizzle/meta/0007_snapshot.json +7620 -0
  88. package/drizzle/meta/0008_snapshot.json +7666 -0
  89. package/drizzle/meta/0009_snapshot.json +8185 -0
  90. package/drizzle/meta/0010_snapshot.json +8813 -0
  91. package/drizzle/meta/_journal.json +57 -1
  92. package/package.json +2 -2
  93. package/dist/repositories/course-analytics.d.ts +0 -15
  94. package/dist/repositories/course-analytics.js +0 -137
  95. package/dist/repositories/course-analytics.js.map +0 -1
  96. package/dist/repositories/dashboard.d.ts +0 -14
  97. package/dist/repositories/dashboard.js +0 -57
  98. package/dist/repositories/dashboard.js.map +0 -1
  99. package/dist/repositories/learn.d.ts +0 -19
  100. package/dist/repositories/learn.js +0 -56
  101. package/dist/repositories/learn.js.map +0 -1
  102. package/dist/repositories/students.d.ts +0 -16
  103. package/dist/repositories/students.js +0 -160
  104. package/dist/repositories/students.js.map +0 -1
@@ -1,15 +1,55 @@
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
+ import { posts } from "../schema/discussion.js";
2
3
  import { settings } from "../schema/settings.js";
3
- import { isUniqueViolation, translateDbErrors } from "./pg-errors.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, 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 toCommunity(row, storedSettings) {
25
+ const raw = storedSettings?.dm;
26
+ const stored = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
27
+ return {
28
+ ...row,
29
+ status: row.status,
30
+ settings: { dm: {
31
+ ...communitySettingsDefaults.dm,
32
+ ...stored
33
+ } }
34
+ };
35
+ }
36
+ function toSpace(row) {
37
+ return {
38
+ ...row,
39
+ accessRule: row.accessRule,
40
+ settings: {
41
+ ...spaceSettingsDefaults,
42
+ ...row.settings
43
+ }
44
+ };
45
+ }
46
+ const communitySortColumns = {
47
+ title: communities.title,
48
+ slug: communities.slug,
49
+ status: communities.status,
50
+ createdAt: communities.createdAt,
51
+ updatedAt: communities.updatedAt
52
+ };
13
53
  function toCourse(row, storedSettings) {
14
54
  return {
15
55
  ...row,
@@ -199,12 +239,16 @@ var DrizzleContentRepository = class {
199
239
  /** Replace an activity's ordered asset links with `assetIds`. */
200
240
  async replaceActivityAssets(tx, orgId, activityId, assetIds) {
201
241
  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
- })));
242
+ if (assetIds.length) try {
243
+ await tx.insert(activityAssets).values(assetIds.map((assetId, i) => ({
244
+ orgId,
245
+ activityId,
246
+ assetId,
247
+ seq: i
248
+ })));
249
+ } catch (err) {
250
+ throw refusedLink(err) ?? err;
251
+ }
208
252
  }
209
253
  createModule(orgId, courseId, title) {
210
254
  return this.tx(async (tx) => {
@@ -492,6 +536,246 @@ var DrizzleContentRepository = class {
492
536
  await this.db.delete(bundleItems).where(and(eq(bundleItems.orgId, orgId), eq(bundleItems.bundleId, bundleId), eq(bundleItems.contentId, contentId)));
493
537
  return this.listBundleItems(orgId, bundleId);
494
538
  }
539
+ async listCommunities(orgId, query) {
540
+ const conditions = [eq(communities.orgId, orgId)];
541
+ if (query.status) conditions.push(eq(communities.status, query.status));
542
+ const search = query.search?.trim();
543
+ if (search) {
544
+ const like = `%${search}%`;
545
+ const match = or(ilike(communities.title, like), ilike(communities.description, like));
546
+ if (match) conditions.push(match);
547
+ }
548
+ const where = and(...conditions);
549
+ let sortKey = "createdAt";
550
+ let direction = "desc";
551
+ if (query.sort) {
552
+ const isDesc = query.sort.startsWith("-");
553
+ const key = isDesc ? query.sort.slice(1) : query.sort;
554
+ if (key in communitySortColumns) {
555
+ sortKey = key;
556
+ direction = isDesc ? "desc" : "asc";
557
+ }
558
+ }
559
+ const sortExpr = communitySortColumns[sortKey] ?? communities.createdAt;
560
+ const orderBy = direction === "desc" ? desc(sortExpr) : asc(sortExpr);
561
+ const rows = await this.db.select({
562
+ community: communities,
563
+ settingsValue: settings.value
564
+ }).from(communities).leftJoin(settings, communitySettingsJoin).where(where).orderBy(orderBy).limit(query.pageSize).offset((query.page - 1) * query.pageSize);
565
+ const [totalRow] = await this.db.select({ value: count() }).from(communities).where(where);
566
+ return {
567
+ rows: rows.map((row) => toCommunity(row.community, row.settingsValue)),
568
+ total: totalRow?.value ?? 0,
569
+ page: query.page,
570
+ pageSize: query.pageSize
571
+ };
572
+ }
573
+ async findCommunityById(orgId, id) {
574
+ const [row] = await this.db.select({
575
+ community: communities,
576
+ settingsValue: settings.value
577
+ }).from(communities).leftJoin(settings, communitySettingsJoin).where(and(eq(communities.orgId, orgId), eq(communities.id, id))).limit(1);
578
+ return row ? toCommunity(row.community, row.settingsValue) : null;
579
+ }
580
+ async findCommunitiesByIds(orgId, ids) {
581
+ return (await this.db.select({
582
+ community: communities,
583
+ settingsValue: settings.value
584
+ }).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));
585
+ }
586
+ async listPublishedCommunityIds(orgId) {
587
+ return (await this.db.select({ id: communities.id }).from(communities).where(and(eq(communities.orgId, orgId), eq(communities.status, "published"))).orderBy(asc(communities.createdAt), asc(communities.id))).map((row) => row.id);
588
+ }
589
+ async createCommunity(orgId, input, slug) {
590
+ const id = genId("community");
591
+ await this.db.insert(contentItems).values({
592
+ orgId,
593
+ id,
594
+ type: "community"
595
+ });
596
+ const [inserted] = await this.db.insert(communities).values({
597
+ orgId,
598
+ id,
599
+ title: input.title,
600
+ slug,
601
+ description: input.description ?? ""
602
+ }).returning({ id: communities.id });
603
+ if (!inserted) throw new Error("failed to insert community");
604
+ const created = await this.findCommunityById(orgId, inserted.id);
605
+ if (!created) throw new Error("failed to load created community");
606
+ return created;
607
+ }
608
+ async updateCommunity(orgId, id, patch) {
609
+ const set = { updatedAt: /* @__PURE__ */ new Date() };
610
+ if (patch.title !== void 0) set.title = patch.title;
611
+ if (patch.description !== void 0) set.description = patch.description;
612
+ if (patch.status !== void 0) set.status = patch.status;
613
+ if (patch.thumbnailAssetId !== void 0) set.thumbnailAssetId = patch.thumbnailAssetId;
614
+ const [updated] = await this.db.update(communities).set(set).where(and(eq(communities.orgId, orgId), eq(communities.id, id))).returning({ id: communities.id });
615
+ if (!updated) return null;
616
+ return this.findCommunityById(orgId, id);
617
+ }
618
+ async patchDmSettings(orgId, id, patch) {
619
+ if (!await this.findCommunityById(orgId, id)) return null;
620
+ const key = and(eq(settings.orgId, orgId), eq(settings.namespace, SETTINGS_NAMESPACE), eq(settings.scopeId, id));
621
+ await this.tx(async (tx) => {
622
+ let [existing] = await tx.select().from(settings).where(key).for("update");
623
+ const value = { dm: patch };
624
+ if (!existing) {
625
+ const [inserted] = await tx.insert(settings).values({
626
+ orgId,
627
+ namespace: SETTINGS_NAMESPACE,
628
+ scopeId: id,
629
+ value
630
+ }).onConflictDoNothing().returning();
631
+ if (inserted) return;
632
+ [existing] = await tx.select().from(settings).where(key).for("update");
633
+ if (!existing) throw new Error("community settings patch: row vanished mid-transaction");
634
+ }
635
+ await tx.update(settings).set({
636
+ value: deepMerge(existing.value, { dm: patch }),
637
+ updatedAt: /* @__PURE__ */ new Date()
638
+ }).where(key);
639
+ });
640
+ return this.findCommunityById(orgId, id);
641
+ }
642
+ async deleteCommunity(orgId, id) {
643
+ 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;
644
+ }
645
+ async deleteCommunitySettings(orgId, id) {
646
+ await this.db.delete(settings).where(and(eq(settings.orgId, orgId), eq(settings.namespace, SETTINGS_NAMESPACE), eq(settings.scopeId, id)));
647
+ }
648
+ async listSpaces(orgId, communityId) {
649
+ const rows = await this.db.select().from(spaces).where(and(eq(spaces.orgId, orgId), eq(spaces.communityId, communityId))).orderBy(asc(spaces.seq));
650
+ return this.withSpaceParts(orgId, rows.map(toSpace));
651
+ }
652
+ async findSpace(orgId, spaceId) {
653
+ const [row] = await this.db.select().from(spaces).where(and(eq(spaces.orgId, orgId), eq(spaces.id, spaceId))).limit(1);
654
+ if (!row) return null;
655
+ const [detail] = await this.withSpaceParts(orgId, [toSpace(row)]);
656
+ return detail ?? null;
657
+ }
658
+ async lockSpace(orgId, spaceId) {
659
+ const [row] = await this.db.select().from(spaces).where(and(eq(spaces.orgId, orgId), eq(spaces.id, spaceId))).for("update").limit(1);
660
+ return row ? toSpace(row) : null;
661
+ }
662
+ async createSpace(orgId, communityId, input) {
663
+ 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;
664
+ const [inserted] = await this.db.insert(spaces).values({
665
+ orgId,
666
+ communityId,
667
+ name: input.name,
668
+ description: input.description ?? "",
669
+ seq: nextSeq,
670
+ accessRule: input.accessRule ?? "all_members",
671
+ capacity: input.capacity ?? null,
672
+ settings: {
673
+ ...spaceSettingsDefaults,
674
+ ...input.settings
675
+ }
676
+ }).returning({ id: spaces.id });
677
+ if (!inserted) throw new Error("failed to insert space");
678
+ const created = await this.findSpace(orgId, inserted.id);
679
+ if (!created) throw new Error("failed to load created space");
680
+ return created;
681
+ }
682
+ async updateSpace(orgId, communityId, spaceId, patch) {
683
+ const set = { updatedAt: /* @__PURE__ */ new Date() };
684
+ if (patch.name !== void 0) set.name = patch.name;
685
+ if (patch.description !== void 0) set.description = patch.description;
686
+ if (patch.accessRule !== void 0) set.accessRule = patch.accessRule;
687
+ if (patch.capacity !== void 0) set.capacity = patch.capacity;
688
+ if (patch.settings !== void 0) set.settings = {
689
+ ...spaceSettingsDefaults,
690
+ ...patch.settings
691
+ };
692
+ 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 });
693
+ if (!updated) return null;
694
+ return this.findSpace(orgId, spaceId);
695
+ }
696
+ async deleteSpace(orgId, communityId, spaceId) {
697
+ 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;
698
+ }
699
+ async reorderSpaces(orgId, communityId, orderedIds) {
700
+ 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])));
701
+ return this.listSpaces(orgId, communityId);
702
+ }
703
+ async setSpaceTopics(orgId, spaceId, input) {
704
+ await this.tx(async (tx) => {
705
+ const existing = await tx.select({ id: spaceTopics.id }).from(spaceTopics).where(and(eq(spaceTopics.orgId, orgId), eq(spaceTopics.spaceId, spaceId))).for("update");
706
+ const kept = new Set(input.topics.flatMap((topic) => topic.id ? [topic.id] : []));
707
+ const known = new Set(existing.map((topic) => topic.id));
708
+ for (const id of kept) if (!known.has(id)) throw new NotFoundError("Topic", id);
709
+ const removed = existing.filter((topic) => !kept.has(topic.id)).map((topic) => topic.id);
710
+ if (removed.length) await tx.delete(spaceTopics).where(and(eq(spaceTopics.orgId, orgId), inArray(spaceTopics.id, removed)));
711
+ for (let seq = 0; seq < input.topics.length; seq++) {
712
+ const topic = input.topics[seq];
713
+ if (topic.id) await tx.update(spaceTopics).set({
714
+ name: topic.name,
715
+ seq,
716
+ updatedAt: /* @__PURE__ */ new Date()
717
+ }).where(and(eq(spaceTopics.orgId, orgId), eq(spaceTopics.id, topic.id)));
718
+ else await tx.insert(spaceTopics).values({
719
+ orgId,
720
+ spaceId,
721
+ name: topic.name,
722
+ seq
723
+ });
724
+ }
725
+ });
726
+ const space = await this.findSpace(orgId, spaceId);
727
+ if (!space) throw new NotFoundError("Space", spaceId);
728
+ return space;
729
+ }
730
+ async topicsInUse(orgId, spaceId, topicIds) {
731
+ if (topicIds.length === 0) return [];
732
+ 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");
733
+ 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] : []);
734
+ }
735
+ async addSpaceLink(orgId, spaceId, contentId) {
736
+ const [item] = await this.db.select({
737
+ id: contentItems.id,
738
+ type: contentItems.type
739
+ }).from(contentItems).where(and(eq(contentItems.orgId, orgId), eq(contentItems.id, contentId))).limit(1);
740
+ if (!item || item.type !== "course" && item.type !== "download" && item.type !== "community") throw new NotFoundError("Content", contentId);
741
+ 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;
742
+ await this.db.insert(spaceLinks).values({
743
+ orgId,
744
+ spaceId,
745
+ contentId,
746
+ seq: nextSeq
747
+ });
748
+ const space = await this.findSpace(orgId, spaceId);
749
+ if (!space) throw new NotFoundError("Space", spaceId);
750
+ return space;
751
+ }
752
+ async removeSpaceLink(orgId, spaceId, linkId) {
753
+ 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);
754
+ const space = await this.findSpace(orgId, spaceId);
755
+ if (!space) throw new NotFoundError("Space", spaceId);
756
+ return space;
757
+ }
758
+ async reorderSpaceLinks(orgId, spaceId, orderedIds) {
759
+ 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])));
760
+ const space = await this.findSpace(orgId, spaceId);
761
+ if (!space) throw new NotFoundError("Space", spaceId);
762
+ return space;
763
+ }
764
+ async spacesLinkingContent(orgId, contentId) {
765
+ const rows = await this.db.select({ spaceId: spaceLinks.spaceId }).from(spaceLinks).where(and(eq(spaceLinks.orgId, orgId), eq(spaceLinks.contentId, contentId)));
766
+ return [...new Set(rows.map((row) => row.spaceId))];
767
+ }
768
+ async withSpaceParts(orgId, rows) {
769
+ if (rows.length === 0) return [];
770
+ const ids = rows.map((row) => row.id);
771
+ const topics = await this.db.select().from(spaceTopics).where(and(eq(spaceTopics.orgId, orgId), inArray(spaceTopics.spaceId, ids))).orderBy(asc(spaceTopics.seq));
772
+ const links = await this.db.select().from(spaceLinks).where(and(eq(spaceLinks.orgId, orgId), inArray(spaceLinks.spaceId, ids))).orderBy(asc(spaceLinks.seq));
773
+ return rows.map((space) => ({
774
+ ...space,
775
+ topics: topics.filter((topic) => topic.spaceId === space.id),
776
+ links: links.filter((link) => link.spaceId === space.id)
777
+ }));
778
+ }
495
779
  };
496
780
  translateDbErrors(DrizzleContentRepository);
497
781
  //#endregion