@headless-lms/adapter-db 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +8 -2
- package/dist/client.js +10 -2
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.js +4 -3
- package/dist/repositories/admin.d.ts +12 -4
- package/dist/repositories/admin.js +164 -22
- package/dist/repositories/admin.js.map +1 -1
- package/dist/repositories/assets.d.ts +2 -1
- package/dist/repositories/assets.js +12 -1
- package/dist/repositories/assets.js.map +1 -1
- package/dist/repositories/automations.js +1 -1
- package/dist/repositories/chats.d.ts +13 -4
- package/dist/repositories/chats.js +164 -70
- package/dist/repositories/chats.js.map +1 -1
- package/dist/repositories/content.d.ts +22 -5
- package/dist/repositories/content.js +201 -40
- package/dist/repositories/content.js.map +1 -1
- package/dist/repositories/credentials.js +1 -1
- package/dist/repositories/devices.js +1 -1
- package/dist/repositories/digest-runs.js +1 -1
- package/dist/repositories/discussion.d.ts +1 -1
- package/dist/repositories/discussion.js +9 -3
- package/dist/repositories/discussion.js.map +1 -1
- package/dist/repositories/entitlements.d.ts +10 -10
- package/dist/repositories/entitlements.js +47 -56
- package/dist/repositories/entitlements.js.map +1 -1
- package/dist/repositories/event-reports.d.ts +16 -0
- package/dist/repositories/event-reports.js +199 -0
- package/dist/repositories/event-reports.js.map +1 -0
- package/dist/repositories/grant-covers.d.ts +4 -3
- package/dist/repositories/grant-covers.js +7 -4
- package/dist/repositories/grant-covers.js.map +1 -1
- package/dist/repositories/identity.js +1 -1
- package/dist/repositories/integrations.js +1 -1
- package/dist/repositories/member.d.ts +12 -4
- package/dist/repositories/member.js +19 -7
- package/dist/repositories/member.js.map +1 -1
- package/dist/repositories/members.d.ts +2 -5
- package/dist/repositories/members.js +39 -94
- package/dist/repositories/members.js.map +1 -1
- package/dist/repositories/mentions.js +1 -1
- package/dist/repositories/notification-settings.js +1 -1
- package/dist/repositories/notifications.js +2 -2
- package/dist/repositories/notifications.js.map +1 -1
- package/dist/repositories/org-user-profile.d.ts +0 -17
- package/dist/repositories/org-user-profile.js +0 -1
- package/dist/repositories/org-user-profile.js.map +1 -1
- package/dist/repositories/organizations.d.ts +4 -4
- package/dist/repositories/organizations.js +26 -46
- package/dist/repositories/organizations.js.map +1 -1
- package/dist/repositories/outbox.d.ts +5 -1
- package/dist/repositories/outbox.js +12 -3
- package/dist/repositories/outbox.js.map +1 -1
- package/dist/repositories/posts.js +1 -1
- package/dist/repositories/progress.js +1 -1
- package/dist/repositories/roles.d.ts +35 -0
- package/dist/repositories/roles.js +244 -0
- package/dist/repositories/roles.js.map +1 -0
- package/dist/repositories/settings.js +1 -1
- package/dist/repositories/subscriptions.js +1 -1
- package/dist/schema/assets.d.ts +17 -0
- package/dist/schema/assets.js +1 -0
- package/dist/schema/assets.js.map +1 -1
- package/dist/schema/automations.d.ts +3 -3
- package/dist/schema/automations.js +1 -1
- package/dist/schema/automations.js.map +1 -1
- package/dist/schema/content.d.ts +488 -3
- package/dist/schema/content.js +89 -4
- package/dist/schema/content.js.map +1 -1
- package/dist/schema/discussion.d.ts +17 -0
- package/dist/schema/discussion.js +4 -2
- package/dist/schema/discussion.js.map +1 -1
- package/dist/schema/index.d.ts +4 -3
- package/dist/schema/index.js +7 -2
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/organizations.d.ts +24 -22
- package/dist/schema/organizations.js +3 -14
- package/dist/schema/organizations.js.map +1 -1
- package/dist/schema/roles.d.ts +369 -0
- package/dist/schema/roles.js +71 -0
- package/dist/schema/roles.js.map +1 -0
- package/drizzle/0017_configurable_roles.sql +38 -0
- package/drizzle/0018_messages_client_id.sql +2 -0
- package/drizzle/0019_events.sql +44 -0
- package/drizzle/0020_space_images.sql +9 -0
- package/drizzle/0021_org_metadata.sql +1 -0
- package/drizzle/meta/0017_snapshot.json +9518 -0
- package/drizzle/meta/0018_snapshot.json +9557 -0
- package/drizzle/meta/0019_snapshot.json +9919 -0
- package/drizzle/meta/0020_snapshot.json +10045 -0
- package/drizzle/meta/0021_snapshot.json +10052 -0
- package/drizzle/meta/_journal.json +35 -0
- package/package.json +2 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { assets } from "../schema/assets.js";
|
|
2
|
+
import { contentItems } from "../schema/content.js";
|
|
2
3
|
import { pgErrorFields, translateDbErrors } from "./pg-errors.js";
|
|
4
|
+
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
3
5
|
import { and, asc, desc, eq, ilike, inArray, lt, sql } from "drizzle-orm";
|
|
4
6
|
import { ConflictError } from "@headless-lms/core/shared/errors";
|
|
5
|
-
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
6
7
|
//#region src/repositories/assets.ts
|
|
7
8
|
function toAsset(row) {
|
|
8
9
|
return {
|
|
@@ -16,6 +17,7 @@ function toAsset(row) {
|
|
|
16
17
|
size: row.size,
|
|
17
18
|
status: row.status,
|
|
18
19
|
uploadedBy: row.uploadedBy,
|
|
20
|
+
contentId: row.contentId,
|
|
19
21
|
createdAt: row.createdAt,
|
|
20
22
|
updatedAt: row.updatedAt
|
|
21
23
|
};
|
|
@@ -35,6 +37,9 @@ function referenced(orgId, assetId) {
|
|
|
35
37
|
or exists (select 1 from courses c where c.org_id = ${orgId} and c.thumbnail_asset_id = ${assetId})
|
|
36
38
|
or exists (select 1 from downloads d where d.org_id = ${orgId} and d.thumbnail_asset_id = ${assetId})
|
|
37
39
|
or exists (select 1 from communities c where c.org_id = ${orgId} and c.thumbnail_asset_id = ${assetId})
|
|
40
|
+
or exists (select 1 from spaces s where s.org_id = ${orgId} and ${assetId} in (s.cover_asset_id, s.background_asset_id, s.icon_asset_id))
|
|
41
|
+
or exists (select 1 from event_assets ea where ea.org_id = ${orgId} and ea.asset_id = ${assetId})
|
|
42
|
+
or exists (select 1 from events e where e.org_id = ${orgId} and e.thumbnail_asset_id = ${assetId})
|
|
38
43
|
)`;
|
|
39
44
|
}
|
|
40
45
|
function unreferencedMemberUploads(orgId, before) {
|
|
@@ -60,6 +65,7 @@ var DrizzleAssetsRepository = class {
|
|
|
60
65
|
size: asset.size,
|
|
61
66
|
status: asset.status,
|
|
62
67
|
uploadedBy: asset.uploadedBy,
|
|
68
|
+
contentId: asset.contentId,
|
|
63
69
|
createdAt: new Date(asset.createdAt)
|
|
64
70
|
}).returning();
|
|
65
71
|
if (!row) throw new Error("failed to insert asset");
|
|
@@ -69,6 +75,7 @@ var DrizzleAssetsRepository = class {
|
|
|
69
75
|
const filters = [eq(assets.orgId, orgId)];
|
|
70
76
|
if (query.kind) filters.push(eq(assets.kind, query.kind));
|
|
71
77
|
if (query.source) filters.push(eq(assets.source, query.source));
|
|
78
|
+
if (query.contentId) filters.push(eq(assets.contentId, query.contentId));
|
|
72
79
|
const q = query.search?.trim();
|
|
73
80
|
if (q) filters.push(ilike(assets.filename, `%${q}%`));
|
|
74
81
|
const where = and(...filters);
|
|
@@ -122,6 +129,10 @@ var DrizzleAssetsRepository = class {
|
|
|
122
129
|
if (keys.length === 0) return [];
|
|
123
130
|
return (await this.db.select({ key: assets.key }).from(assets).where(and(eq(assets.orgId, orgId), inArray(assets.key, keys)))).map((r) => r.key);
|
|
124
131
|
}
|
|
132
|
+
async contentTypeOf(orgId, contentId) {
|
|
133
|
+
const [row] = await this.db.select({ type: contentItems.type }).from(contentItems).where(and(eq(contentItems.orgId, orgId), eq(contentItems.id, contentId))).limit(1);
|
|
134
|
+
return row?.type ?? null;
|
|
135
|
+
}
|
|
125
136
|
};
|
|
126
137
|
translateDbErrors(DrizzleAssetsRepository);
|
|
127
138
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.js","names":[],"sources":["../../src/repositories/assets.ts"],"sourcesContent":["import { and, eq, ilike, inArray, lt, sql, asc, desc, type SQL } from \"drizzle-orm\";\nimport type { AssetsRepository } from \"@headless-lms/core/assets\";\nimport type {\n Asset,\n AssetKind,\n AssetSource,\n AssetStatus,\n AssetsQuery,\n Logger,\n Page,\n} from \"@headless-lms/core/types\";\nimport { assets } from \"../schema/assets.js\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport type { DbExecutor } from \"../client.js\";\nimport { ConflictError } from \"@headless-lms/core/shared/errors\";\nimport { pgErrorFields, translateDbErrors } from \"./pg-errors.js\";\n\ntype Row = typeof assets.$inferSelect;\n\nfunction toAsset(row: Row): Asset {\n return {\n orgId: row.orgId,\n id: row.id,\n key: row.key,\n kind: row.kind as AssetKind,\n source: row.source as AssetSource,\n filename: row.filename,\n contentType: row.contentType,\n size: row.size,\n status: row.status as AssetStatus,\n uploadedBy: row.uploadedBy,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nconst SORT_COLUMNS = {\n filename: assets.filename,\n size: assets.size,\n createdAt: assets.createdAt,\n} as const;\n\nfunction referenced(orgId: SQL, assetId: SQL): SQL {\n return sql`(\n exists (select 1 from user_profiles p where p.image_asset_org_id = ${orgId} and p.image_asset_id = ${assetId})\n or exists (select 1 from posts p where p.org_id = ${orgId} and p.cover_asset_id = ${assetId})\n or exists (select 1 from message_attachments ma where ma.org_id = ${orgId} and ma.asset_id = ${assetId})\n or exists (select 1 from activity_assets aa where aa.org_id = ${orgId} and aa.asset_id = ${assetId})\n or exists (select 1 from download_assets da where da.org_id = ${orgId} and da.asset_id = ${assetId})\n or exists (select 1 from courses c where c.org_id = ${orgId} and c.thumbnail_asset_id = ${assetId})\n or exists (select 1 from downloads d where d.org_id = ${orgId} and d.thumbnail_asset_id = ${assetId})\n or exists (select 1 from communities c where c.org_id = ${orgId} and c.thumbnail_asset_id = ${assetId})\n )`;\n}\n\nfunction unreferencedMemberUploads(orgId: string, before: Date): SQL | undefined {\n return and(\n eq(assets.orgId, orgId),\n eq(assets.source, \"member\"),\n lt(assets.createdAt, before),\n sql`not ${referenced(sql`${assets.orgId}`, sql`${assets.id}`)}`,\n );\n}\n\nexport class DrizzleAssetsRepository implements AssetsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, asset: Asset): Promise<Asset> {\n if (asset.orgId !== orgId) {\n throw new Error(\"asset org mismatch\");\n }\n const [row] = await this.db\n .insert(assets)\n .values({\n id: asset.id,\n orgId: asset.orgId,\n key: asset.key,\n kind: asset.kind,\n source: asset.source,\n filename: asset.filename,\n contentType: asset.contentType,\n size: asset.size,\n status: asset.status,\n uploadedBy: asset.uploadedBy,\n createdAt: new Date(asset.createdAt),\n })\n .returning();\n if (!row) {\n throw new Error(\"failed to insert asset\");\n }\n return toAsset(row);\n }\n\n async list(orgId: string, query: AssetsQuery): Promise<Page<Asset>> {\n const filters: (SQL | undefined)[] = [eq(assets.orgId, orgId)];\n if (query.kind) {\n filters.push(eq(assets.kind, query.kind));\n }\n if (query.source) {\n filters.push(eq(assets.source, query.source));\n }\n const q = query.search?.trim();\n if (q) {\n filters.push(ilike(assets.filename, `%${q}%`));\n }\n const where = and(...filters);\n\n const orderBy = (() => {\n if (!query.sort) {\n return desc(assets.createdAt);\n }\n const descending = query.sort.startsWith(\"-\");\n const field = descending ? query.sort.slice(1) : query.sort;\n const col = SORT_COLUMNS[field as keyof typeof SORT_COLUMNS] ?? assets.createdAt;\n return descending ? desc(col) : asc(col);\n })();\n\n const offset = (query.page - 1) * query.pageSize;\n const rows = await this.db\n .select()\n .from(assets)\n .where(where)\n .orderBy(orderBy)\n .limit(query.pageSize)\n .offset(offset);\n\n const counted = await this.db\n .select({ value: sql<number>`count(*)::int` })\n .from(assets)\n .where(where);\n\n return {\n rows: rows.map(toAsset),\n total: counted[0]?.value ?? 0,\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async findById(orgId: string, id: string): Promise<Asset | null> {\n const [row] = await this.db\n .select()\n .from(assets)\n .where(and(eq(assets.orgId, orgId), eq(assets.id, id)))\n .limit(1);\n return row ? toAsset(row) : null;\n }\n\n async findByIds(orgId: string, ids: string[]): Promise<Asset[]> {\n if (ids.length === 0) {\n return [];\n }\n const rows = await this.db\n .select()\n .from(assets)\n .where(and(eq(assets.orgId, orgId), inArray(assets.id, ids)));\n return rows.map(toAsset);\n }\n\n async markReady(\n orgId: string,\n id: string,\n patch: Pick<Asset, \"key\" | \"kind\" | \"size\" | \"contentType\">,\n ): Promise<Asset | null> {\n const [row] = await this.db\n .update(assets)\n .set({\n key: patch.key,\n kind: patch.kind,\n size: patch.size,\n contentType: patch.contentType,\n status: \"ready\",\n })\n .where(and(eq(assets.orgId, orgId), eq(assets.id, id), eq(assets.status, \"pending\")))\n .returning();\n return row ? toAsset(row) : null;\n }\n\n async delete(orgId: string, id: string): Promise<boolean> {\n try {\n const deleted = await this.db\n .delete(assets)\n .where(and(eq(assets.orgId, orgId), eq(assets.id, id)))\n .returning({ id: assets.id });\n return deleted.length > 0;\n } catch (err) {\n if (pgErrorFields(err)?.code === \"23503\") {\n throw Object.assign(new ConflictError(\"This file is still in use\"), { cause: err });\n }\n throw err;\n }\n }\n\n async listUnreferencedMemberUploads(orgId: string, before: Date): Promise<Asset[]> {\n const rows = await this.db\n .select()\n .from(assets)\n .where(unreferencedMemberUploads(orgId, before))\n .orderBy(asc(assets.createdAt));\n return rows.map(toAsset);\n }\n\n async heldKeys(orgId: string, keys: string[]): Promise<string[]> {\n if (keys.length === 0) {\n return [];\n }\n const rows = await this.db\n .select({ key: assets.key })\n .from(assets)\n .where(and(eq(assets.orgId, orgId), inArray(assets.key, keys)));\n return rows.map((r) => r.key);\n }\n}\ntranslateDbErrors(DrizzleAssetsRepository);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"assets.js","names":[],"sources":["../../src/repositories/assets.ts"],"sourcesContent":["import { and, eq, ilike, inArray, lt, sql, asc, desc, type SQL } from \"drizzle-orm\";\nimport type { AssetsRepository } from \"@headless-lms/core/assets\";\nimport type {\n Asset,\n AssetKind,\n AssetSource,\n AssetStatus,\n AssetsQuery,\n ContentType,\n Logger,\n Page,\n} from \"@headless-lms/core/types\";\nimport { assets } from \"../schema/assets.js\";\nimport { contentItems } from \"../schema/content.js\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport type { DbExecutor } from \"../client.js\";\nimport { ConflictError } from \"@headless-lms/core/shared/errors\";\nimport { pgErrorFields, translateDbErrors } from \"./pg-errors.js\";\n\ntype Row = typeof assets.$inferSelect;\n\nfunction toAsset(row: Row): Asset {\n return {\n orgId: row.orgId,\n id: row.id,\n key: row.key,\n kind: row.kind as AssetKind,\n source: row.source as AssetSource,\n filename: row.filename,\n contentType: row.contentType,\n size: row.size,\n status: row.status as AssetStatus,\n uploadedBy: row.uploadedBy,\n contentId: row.contentId,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nconst SORT_COLUMNS = {\n filename: assets.filename,\n size: assets.size,\n createdAt: assets.createdAt,\n} as const;\n\nfunction referenced(orgId: SQL, assetId: SQL): SQL {\n return sql`(\n exists (select 1 from user_profiles p where p.image_asset_org_id = ${orgId} and p.image_asset_id = ${assetId})\n or exists (select 1 from posts p where p.org_id = ${orgId} and p.cover_asset_id = ${assetId})\n or exists (select 1 from message_attachments ma where ma.org_id = ${orgId} and ma.asset_id = ${assetId})\n or exists (select 1 from activity_assets aa where aa.org_id = ${orgId} and aa.asset_id = ${assetId})\n or exists (select 1 from download_assets da where da.org_id = ${orgId} and da.asset_id = ${assetId})\n or exists (select 1 from courses c where c.org_id = ${orgId} and c.thumbnail_asset_id = ${assetId})\n or exists (select 1 from downloads d where d.org_id = ${orgId} and d.thumbnail_asset_id = ${assetId})\n or exists (select 1 from communities c where c.org_id = ${orgId} and c.thumbnail_asset_id = ${assetId})\n or exists (select 1 from spaces s where s.org_id = ${orgId} and ${assetId} in (s.cover_asset_id, s.background_asset_id, s.icon_asset_id))\n or exists (select 1 from event_assets ea where ea.org_id = ${orgId} and ea.asset_id = ${assetId})\n or exists (select 1 from events e where e.org_id = ${orgId} and e.thumbnail_asset_id = ${assetId})\n )`;\n}\n\nfunction unreferencedMemberUploads(orgId: string, before: Date): SQL | undefined {\n return and(\n eq(assets.orgId, orgId),\n eq(assets.source, \"member\"),\n lt(assets.createdAt, before),\n sql`not ${referenced(sql`${assets.orgId}`, sql`${assets.id}`)}`,\n );\n}\n\nexport class DrizzleAssetsRepository implements AssetsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, asset: Asset): Promise<Asset> {\n if (asset.orgId !== orgId) {\n throw new Error(\"asset org mismatch\");\n }\n const [row] = await this.db\n .insert(assets)\n .values({\n id: asset.id,\n orgId: asset.orgId,\n key: asset.key,\n kind: asset.kind,\n source: asset.source,\n filename: asset.filename,\n contentType: asset.contentType,\n size: asset.size,\n status: asset.status,\n uploadedBy: asset.uploadedBy,\n contentId: asset.contentId,\n createdAt: new Date(asset.createdAt),\n })\n .returning();\n if (!row) {\n throw new Error(\"failed to insert asset\");\n }\n return toAsset(row);\n }\n\n async list(orgId: string, query: AssetsQuery): Promise<Page<Asset>> {\n const filters: (SQL | undefined)[] = [eq(assets.orgId, orgId)];\n if (query.kind) {\n filters.push(eq(assets.kind, query.kind));\n }\n if (query.source) {\n filters.push(eq(assets.source, query.source));\n }\n if (query.contentId) {\n filters.push(eq(assets.contentId, query.contentId));\n }\n const q = query.search?.trim();\n if (q) {\n filters.push(ilike(assets.filename, `%${q}%`));\n }\n const where = and(...filters);\n\n const orderBy = (() => {\n if (!query.sort) {\n return desc(assets.createdAt);\n }\n const descending = query.sort.startsWith(\"-\");\n const field = descending ? query.sort.slice(1) : query.sort;\n const col = SORT_COLUMNS[field as keyof typeof SORT_COLUMNS] ?? assets.createdAt;\n return descending ? desc(col) : asc(col);\n })();\n\n const offset = (query.page - 1) * query.pageSize;\n const rows = await this.db\n .select()\n .from(assets)\n .where(where)\n .orderBy(orderBy)\n .limit(query.pageSize)\n .offset(offset);\n\n const counted = await this.db\n .select({ value: sql<number>`count(*)::int` })\n .from(assets)\n .where(where);\n\n return {\n rows: rows.map(toAsset),\n total: counted[0]?.value ?? 0,\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async findById(orgId: string, id: string): Promise<Asset | null> {\n const [row] = await this.db\n .select()\n .from(assets)\n .where(and(eq(assets.orgId, orgId), eq(assets.id, id)))\n .limit(1);\n return row ? toAsset(row) : null;\n }\n\n async findByIds(orgId: string, ids: string[]): Promise<Asset[]> {\n if (ids.length === 0) {\n return [];\n }\n const rows = await this.db\n .select()\n .from(assets)\n .where(and(eq(assets.orgId, orgId), inArray(assets.id, ids)));\n return rows.map(toAsset);\n }\n\n async markReady(\n orgId: string,\n id: string,\n patch: Pick<Asset, \"key\" | \"kind\" | \"size\" | \"contentType\">,\n ): Promise<Asset | null> {\n const [row] = await this.db\n .update(assets)\n .set({\n key: patch.key,\n kind: patch.kind,\n size: patch.size,\n contentType: patch.contentType,\n status: \"ready\",\n })\n .where(and(eq(assets.orgId, orgId), eq(assets.id, id), eq(assets.status, \"pending\")))\n .returning();\n return row ? toAsset(row) : null;\n }\n\n async delete(orgId: string, id: string): Promise<boolean> {\n try {\n const deleted = await this.db\n .delete(assets)\n .where(and(eq(assets.orgId, orgId), eq(assets.id, id)))\n .returning({ id: assets.id });\n return deleted.length > 0;\n } catch (err) {\n if (pgErrorFields(err)?.code === \"23503\") {\n throw Object.assign(new ConflictError(\"This file is still in use\"), { cause: err });\n }\n throw err;\n }\n }\n\n async listUnreferencedMemberUploads(orgId: string, before: Date): Promise<Asset[]> {\n const rows = await this.db\n .select()\n .from(assets)\n .where(unreferencedMemberUploads(orgId, before))\n .orderBy(asc(assets.createdAt));\n return rows.map(toAsset);\n }\n\n async heldKeys(orgId: string, keys: string[]): Promise<string[]> {\n if (keys.length === 0) {\n return [];\n }\n const rows = await this.db\n .select({ key: assets.key })\n .from(assets)\n .where(and(eq(assets.orgId, orgId), inArray(assets.key, keys)));\n return rows.map((r) => r.key);\n }\n\n async contentTypeOf(orgId: string, contentId: string): Promise<ContentType | null> {\n const [row] = await this.db\n .select({ type: contentItems.type })\n .from(contentItems)\n .where(and(eq(contentItems.orgId, orgId), eq(contentItems.id, contentId)))\n .limit(1);\n return row?.type ?? null;\n }\n}\ntranslateDbErrors(DrizzleAssetsRepository);\n"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { automationRuns, automations } from "../schema/automations.js";
|
|
2
2
|
import "../schema/index.js";
|
|
3
3
|
import { translateDbErrors } from "./pg-errors.js";
|
|
4
|
-
import { and, arrayContains, asc, desc, eq, gte, ne, sql } from "drizzle-orm";
|
|
5
4
|
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
5
|
+
import { and, arrayContains, asc, desc, eq, gte, ne, sql } from "drizzle-orm";
|
|
6
6
|
//#region src/repositories/automations.ts
|
|
7
7
|
const automationSelection = {
|
|
8
8
|
orgId: automations.orgId,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
2
2
|
import { Chat, ChatUser, Logger, Message, MessageFlag, MessageFlagResolution, Page, ReactionEmoji } from "@headless-lms/core/types";
|
|
3
|
-
import { ChatInboxRow, ChatRepository, ChatUnread, FeedQuery, MessageReactions,
|
|
3
|
+
import { ChatInboxRow, ChatListQuery, ChatMemberStates, ChatRepository, ChatUnread, ChatUnreadSummary, FeedQuery, MessageReactions, ReactionChange } from "@headless-lms/core/discussion";
|
|
4
4
|
//#region src/repositories/chats.d.ts
|
|
5
5
|
declare class DrizzleChatRepository implements ChatRepository {
|
|
6
6
|
private readonly db;
|
|
@@ -11,11 +11,16 @@ declare class DrizzleChatRepository implements ChatRepository {
|
|
|
11
11
|
insertChat(orgId: string, chat: Chat): Promise<Chat>;
|
|
12
12
|
updateChat(orgId: string, id: string, patch: Partial<Pick<Chat, "status" | "lockedAt" | "lockedBy">>): Promise<Chat | null>;
|
|
13
13
|
findChat(orgId: string, id: string): Promise<Chat | null>;
|
|
14
|
-
listForUser(orgId: string, communityId: string, orgUserId: string, query:
|
|
14
|
+
listForUser(orgId: string, communityId: string, orgUserId: string, query: ChatListQuery): Promise<Page<ChatInboxRow>>;
|
|
15
|
+
inboxRow(orgId: string, chatId: string, orgUserId: string): Promise<ChatInboxRow | null>;
|
|
16
|
+
activeParticipants(orgId: string, chatId: string): Promise<string[]>;
|
|
17
|
+
unreadSummary(orgId: string, communityId: string, orgUserId: string, at: Date): Promise<ChatUnreadSummary>;
|
|
18
|
+
memberStates(orgId: string, communityId: string, orgUserIds: string[], at: Date): Promise<ChatMemberStates[]>;
|
|
19
|
+
findByClientId(orgId: string, chatId: string, orgUserId: string, clientId: string): Promise<Message | null>;
|
|
15
20
|
participants(orgId: string, chatId: string): Promise<ChatUser[]>;
|
|
16
21
|
join(orgId: string, user: ChatUser): Promise<void>;
|
|
17
22
|
markLeft(orgId: string, chatId: string, orgUserId: string, leftAt: Date): Promise<void>;
|
|
18
|
-
setRead(orgId: string, chatId: string, orgUserId: string, messageId: string): Promise<
|
|
23
|
+
setRead(orgId: string, chatId: string, orgUserId: string, messageId: string): Promise<boolean>;
|
|
19
24
|
unreadFor(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;
|
|
20
25
|
insertMessage(orgId: string, message: Message): Promise<Message>;
|
|
21
26
|
updateMessage(orgId: string, id: string, patch: Partial<Pick<Message, "body" | "editedAt" | "deletedAt">>): Promise<Message | null>;
|
|
@@ -23,7 +28,7 @@ declare class DrizzleChatRepository implements ChatRepository {
|
|
|
23
28
|
listMessages(orgId: string, chatId: string, query: Pick<FeedQuery, "page" | "pageSize">): Promise<Page<Message>>;
|
|
24
29
|
attach(orgId: string, communityId: string, messageId: string, assetIds: string[]): Promise<void>;
|
|
25
30
|
attachmentsOf(orgId: string, messageIds: string[]): Promise<Record<string, string[]>>;
|
|
26
|
-
setReaction(orgId: string, communityId: string, messageId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<
|
|
31
|
+
setReaction(orgId: string, communityId: string, messageId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<ReactionChange>;
|
|
27
32
|
reactionsOf(orgId: string, messageIds: string[], viewerId: string): Promise<Record<string, MessageReactions>>;
|
|
28
33
|
block(orgId: string, communityId: string, blockerOrgUserId: string, blockedOrgUserId: string): Promise<void>;
|
|
29
34
|
unblock(orgId: string, communityId: string, blockerOrgUserId: string, blockedOrgUserId: string): Promise<void>;
|
|
@@ -48,6 +53,10 @@ declare class DrizzleChatRepository implements ChatRepository {
|
|
|
48
53
|
resolvedBy: string;
|
|
49
54
|
resolution: MessageFlagResolution;
|
|
50
55
|
}): Promise<void>;
|
|
56
|
+
private requestTo;
|
|
57
|
+
private unreadCount;
|
|
58
|
+
private inbox;
|
|
59
|
+
private withParticipants;
|
|
51
60
|
private touch;
|
|
52
61
|
}
|
|
53
62
|
//#endregion
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { blockedUsers, chatUsers, chats, messageAttachments, messageFlags, messageReactions, messages } from "../schema/discussion.js";
|
|
1
|
+
import { blockedUsers, chatUsers, chats, messageAttachments, messageFlags, messageReactions, messages, subscriptions } from "../schema/discussion.js";
|
|
2
2
|
import "../schema/index.js";
|
|
3
3
|
import { translateDbErrors } from "./pg-errors.js";
|
|
4
4
|
import { insertedRow } from "./inserted-row.js";
|
|
5
|
-
import { and, asc, desc, eq, gt, inArray, isNull, ne, or, sql } from "drizzle-orm";
|
|
6
5
|
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
6
|
+
import { and, asc, desc, eq, gt, inArray, isNull, ne, or, sql } from "drizzle-orm";
|
|
7
7
|
//#region src/repositories/chats.ts
|
|
8
8
|
function toChat(row) {
|
|
9
9
|
return {
|
|
@@ -41,6 +41,7 @@ function toMessage(row) {
|
|
|
41
41
|
orgUserId: row.orgUserId,
|
|
42
42
|
body: row.body,
|
|
43
43
|
replyToId: row.replyToId,
|
|
44
|
+
clientId: row.clientId,
|
|
44
45
|
editedAt: row.editedAt,
|
|
45
46
|
deletedAt: row.deletedAt,
|
|
46
47
|
createdAt: row.createdAt,
|
|
@@ -126,75 +127,81 @@ var DrizzleChatRepository = class {
|
|
|
126
127
|
return row ? toChat(row) : null;
|
|
127
128
|
}
|
|
128
129
|
async listForUser(orgId, communityId, orgUserId, query) {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
and m.chat_id = ${chats.id}
|
|
134
|
-
and m.org_user_id <> ${orgUserId}
|
|
135
|
-
and m.deleted_at is null
|
|
136
|
-
and (
|
|
137
|
-
${chatUsers.lastReadMessageId} is null
|
|
138
|
-
or m.created_at > coalesce((
|
|
139
|
-
select lr.created_at
|
|
140
|
-
from messages lr
|
|
141
|
-
where lr.org_id = ${chats.orgId}
|
|
142
|
-
and lr.id = ${chatUsers.lastReadMessageId}
|
|
143
|
-
), '-infinity'::timestamptz)
|
|
144
|
-
)
|
|
145
|
-
) as int)`;
|
|
146
|
-
const latest = this.db.select({
|
|
147
|
-
id: messages.id,
|
|
148
|
-
orgUserId: messages.orgUserId,
|
|
149
|
-
body: messages.body,
|
|
150
|
-
createdAt: messages.createdAt,
|
|
151
|
-
deletedAt: messages.deletedAt
|
|
152
|
-
}).from(messages).where(and(eq(messages.orgId, chats.orgId), eq(messages.chatId, chats.id))).orderBy(desc(messages.createdAt), desc(messages.id)).limit(1).as("last_message");
|
|
153
|
-
const mine = and(eq(chatUsers.orgId, orgId), eq(chatUsers.communityId, communityId), eq(chatUsers.orgUserId, orgUserId), isNull(chatUsers.leftAt));
|
|
154
|
-
const rows = await this.db.select({
|
|
155
|
-
chat: chats,
|
|
156
|
-
lastReadMessageId: chatUsers.lastReadMessageId,
|
|
157
|
-
unread,
|
|
158
|
-
lastId: latest.id,
|
|
159
|
-
lastOrgUserId: latest.orgUserId,
|
|
160
|
-
lastBody: latest.body,
|
|
161
|
-
lastCreatedAt: latest.createdAt,
|
|
162
|
-
lastDeletedAt: latest.deletedAt
|
|
163
|
-
}).from(chatUsers).innerJoin(chats, and(eq(chats.orgId, chatUsers.orgId), eq(chats.id, chatUsers.chatId))).leftJoinLateral(latest, sql`true`).where(mine).orderBy(desc(chats.updatedAt), desc(chats.id)).limit(query.pageSize).offset((query.page - 1) * query.pageSize);
|
|
164
|
-
const [{ total } = { total: 0 }] = await this.db.select({ total: sql`cast(count(*) as int)` }).from(chatUsers).where(mine);
|
|
130
|
+
const request = this.requestTo(orgUserId);
|
|
131
|
+
const mine = and(eq(chatUsers.orgId, orgId), eq(chatUsers.communityId, communityId), eq(chatUsers.orgUserId, orgUserId), isNull(chatUsers.leftAt), query.requests === void 0 ? void 0 : query.requests ? request : sql`not ${request}`);
|
|
132
|
+
const rows = await this.inbox(orgUserId, mine, query);
|
|
133
|
+
const [{ total } = { total: 0 }] = await this.db.select({ total: sql`cast(count(*) as int)` }).from(chatUsers).innerJoin(chats, and(eq(chats.orgId, chatUsers.orgId), eq(chats.id, chatUsers.chatId))).where(mine);
|
|
165
134
|
const page = {
|
|
166
135
|
total,
|
|
167
136
|
page: query.page,
|
|
168
137
|
pageSize: query.pageSize
|
|
169
138
|
};
|
|
170
|
-
if (rows.length === 0) return {
|
|
171
|
-
rows: [],
|
|
172
|
-
...page
|
|
173
|
-
};
|
|
174
|
-
const people = await this.db.select().from(chatUsers).where(and(eq(chatUsers.orgId, orgId), inArray(chatUsers.chatId, rows.map((row) => row.chat.id)), isNull(chatUsers.leftAt)));
|
|
175
|
-
const byChat = /* @__PURE__ */ new Map();
|
|
176
|
-
for (const person of people) {
|
|
177
|
-
const list = byChat.get(person.chatId) ?? [];
|
|
178
|
-
list.push(person.orgUserId);
|
|
179
|
-
byChat.set(person.chatId, list);
|
|
180
|
-
}
|
|
181
139
|
return {
|
|
182
|
-
rows:
|
|
183
|
-
chat: toChat(row.chat),
|
|
184
|
-
participantIds: (byChat.get(row.chat.id) ?? []).sort(),
|
|
185
|
-
lastReadMessageId: row.lastReadMessageId,
|
|
186
|
-
unread: row.unread,
|
|
187
|
-
lastMessage: row.lastId !== null && row.lastOrgUserId !== null && row.lastCreatedAt !== null ? {
|
|
188
|
-
id: row.lastId,
|
|
189
|
-
orgUserId: row.lastOrgUserId,
|
|
190
|
-
body: row.lastBody ?? "",
|
|
191
|
-
createdAt: row.lastCreatedAt,
|
|
192
|
-
deletedAt: row.lastDeletedAt
|
|
193
|
-
} : null
|
|
194
|
-
})),
|
|
140
|
+
rows: await this.withParticipants(orgId, rows),
|
|
195
141
|
...page
|
|
196
142
|
};
|
|
197
143
|
}
|
|
144
|
+
async inboxRow(orgId, chatId, orgUserId) {
|
|
145
|
+
const rows = await this.inbox(orgUserId, and(eq(chatUsers.orgId, orgId), eq(chatUsers.chatId, chatId), eq(chatUsers.orgUserId, orgUserId), isNull(chatUsers.leftAt)));
|
|
146
|
+
const [row] = await this.withParticipants(orgId, rows);
|
|
147
|
+
return row ?? null;
|
|
148
|
+
}
|
|
149
|
+
async activeParticipants(orgId, chatId) {
|
|
150
|
+
return (await this.db.select({ orgUserId: chatUsers.orgUserId }).from(chatUsers).where(and(eq(chatUsers.orgId, orgId), eq(chatUsers.chatId, chatId), isNull(chatUsers.leftAt))).orderBy(asc(chatUsers.orgUserId))).map((row) => row.orgUserId);
|
|
151
|
+
}
|
|
152
|
+
async unreadSummary(orgId, communityId, orgUserId, at) {
|
|
153
|
+
const [states] = await this.memberStates(orgId, communityId, [orgUserId], at);
|
|
154
|
+
return states?.unread ?? {
|
|
155
|
+
unread: 0,
|
|
156
|
+
requests: 0
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async memberStates(orgId, communityId, orgUserIds, at) {
|
|
160
|
+
const ids = [...new Set(orgUserIds)];
|
|
161
|
+
if (ids.length === 0) return [];
|
|
162
|
+
const rows = await this.db.select({
|
|
163
|
+
orgUserId: chatUsers.orgUserId,
|
|
164
|
+
chatId: chatUsers.chatId,
|
|
165
|
+
lastReadMessageId: chatUsers.lastReadMessageId,
|
|
166
|
+
kind: chats.kind,
|
|
167
|
+
status: chats.status,
|
|
168
|
+
createdBy: chats.createdBy,
|
|
169
|
+
state: subscriptions.state,
|
|
170
|
+
mutedUntil: subscriptions.mutedUntil,
|
|
171
|
+
unread: this.unreadCount(chatUsers.orgUserId)
|
|
172
|
+
}).from(chatUsers).innerJoin(chats, and(eq(chats.orgId, chatUsers.orgId), eq(chats.id, chatUsers.chatId))).leftJoin(subscriptions, and(eq(subscriptions.orgId, chatUsers.orgId), eq(subscriptions.orgUserId, chatUsers.orgUserId), eq(subscriptions.itemType, "chat"), eq(subscriptions.itemId, chatUsers.chatId))).where(and(eq(chatUsers.orgId, orgId), eq(chatUsers.communityId, communityId), inArray(chatUsers.orgUserId, ids), isNull(chatUsers.leftAt))).orderBy(asc(chatUsers.orgUserId), asc(chatUsers.chatId));
|
|
173
|
+
const byMember = new Map(ids.map((id) => [id, {
|
|
174
|
+
orgUserId: id,
|
|
175
|
+
unread: {
|
|
176
|
+
unread: 0,
|
|
177
|
+
requests: 0
|
|
178
|
+
},
|
|
179
|
+
chats: []
|
|
180
|
+
}]));
|
|
181
|
+
for (const row of rows) {
|
|
182
|
+
const entry = byMember.get(row.orgUserId);
|
|
183
|
+
const lapsed = row.state === "muted" && row.mutedUntil !== null && row.mutedUntil.getTime() <= at.getTime();
|
|
184
|
+
const subscription = row.state !== null && !lapsed ? {
|
|
185
|
+
state: row.state,
|
|
186
|
+
mutedUntil: row.mutedUntil
|
|
187
|
+
} : null;
|
|
188
|
+
if (row.kind === "direct" && row.status === "pending" && row.createdBy !== row.orgUserId) {
|
|
189
|
+
entry.unread.requests += 1;
|
|
190
|
+
entry.unread.unread += 1;
|
|
191
|
+
} else if (subscription?.state !== "muted") entry.unread.unread += row.unread;
|
|
192
|
+
entry.chats.push({
|
|
193
|
+
chatId: row.chatId,
|
|
194
|
+
unread: row.unread,
|
|
195
|
+
lastReadMessageId: row.lastReadMessageId,
|
|
196
|
+
subscription
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
return [...byMember.values()];
|
|
200
|
+
}
|
|
201
|
+
async findByClientId(orgId, chatId, orgUserId, clientId) {
|
|
202
|
+
const [row] = await this.db.select().from(messages).where(and(eq(messages.orgId, orgId), eq(messages.chatId, chatId), eq(messages.orgUserId, orgUserId), eq(messages.clientId, clientId))).limit(1);
|
|
203
|
+
return row ? toMessage(row) : null;
|
|
204
|
+
}
|
|
198
205
|
async participants(orgId, chatId) {
|
|
199
206
|
return (await this.db.select().from(chatUsers).where(and(eq(chatUsers.orgId, orgId), eq(chatUsers.chatId, chatId))).orderBy(asc(chatUsers.orgUserId))).map(toChatUser);
|
|
200
207
|
}
|
|
@@ -230,10 +237,25 @@ var DrizzleChatRepository = class {
|
|
|
230
237
|
await this.touch(orgId, chatId);
|
|
231
238
|
}
|
|
232
239
|
async setRead(orgId, chatId, orgUserId, messageId) {
|
|
233
|
-
await this.db.update(chatUsers).set({
|
|
240
|
+
return (await this.db.update(chatUsers).set({
|
|
234
241
|
lastReadMessageId: messageId,
|
|
235
242
|
updatedAt: /* @__PURE__ */ new Date()
|
|
236
|
-
}).where(and(eq(chatUsers.orgId, orgId), eq(chatUsers.chatId, chatId), eq(chatUsers.orgUserId, orgUserId)
|
|
243
|
+
}).where(and(eq(chatUsers.orgId, orgId), eq(chatUsers.chatId, chatId), eq(chatUsers.orgUserId, orgUserId), sql`(
|
|
244
|
+
${chatUsers.lastReadMessageId} is null
|
|
245
|
+
or not exists (
|
|
246
|
+
select 1 from messages cur
|
|
247
|
+
where cur.org_id = ${chatUsers.orgId} and cur.id = ${chatUsers.lastReadMessageId}
|
|
248
|
+
)
|
|
249
|
+
or exists (
|
|
250
|
+
select 1
|
|
251
|
+
from messages target
|
|
252
|
+
join messages cur on cur.org_id = target.org_id
|
|
253
|
+
where target.org_id = ${chatUsers.orgId}
|
|
254
|
+
and target.id = ${messageId}
|
|
255
|
+
and cur.id = ${chatUsers.lastReadMessageId}
|
|
256
|
+
and (target.created_at, target.id) > (cur.created_at, cur.id)
|
|
257
|
+
)
|
|
258
|
+
)`)).returning({ orgUserId: chatUsers.orgUserId })).length > 0;
|
|
237
259
|
}
|
|
238
260
|
async unreadFor(orgId, chatId, orgUserId) {
|
|
239
261
|
const readUpTo = sql`coalesce((
|
|
@@ -271,6 +293,7 @@ var DrizzleChatRepository = class {
|
|
|
271
293
|
orgUserId: message.orgUserId,
|
|
272
294
|
body: message.body,
|
|
273
295
|
replyToId: message.replyToId,
|
|
296
|
+
clientId: message.clientId,
|
|
274
297
|
editedAt: message.editedAt,
|
|
275
298
|
deletedAt: message.deletedAt,
|
|
276
299
|
createdAt: message.createdAt,
|
|
@@ -328,10 +351,10 @@ var DrizzleChatRepository = class {
|
|
|
328
351
|
return byMessage;
|
|
329
352
|
}
|
|
330
353
|
async setReaction(orgId, communityId, messageId, orgUserId, emoji) {
|
|
331
|
-
if (emoji === null) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
354
|
+
if (emoji === null) return {
|
|
355
|
+
inserted: false,
|
|
356
|
+
changed: (await this.db.delete(messageReactions).where(and(eq(messageReactions.orgId, orgId), eq(messageReactions.messageId, messageId), eq(messageReactions.orgUserId, orgUserId))).returning({ orgUserId: messageReactions.orgUserId })).length > 0
|
|
357
|
+
};
|
|
335
358
|
const [row] = await this.db.insert(messageReactions).values({
|
|
336
359
|
orgId,
|
|
337
360
|
communityId,
|
|
@@ -347,9 +370,13 @@ var DrizzleChatRepository = class {
|
|
|
347
370
|
set: {
|
|
348
371
|
emoji,
|
|
349
372
|
updatedAt: /* @__PURE__ */ new Date()
|
|
350
|
-
}
|
|
373
|
+
},
|
|
374
|
+
setWhere: sql`${messageReactions.emoji} is distinct from excluded.emoji`
|
|
351
375
|
}).returning({ inserted: insertedRow });
|
|
352
|
-
return
|
|
376
|
+
return {
|
|
377
|
+
inserted: row?.inserted === true,
|
|
378
|
+
changed: row !== void 0
|
|
379
|
+
};
|
|
353
380
|
}
|
|
354
381
|
async reactionsOf(orgId, messageIds, viewerId) {
|
|
355
382
|
if (messageIds.length === 0) return {};
|
|
@@ -458,6 +485,73 @@ var DrizzleChatRepository = class {
|
|
|
458
485
|
updatedAt: /* @__PURE__ */ new Date()
|
|
459
486
|
}).where(and(eq(messageFlags.orgId, orgId), eq(messageFlags.messageId, messageId), isNull(messageFlags.resolvedAt)));
|
|
460
487
|
}
|
|
488
|
+
requestTo(orgUserId) {
|
|
489
|
+
return sql`(${chats.kind} = 'direct' and ${chats.status} = 'pending' and ${chats.createdBy} is distinct from ${orgUserId})`;
|
|
490
|
+
}
|
|
491
|
+
unreadCount(orgUserId) {
|
|
492
|
+
return sql`cast((
|
|
493
|
+
select count(*)
|
|
494
|
+
from messages m
|
|
495
|
+
where m.org_id = ${chatUsers.orgId}
|
|
496
|
+
and m.chat_id = ${chatUsers.chatId}
|
|
497
|
+
and m.org_user_id <> ${orgUserId}
|
|
498
|
+
and m.deleted_at is null
|
|
499
|
+
and (
|
|
500
|
+
${chatUsers.lastReadMessageId} is null
|
|
501
|
+
or m.created_at > coalesce((
|
|
502
|
+
select lr.created_at
|
|
503
|
+
from messages lr
|
|
504
|
+
where lr.org_id = ${chatUsers.orgId}
|
|
505
|
+
and lr.id = ${chatUsers.lastReadMessageId}
|
|
506
|
+
), '-infinity'::timestamptz)
|
|
507
|
+
)
|
|
508
|
+
) as int)`;
|
|
509
|
+
}
|
|
510
|
+
async inbox(orgUserId, where, query) {
|
|
511
|
+
const latest = this.db.select({
|
|
512
|
+
id: messages.id,
|
|
513
|
+
orgUserId: messages.orgUserId,
|
|
514
|
+
body: messages.body,
|
|
515
|
+
createdAt: messages.createdAt,
|
|
516
|
+
deletedAt: messages.deletedAt
|
|
517
|
+
}).from(messages).where(and(eq(messages.orgId, chats.orgId), eq(messages.chatId, chats.id))).orderBy(desc(messages.createdAt), desc(messages.id)).limit(1).as("last_message");
|
|
518
|
+
const select = this.db.select({
|
|
519
|
+
chat: chats,
|
|
520
|
+
lastReadMessageId: chatUsers.lastReadMessageId,
|
|
521
|
+
unread: this.unreadCount(orgUserId),
|
|
522
|
+
lastId: latest.id,
|
|
523
|
+
lastOrgUserId: latest.orgUserId,
|
|
524
|
+
lastBody: latest.body,
|
|
525
|
+
lastCreatedAt: latest.createdAt,
|
|
526
|
+
lastDeletedAt: latest.deletedAt
|
|
527
|
+
}).from(chatUsers).innerJoin(chats, and(eq(chats.orgId, chatUsers.orgId), eq(chats.id, chatUsers.chatId))).leftJoinLateral(latest, sql`true`).where(where).orderBy(desc(chats.updatedAt), desc(chats.id));
|
|
528
|
+
return (query ? await select.limit(query.pageSize).offset((query.page - 1) * query.pageSize) : await select.limit(1)).map((row) => ({
|
|
529
|
+
chat: toChat(row.chat),
|
|
530
|
+
lastReadMessageId: row.lastReadMessageId,
|
|
531
|
+
unread: row.unread,
|
|
532
|
+
lastMessage: row.lastId !== null && row.lastOrgUserId !== null && row.lastCreatedAt !== null ? {
|
|
533
|
+
id: row.lastId,
|
|
534
|
+
orgUserId: row.lastOrgUserId,
|
|
535
|
+
body: row.lastBody ?? "",
|
|
536
|
+
createdAt: row.lastCreatedAt,
|
|
537
|
+
deletedAt: row.lastDeletedAt
|
|
538
|
+
} : null
|
|
539
|
+
}));
|
|
540
|
+
}
|
|
541
|
+
async withParticipants(orgId, rows) {
|
|
542
|
+
if (rows.length === 0) return [];
|
|
543
|
+
const people = await this.db.select().from(chatUsers).where(and(eq(chatUsers.orgId, orgId), inArray(chatUsers.chatId, rows.map((row) => row.chat.id)), isNull(chatUsers.leftAt)));
|
|
544
|
+
const byChat = /* @__PURE__ */ new Map();
|
|
545
|
+
for (const person of people) {
|
|
546
|
+
const list = byChat.get(person.chatId) ?? [];
|
|
547
|
+
list.push(person.orgUserId);
|
|
548
|
+
byChat.set(person.chatId, list);
|
|
549
|
+
}
|
|
550
|
+
return rows.map((row) => ({
|
|
551
|
+
...row,
|
|
552
|
+
participantIds: (byChat.get(row.chat.id) ?? []).sort()
|
|
553
|
+
}));
|
|
554
|
+
}
|
|
461
555
|
async touch(orgId, chatId) {
|
|
462
556
|
await this.db.update(chats).set({ updatedAt: /* @__PURE__ */ new Date() }).where(and(eq(chats.orgId, orgId), eq(chats.id, chatId)));
|
|
463
557
|
}
|