@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.
- package/dist/index.d.ts +12 -6
- package/dist/index.js +12 -6
- package/dist/repositories/admin.d.ts +23 -0
- package/dist/repositories/admin.js +393 -0
- package/dist/repositories/admin.js.map +1 -0
- package/dist/repositories/assets.d.ts +6 -3
- package/dist/repositories/assets.js +46 -9
- package/dist/repositories/assets.js.map +1 -1
- package/dist/repositories/automations.d.ts +5 -2
- package/dist/repositories/automations.js +29 -2
- package/dist/repositories/automations.js.map +1 -1
- package/dist/repositories/chats.d.ts +55 -0
- package/dist/repositories/chats.js +469 -0
- package/dist/repositories/chats.js.map +1 -0
- package/dist/repositories/content.d.ts +24 -1
- package/dist/repositories/content.js +316 -9
- package/dist/repositories/content.js.map +1 -1
- package/dist/repositories/devices.d.ts +17 -0
- package/dist/repositories/devices.js +75 -0
- package/dist/repositories/devices.js.map +1 -0
- package/dist/repositories/digest-runs.d.ts +14 -0
- package/dist/repositories/digest-runs.js +39 -0
- package/dist/repositories/digest-runs.js.map +1 -0
- package/dist/repositories/discussion.d.ts +16 -4
- package/dist/repositories/discussion.js +78 -18
- package/dist/repositories/discussion.js.map +1 -1
- package/dist/repositories/entitlements.d.ts +31 -9
- package/dist/repositories/entitlements.js +213 -17
- package/dist/repositories/entitlements.js.map +1 -1
- package/dist/repositories/grant-covers.d.ts +20 -0
- package/dist/repositories/grant-covers.js +55 -0
- package/dist/repositories/grant-covers.js.map +1 -0
- package/dist/repositories/identity.d.ts +4 -2
- package/dist/repositories/identity.js +32 -1
- package/dist/repositories/identity.js.map +1 -1
- package/dist/repositories/inserted-row.d.ts +5 -0
- package/dist/repositories/inserted-row.js +7 -0
- package/dist/repositories/inserted-row.js.map +1 -0
- package/dist/repositories/member.d.ts +16 -0
- package/dist/repositories/member.js +63 -0
- package/dist/repositories/member.js.map +1 -0
- package/dist/repositories/members.js +2 -2
- package/dist/repositories/members.js.map +1 -1
- package/dist/repositories/mentions.d.ts +17 -0
- package/dist/repositories/mentions.js +55 -0
- package/dist/repositories/mentions.js.map +1 -0
- package/dist/repositories/notification-settings.d.ts +19 -0
- package/dist/repositories/notification-settings.js +67 -0
- package/dist/repositories/notification-settings.js.map +1 -0
- package/dist/repositories/notifications.d.ts +30 -0
- package/dist/repositories/notifications.js +178 -0
- package/dist/repositories/notifications.js.map +1 -0
- package/dist/repositories/org-user-profile.d.ts +214 -1
- package/dist/repositories/org-user-profile.js +23 -2
- package/dist/repositories/org-user-profile.js.map +1 -1
- package/dist/repositories/organizations.d.ts +4 -2
- package/dist/repositories/organizations.js +23 -8
- package/dist/repositories/organizations.js.map +1 -1
- package/dist/repositories/pg-errors.js +3 -1
- package/dist/repositories/pg-errors.js.map +1 -1
- package/dist/repositories/posts.d.ts +35 -0
- package/dist/repositories/posts.js +252 -0
- package/dist/repositories/posts.js.map +1 -0
- package/dist/repositories/subscriptions.d.ts +18 -0
- package/dist/repositories/subscriptions.js +72 -0
- package/dist/repositories/subscriptions.js.map +1 -0
- package/dist/schema/assets.d.ts +19 -2
- package/dist/schema/assets.js +10 -4
- package/dist/schema/assets.js.map +1 -1
- package/dist/schema/automations.d.ts +545 -2
- package/dist/schema/automations.js +73 -3
- package/dist/schema/automations.js.map +1 -1
- package/dist/schema/better-auth.d.ts +1613 -238
- package/dist/schema/better-auth.js +107 -22
- package/dist/schema/better-auth.js.map +1 -1
- package/dist/schema/content.d.ts +642 -5
- package/dist/schema/content.js +119 -10
- package/dist/schema/content.js.map +1 -1
- package/dist/schema/discussion.d.ts +2182 -42
- package/dist/schema/discussion.js +461 -11
- package/dist/schema/discussion.js.map +1 -1
- package/dist/schema/entitlements.d.ts +432 -1
- package/dist/schema/entitlements.js +109 -4
- package/dist/schema/entitlements.js.map +1 -1
- package/dist/schema/identity.d.ts +517 -1
- package/dist/schema/identity.js +76 -2
- package/dist/schema/identity.js.map +1 -1
- package/dist/schema/index.d.ts +7 -7
- package/dist/schema/index.js +35 -8
- package/dist/schema/organizations.d.ts +4 -4
- package/dist/schema/organizations.js +2 -0
- package/dist/schema/organizations.js.map +1 -1
- package/drizzle/0003_community.sql +340 -0
- package/drizzle/0004_assets_sources.sql +1 -0
- package/drizzle/0005_notices_and_seed_index.sql +12 -0
- package/drizzle/0006_community_review_fixes.sql +24 -0
- package/drizzle/0007_notifications.sql +37 -0
- package/drizzle/0008_content_access.sql +13 -0
- package/drizzle/0009_community_indexes.sql +56 -0
- package/drizzle/0010_mcp_access.sql +114 -0
- package/drizzle/0011_digest_runs.sql +8 -0
- package/drizzle/0012_subscriptions.sql +16 -0
- package/drizzle/0013_mentions.sql +10 -0
- package/drizzle/0014_notification_settings_devices.sql +36 -0
- package/drizzle/0015_automations_drop_source.sql +2 -0
- package/drizzle/0016_notifications_automations.sql +48 -0
- package/drizzle/meta/0003_snapshot.json +7061 -0
- package/drizzle/meta/0004_snapshot.json +7068 -0
- package/drizzle/meta/0005_snapshot.json +7180 -0
- package/drizzle/meta/0006_snapshot.json +7298 -0
- package/drizzle/meta/0007_snapshot.json +7620 -0
- package/drizzle/meta/0008_snapshot.json +7666 -0
- package/drizzle/meta/0009_snapshot.json +8185 -0
- package/drizzle/meta/0010_snapshot.json +8813 -0
- package/drizzle/meta/0011_snapshot.json +8868 -0
- package/drizzle/meta/0012_snapshot.json +8873 -0
- package/drizzle/meta/0013_snapshot.json +8943 -0
- package/drizzle/meta/0014_snapshot.json +9305 -0
- package/drizzle/meta/0015_snapshot.json +9270 -0
- package/drizzle/meta/0016_snapshot.json +9230 -0
- package/drizzle/meta/_journal.json +99 -1
- package/package.json +2 -2
- package/dist/repositories/course-analytics.d.ts +0 -15
- package/dist/repositories/course-analytics.js +0 -137
- package/dist/repositories/course-analytics.js.map +0 -1
- package/dist/repositories/dashboard.d.ts +0 -14
- package/dist/repositories/dashboard.js +0 -57
- package/dist/repositories/dashboard.js.map +0 -1
- package/dist/repositories/learn.d.ts +0 -19
- package/dist/repositories/learn.js +0 -56
- package/dist/repositories/learn.js.map +0 -1
- package/dist/repositories/students.d.ts +0 -16
- package/dist/repositories/students.js +0 -160
- 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 {
|
|
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)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|