@headless-lms/adapter-db 0.3.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 +6 -1
- package/dist/index.js +6 -1
- package/dist/repositories/admin.js +1 -1
- package/dist/repositories/assets.d.ts +1 -2
- package/dist/repositories/assets.js +0 -4
- package/dist/repositories/assets.js.map +1 -1
- package/dist/repositories/automations.d.ts +0 -1
- package/dist/repositories/automations.js +1 -17
- package/dist/repositories/automations.js.map +1 -1
- package/dist/repositories/chats.d.ts +4 -4
- package/dist/repositories/chats.js +33 -18
- package/dist/repositories/chats.js.map +1 -1
- package/dist/repositories/content.d.ts +3 -3
- package/dist/repositories/content.js +35 -12
- 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 +6 -1
- package/dist/repositories/discussion.js +9 -4
- package/dist/repositories/discussion.js.map +1 -1
- package/dist/repositories/entitlements.d.ts +3 -1
- package/dist/repositories/entitlements.js +12 -6
- package/dist/repositories/entitlements.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/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 +23 -16
- package/dist/repositories/notifications.js +152 -44
- package/dist/repositories/notifications.js.map +1 -1
- package/dist/repositories/organizations.d.ts +1 -0
- package/dist/repositories/organizations.js +4 -1
- package/dist/repositories/organizations.js.map +1 -1
- package/dist/repositories/posts.d.ts +4 -8
- package/dist/repositories/posts.js +25 -28
- package/dist/repositories/posts.js.map +1 -1
- 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/automations.d.ts +524 -19
- package/dist/schema/automations.js +73 -7
- package/dist/schema/automations.js.map +1 -1
- package/dist/schema/discussion.d.ts +277 -127
- package/dist/schema/discussion.js +59 -24
- package/dist/schema/discussion.js.map +1 -1
- package/dist/schema/identity.d.ts +109 -109
- package/dist/schema/identity.js +28 -70
- package/dist/schema/identity.js.map +1 -1
- package/dist/schema/index.d.ts +5 -5
- package/dist/schema/index.js +10 -6
- 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/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 +42 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { DrizzleAutomationRunsRepository, DrizzleAutomationsRepository } from ".
|
|
|
7
7
|
import { DrizzleContentRepository } from "./repositories/content.js";
|
|
8
8
|
import { DrizzleCredentialStore, decryptSecret, encryptSecret } from "./repositories/credentials.js";
|
|
9
9
|
import { DrizzleDiscussionRepository } from "./repositories/discussion.js";
|
|
10
|
+
import { DrizzleDigestRunRepository } from "./repositories/digest-runs.js";
|
|
10
11
|
import { DrizzlePostRepository } from "./repositories/posts.js";
|
|
11
12
|
import { DrizzleChatRepository } from "./repositories/chats.js";
|
|
12
13
|
import { DrizzleEntitlementsRepository } from "./repositories/entitlements.js";
|
|
@@ -14,11 +15,15 @@ import { DrizzleIdentityRepository } from "./repositories/identity.js";
|
|
|
14
15
|
import { DrizzleConnectionsRepository } from "./repositories/integrations.js";
|
|
15
16
|
import { DrizzleMemberRepository } from "./repositories/member.js";
|
|
16
17
|
import { DrizzleMembersRepository } from "./repositories/members.js";
|
|
18
|
+
import { DrizzleMentionsRepository } from "./repositories/mentions.js";
|
|
17
19
|
import { DrizzleNotificationsRepository } from "./repositories/notifications.js";
|
|
20
|
+
import { DrizzleNotificationSettingsRepository } from "./repositories/notification-settings.js";
|
|
21
|
+
import { DrizzleDevicesRepository } from "./repositories/devices.js";
|
|
18
22
|
import { authorProfileColumns, orgUserProfileColumns, studentFirstName, studentLastName, studentProfileColumns } from "./repositories/org-user-profile.js";
|
|
19
23
|
import { DrizzleOrganizationsRepository } from "./repositories/organizations.js";
|
|
20
24
|
import { DrizzleOutboxAppender, DrizzleOutboxStore, OUTBOX_MAX_ATTEMPTS } from "./repositories/outbox.js";
|
|
21
25
|
import { DrizzleProgressRepository } from "./repositories/progress.js";
|
|
22
26
|
import { DrizzleSettingsRepository, deepMerge } from "./repositories/settings.js";
|
|
27
|
+
import { DrizzleSubscriptionsRepository } from "./repositories/subscriptions.js";
|
|
23
28
|
import { DrizzleAdminRepository } from "./repositories/admin.js";
|
|
24
|
-
export { type Db, DrizzleAdminRepository, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleChatRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleMemberRepository, DrizzleMembersRepository, DrizzleNotificationsRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzlePostRepository, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleUnitOfWork, OUTBOX_MAX_ATTEMPTS, authorProfileColumns, createDb, decryptSecret, deepMerge, encryptSecret, migrationsFolder, orgUserProfileColumns, runMigrations, index_d_exports as schema, studentFirstName, studentLastName, studentProfileColumns };
|
|
29
|
+
export { type Db, DrizzleAdminRepository, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleChatRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDevicesRepository, DrizzleDigestRunRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleMemberRepository, DrizzleMembersRepository, DrizzleMentionsRepository, DrizzleNotificationSettingsRepository, DrizzleNotificationsRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzlePostRepository, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleSubscriptionsRepository, DrizzleUnitOfWork, OUTBOX_MAX_ATTEMPTS, authorProfileColumns, createDb, decryptSecret, deepMerge, encryptSecret, migrationsFolder, orgUserProfileColumns, runMigrations, index_d_exports as schema, studentFirstName, studentLastName, studentProfileColumns };
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { DrizzleContentRepository } from "./repositories/content.js";
|
|
|
9
9
|
import { DrizzleCredentialStore, decryptSecret, encryptSecret } from "./repositories/credentials.js";
|
|
10
10
|
import { authorProfileColumns, orgUserProfileColumns, studentFirstName, studentLastName, studentProfileColumns } from "./repositories/org-user-profile.js";
|
|
11
11
|
import { DrizzleDiscussionRepository } from "./repositories/discussion.js";
|
|
12
|
+
import { DrizzleDigestRunRepository } from "./repositories/digest-runs.js";
|
|
12
13
|
import { DrizzlePostRepository } from "./repositories/posts.js";
|
|
13
14
|
import { DrizzleChatRepository } from "./repositories/chats.js";
|
|
14
15
|
import { DrizzleEntitlementsRepository } from "./repositories/entitlements.js";
|
|
@@ -16,9 +17,13 @@ import { DrizzleIdentityRepository } from "./repositories/identity.js";
|
|
|
16
17
|
import { DrizzleConnectionsRepository } from "./repositories/integrations.js";
|
|
17
18
|
import { DrizzleMemberRepository } from "./repositories/member.js";
|
|
18
19
|
import { DrizzleMembersRepository } from "./repositories/members.js";
|
|
20
|
+
import { DrizzleMentionsRepository } from "./repositories/mentions.js";
|
|
19
21
|
import { DrizzleNotificationsRepository } from "./repositories/notifications.js";
|
|
22
|
+
import { DrizzleNotificationSettingsRepository } from "./repositories/notification-settings.js";
|
|
23
|
+
import { DrizzleDevicesRepository } from "./repositories/devices.js";
|
|
20
24
|
import { DrizzleOrganizationsRepository } from "./repositories/organizations.js";
|
|
21
25
|
import { DrizzleOutboxAppender, DrizzleOutboxStore, OUTBOX_MAX_ATTEMPTS } from "./repositories/outbox.js";
|
|
22
26
|
import { DrizzleProgressRepository } from "./repositories/progress.js";
|
|
27
|
+
import { DrizzleSubscriptionsRepository } from "./repositories/subscriptions.js";
|
|
23
28
|
import { DrizzleAdminRepository } from "./repositories/admin.js";
|
|
24
|
-
export { DrizzleAdminRepository, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleChatRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleMemberRepository, DrizzleMembersRepository, DrizzleNotificationsRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzlePostRepository, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleUnitOfWork, OUTBOX_MAX_ATTEMPTS, authorProfileColumns, createDb, decryptSecret, deepMerge, encryptSecret, migrationsFolder, orgUserProfileColumns, runMigrations, schema_exports as schema, studentFirstName, studentLastName, studentProfileColumns };
|
|
29
|
+
export { DrizzleAdminRepository, DrizzleAssetsRepository, DrizzleAutomationRunsRepository, DrizzleAutomationsRepository, DrizzleChatRepository, DrizzleConnectionsRepository, DrizzleContentRepository, DrizzleCredentialStore, DrizzleDevicesRepository, DrizzleDigestRunRepository, DrizzleDiscussionRepository, DrizzleEntitlementsRepository, DrizzleIdentityRepository, DrizzleMemberRepository, DrizzleMembersRepository, DrizzleMentionsRepository, DrizzleNotificationSettingsRepository, DrizzleNotificationsRepository, DrizzleOrganizationsRepository, DrizzleOutboxAppender, DrizzleOutboxStore, DrizzlePostRepository, DrizzleProgressRepository, DrizzleSettingsRepository, DrizzleSubscriptionsRepository, DrizzleUnitOfWork, OUTBOX_MAX_ATTEMPTS, authorProfileColumns, createDb, decryptSecret, deepMerge, encryptSecret, migrationsFolder, orgUserProfileColumns, runMigrations, schema_exports as schema, studentFirstName, studentLastName, studentProfileColumns };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { communities, courses, spaces } from "../schema/content.js";
|
|
2
|
-
import { commentFlags, comments, messageFlags, messages, postFlags, posts } from "../schema/discussion.js";
|
|
3
2
|
import { users } from "../schema/identity.js";
|
|
4
3
|
import { orgUsers } from "../schema/organizations.js";
|
|
5
4
|
import { contentAccess, entitlements } from "../schema/entitlements.js";
|
|
5
|
+
import { commentFlags, comments, messageFlags, messages, postFlags, posts } from "../schema/discussion.js";
|
|
6
6
|
import "../schema/index.js";
|
|
7
7
|
import { translateDbErrors } from "./pg-errors.js";
|
|
8
8
|
import { user } from "../schema/better-auth.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { DbExecutor } from "../client.js";
|
|
2
|
-
import { AssetsRepository } from "@headless-lms/core/assets";
|
|
3
2
|
import { Asset, AssetsQuery, Logger, Page } from "@headless-lms/core/types";
|
|
3
|
+
import { AssetsRepository } from "@headless-lms/core/assets";
|
|
4
4
|
//#region src/repositories/assets.d.ts
|
|
5
5
|
declare class DrizzleAssetsRepository implements AssetsRepository {
|
|
6
6
|
private readonly db;
|
|
@@ -12,7 +12,6 @@ declare class DrizzleAssetsRepository implements AssetsRepository {
|
|
|
12
12
|
findByIds(orgId: string, ids: string[]): Promise<Asset[]>;
|
|
13
13
|
markReady(orgId: string, id: string, patch: Pick<Asset, "key" | "kind" | "size" | "contentType">): Promise<Asset | null>;
|
|
14
14
|
delete(orgId: string, id: string): Promise<boolean>;
|
|
15
|
-
hasUnreferencedMemberUploads(orgId: string, before: Date): Promise<boolean>;
|
|
16
15
|
listUnreferencedMemberUploads(orgId: string, before: Date): Promise<Asset[]>;
|
|
17
16
|
heldKeys(orgId: string, keys: string[]): Promise<string[]>;
|
|
18
17
|
}
|
|
@@ -115,10 +115,6 @@ var DrizzleAssetsRepository = class {
|
|
|
115
115
|
throw err;
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
async hasUnreferencedMemberUploads(orgId, before) {
|
|
119
|
-
const [row] = await this.db.select({ id: assets.id }).from(assets).where(unreferencedMemberUploads(orgId, before)).limit(1);
|
|
120
|
-
return row !== void 0;
|
|
121
|
-
}
|
|
122
118
|
async listUnreferencedMemberUploads(orgId, before) {
|
|
123
119
|
return (await this.db.select().from(assets).where(unreferencedMemberUploads(orgId, before)).orderBy(asc(assets.createdAt))).map(toAsset);
|
|
124
120
|
}
|
|
@@ -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 hasUnreferencedMemberUploads(orgId: string, before: Date): Promise<boolean> {\n const [row] = await this.db\n .select({ id: assets.id })\n .from(assets)\n .where(unreferencedMemberUploads(orgId, before))\n .limit(1);\n return row !== undefined;\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 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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"}
|
|
@@ -8,7 +8,6 @@ declare class DrizzleAutomationsRepository implements AutomationsRepository {
|
|
|
8
8
|
private readonly logger;
|
|
9
9
|
constructor(db: DbExecutor, logger?: Logger);
|
|
10
10
|
insert(orgId: string, input: NewAutomation): Promise<Automation>;
|
|
11
|
-
insertSeed(orgId: string, input: NewAutomation): Promise<Automation | null>;
|
|
12
11
|
update(orgId: string, id: string, patch: UpdateAutomationInput): Promise<Automation | null>;
|
|
13
12
|
delete(orgId: string, id: string): Promise<Automation | null>;
|
|
14
13
|
findById(orgId: string, id: string): Promise<Automation | null>;
|
|
@@ -14,7 +14,6 @@ const automationSelection = {
|
|
|
14
14
|
actions: automations.actions,
|
|
15
15
|
enabled: automations.enabled,
|
|
16
16
|
filter: automations.filter,
|
|
17
|
-
source: automations.source,
|
|
18
17
|
createdAt: automations.createdAt,
|
|
19
18
|
updatedAt: automations.updatedAt
|
|
20
19
|
};
|
|
@@ -29,7 +28,6 @@ function toAutomation(row) {
|
|
|
29
28
|
actions: row.actions,
|
|
30
29
|
enabled: row.enabled,
|
|
31
30
|
filter: row.filter,
|
|
32
|
-
source: row.source,
|
|
33
31
|
createdAt: row.createdAt,
|
|
34
32
|
updatedAt: row.updatedAt
|
|
35
33
|
};
|
|
@@ -49,27 +47,13 @@ var DrizzleAutomationsRepository = class {
|
|
|
49
47
|
description: input.description,
|
|
50
48
|
triggers: input.triggers,
|
|
51
49
|
actions: input.actions,
|
|
52
|
-
filter: input.filter ?? null
|
|
53
|
-
source: input.source ?? "user"
|
|
50
|
+
filter: input.filter ?? null
|
|
54
51
|
}).returning({ id: automations.id });
|
|
55
52
|
if (!inserted) throw new Error("failed to insert automation");
|
|
56
53
|
const created = await this.findById(orgId, inserted.id);
|
|
57
54
|
if (!created) throw new Error("failed to load created automation");
|
|
58
55
|
return created;
|
|
59
56
|
}
|
|
60
|
-
async insertSeed(orgId, input) {
|
|
61
|
-
const [inserted] = await this.db.insert(automations).values({
|
|
62
|
-
orgId,
|
|
63
|
-
name: input.name,
|
|
64
|
-
kind: input.kind,
|
|
65
|
-
description: input.description,
|
|
66
|
-
triggers: input.triggers,
|
|
67
|
-
actions: input.actions,
|
|
68
|
-
filter: input.filter ?? null,
|
|
69
|
-
source: "system"
|
|
70
|
-
}).onConflictDoNothing().returning({ id: automations.id });
|
|
71
|
-
return inserted ? this.findById(orgId, inserted.id) : null;
|
|
72
|
-
}
|
|
73
57
|
async update(orgId, id, patch) {
|
|
74
58
|
const set = { updatedAt: /* @__PURE__ */ new Date() };
|
|
75
59
|
if (patch.name !== void 0) set.name = patch.name;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automations.js","names":[],"sources":["../../src/repositories/automations.ts"],"sourcesContent":["// automations — Drizzle repositories (implement the core outbound ports).\n// Org-scoped: every method takes the domain `organizations.id` and constrains\n// its queries to that tenant. `Automation`/`AutomationRun` carry no `orgId`\n// column of their own in the domain type (mirrors the `Entitlement` shape) —\n// `AutomationRun` is the one exception, since it's the run's own `orgId`\n// field the service reads back off `handle`'s event.\nimport { and, arrayContains, asc, desc, eq, gte, ne, sql, type SQL } from \"drizzle-orm\";\nimport type { DbExecutor } from \"../client.js\";\nimport type {\n Automation,\n AutomationKind,\n AutomationRun,\n AutomationRunsQuery,\n AutomationRunsRepository,\n AutomationsRepository,\n NewAutomation,\n NewAutomationRun,\n Page,\n UpdateAutomationInput,\n} from \"@headless-lms/core/automations\";\nimport type { ScheduledAutomation } from \"@headless-lms/core/types\";\nimport { automations, automationRuns } from \"../schema/index.js\";\nimport type { Logger } from \"@headless-lms/core/shared/ports\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport { translateDbErrors } from \"./pg-errors.js\";\n\nconst automationSelection = {\n orgId: automations.orgId,\n id: automations.id,\n name: automations.name,\n kind: automations.kind,\n description: automations.description,\n triggers: automations.triggers,\n actions: automations.actions,\n enabled: automations.enabled,\n filter: automations.filter,\n source: automations.source,\n createdAt: automations.createdAt,\n updatedAt: automations.updatedAt,\n};\n\ninterface AutomationRow {\n orgId: string;\n id: string;\n name: string;\n kind: AutomationKind;\n description: string | null;\n triggers: string[];\n actions: Automation[\"actions\"];\n enabled: boolean;\n filter: Automation[\"filter\"];\n source: Automation[\"source\"];\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction toAutomation(row: AutomationRow): Automation {\n return {\n orgId: row.orgId,\n id: row.id,\n name: row.name,\n kind: row.kind,\n description: row.description,\n triggers: row.triggers,\n actions: row.actions,\n enabled: row.enabled,\n filter: row.filter,\n source: row.source,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport class DrizzleAutomationsRepository implements AutomationsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, input: NewAutomation): Promise<Automation> {\n const [inserted] = await this.db\n .insert(automations)\n .values({\n orgId,\n name: input.name,\n kind: input.kind,\n description: input.description,\n triggers: input.triggers,\n actions: input.actions,\n filter: input.filter ?? null,\n source: input.source ?? \"user\",\n })\n .returning({ id: automations.id });\n if (!inserted) {\n throw new Error(\"failed to insert automation\");\n }\n const created = await this.findById(orgId, inserted.id);\n if (!created) {\n throw new Error(\"failed to load created automation\");\n }\n return created;\n }\n\n async insertSeed(orgId: string, input: NewAutomation): Promise<Automation | null> {\n const [inserted] = await this.db\n .insert(automations)\n .values({\n orgId,\n name: input.name,\n kind: input.kind,\n description: input.description,\n triggers: input.triggers,\n actions: input.actions,\n filter: input.filter ?? null,\n source: \"system\",\n })\n .onConflictDoNothing()\n .returning({ id: automations.id });\n return inserted ? this.findById(orgId, inserted.id) : null;\n }\n\n async update(\n orgId: string,\n id: string,\n patch: UpdateAutomationInput,\n ): Promise<Automation | null> {\n const set: Record<string, unknown> = { updatedAt: new Date() };\n if (patch.name !== undefined) {\n set.name = patch.name;\n }\n if (patch.description !== undefined) {\n set.description = patch.description;\n }\n if (patch.triggers !== undefined) {\n set.triggers = patch.triggers;\n }\n if (patch.actions !== undefined) {\n set.actions = patch.actions;\n }\n if (patch.enabled !== undefined) {\n set.enabled = patch.enabled;\n }\n if (patch.filter !== undefined) {\n set.filter = patch.filter;\n }\n\n const [updated] = await this.db\n .update(automations)\n .set(set)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .returning({ id: automations.id });\n if (!updated) {\n return null;\n }\n return this.findById(orgId, id);\n }\n\n async delete(orgId: string, id: string): Promise<Automation | null> {\n const [deleted] = await this.db\n .delete(automations)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .returning(automationSelection);\n return deleted ? toAutomation(deleted) : null;\n }\n\n async findById(orgId: string, id: string): Promise<Automation | null> {\n const [row] = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .limit(1);\n return row ? toAutomation(row) : null;\n }\n\n async listByOrg(orgId: string, kind?: AutomationKind): Promise<Automation[]> {\n const conditions: SQL[] = [eq(automations.orgId, orgId)];\n if (kind) {\n conditions.push(eq(automations.kind, kind));\n }\n const rows = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(...conditions))\n .orderBy(desc(automations.createdAt));\n return rows.map(toAutomation);\n }\n\n async listByTrigger(orgId: string, trigger: string): Promise<Automation[]> {\n const rows = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(eq(automations.orgId, orgId), arrayContains(automations.triggers, [trigger])))\n .orderBy(desc(automations.createdAt));\n return rows.map(toAutomation);\n }\n\n async listSchedules(): Promise<ScheduledAutomation[]> {\n const rows = await this.db\n .select({\n orgId: automations.orgId,\n id: automations.id,\n triggers: automations.triggers,\n })\n .from(automations)\n .where(\n and(\n eq(automations.enabled, true),\n sql`exists (select 1 from unnest(${automations.triggers}) as t(trigger) where t.trigger like 'schedule:%')`,\n ),\n );\n const schedules: ScheduledAutomation[] = [];\n for (const row of rows) {\n for (const trigger of row.triggers) {\n if (!trigger.startsWith(\"schedule:\")) {\n continue;\n }\n schedules.push({\n orgId: row.orgId,\n automationId: row.id,\n cron: trigger.slice(\"schedule:\".length),\n });\n }\n }\n return schedules;\n }\n}\n\nconst runSelection = {\n id: automationRuns.id,\n orgId: automationRuns.orgId,\n automationId: automationRuns.automationId,\n trigger: automationRuns.trigger,\n eventId: automationRuns.eventId,\n event: automationRuns.event,\n status: automationRuns.status,\n actionResults: automationRuns.actionResults,\n startedAt: automationRuns.startedAt,\n finishedAt: automationRuns.finishedAt,\n createdAt: automationRuns.createdAt,\n updatedAt: automationRuns.updatedAt,\n};\n\ninterface RunRow {\n id: string;\n orgId: string;\n automationId: string;\n trigger: string;\n eventId: string;\n event: AutomationRun[\"event\"];\n status: AutomationRun[\"status\"];\n actionResults: AutomationRun[\"actionResults\"];\n startedAt: Date;\n finishedAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction toAutomationRun(row: RunRow): AutomationRun {\n return {\n id: row.id,\n orgId: row.orgId,\n automationId: row.automationId,\n trigger: row.trigger,\n eventId: row.eventId,\n event: row.event,\n status: row.status,\n actionResults: row.actionResults,\n startedAt: row.startedAt,\n finishedAt: row.finishedAt,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\n// Sortable columns by the client-facing field name. `-field` = desc.\nconst sortColumns = {\n startedAt: automationRuns.startedAt,\n finishedAt: automationRuns.finishedAt,\n status: automationRuns.status,\n} as const;\n\nexport class DrizzleAutomationRunsRepository implements AutomationRunsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, run: NewAutomationRun): Promise<AutomationRun | null> {\n const [inserted] = await this.db\n .insert(automationRuns)\n .values({\n orgId,\n automationId: run.automationId,\n trigger: run.trigger,\n eventId: run.event.id,\n event: run.event,\n status: run.status,\n actionResults: run.actionResults,\n startedAt: run.startedAt,\n finishedAt: run.finishedAt,\n })\n // A redelivered trigger event hits the unique (org, automation, event) index; no row comes back.\n .onConflictDoNothing({\n target: [automationRuns.orgId, automationRuns.automationId, automationRuns.eventId],\n })\n .returning(runSelection);\n return inserted ? toAutomationRun(inserted) : null;\n }\n\n async findById(orgId: string, id: string): Promise<AutomationRun | null> {\n const [row] = await this.db\n .select(runSelection)\n .from(automationRuns)\n .where(and(eq(automationRuns.orgId, orgId), eq(automationRuns.id, id)))\n .limit(1);\n return row ? toAutomationRun(row) : null;\n }\n\n async restart(orgId: string, id: string, startedAt: Date): Promise<AutomationRun | null> {\n const [restarted] = await this.db\n .update(automationRuns)\n .set({ status: \"running\", actionResults: [], startedAt, finishedAt: null })\n .where(\n and(\n eq(automationRuns.orgId, orgId),\n eq(automationRuns.id, id),\n ne(automationRuns.status, \"running\"),\n ),\n )\n .returning(runSelection);\n return restarted ? toAutomationRun(restarted) : null;\n }\n\n async recordOutcome(\n orgId: string,\n id: string,\n outcome: {\n status: AutomationRun[\"status\"];\n actionResults: AutomationRun[\"actionResults\"];\n finishedAt: Date;\n },\n ): Promise<AutomationRun | null> {\n const [updated] = await this.db\n .update(automationRuns)\n .set({\n status: outcome.status,\n actionResults: outcome.actionResults,\n finishedAt: outcome.finishedAt,\n })\n .where(and(eq(automationRuns.orgId, orgId), eq(automationRuns.id, id)))\n .returning(runSelection);\n return updated ? toAutomationRun(updated) : null;\n }\n\n async list(\n orgId: string,\n automationId: string,\n query: AutomationRunsQuery,\n ): Promise<Page<AutomationRun>> {\n const conditions: SQL[] = [\n eq(automationRuns.orgId, orgId),\n eq(automationRuns.automationId, automationId),\n ];\n if (query.status) {\n conditions.push(eq(automationRuns.status, query.status));\n }\n const where = and(...conditions);\n\n // Sort: `-field` for descending; default to most-recently started first.\n let orderBy: SQL;\n if (query.sort) {\n const isDesc = query.sort.startsWith(\"-\");\n const field = (isDesc ? query.sort.slice(1) : query.sort) as keyof typeof sortColumns;\n const col = sortColumns[field] ?? automationRuns.startedAt;\n orderBy = isDesc ? desc(col) : asc(col);\n } else {\n orderBy = desc(automationRuns.startedAt);\n }\n\n const offset = (query.page - 1) * query.pageSize;\n\n const rows = await this.db\n .select(runSelection)\n .from(automationRuns)\n .where(where)\n .orderBy(orderBy)\n .limit(query.pageSize)\n .offset(offset);\n\n const [{ total } = { total: 0 }] = await this.db\n .select({ total: sql<number>`cast(count(*) as int)` })\n .from(automationRuns)\n .where(where);\n\n return {\n rows: rows.map(toAutomationRun),\n total,\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async recentRecipients(\n orgId: string,\n automationId: string,\n chatId: string,\n since: Date,\n ): Promise<string[]> {\n const rows = await this.db\n .select({\n actionResults: automationRuns.actionResults,\n })\n .from(automationRuns)\n .where(\n and(\n eq(automationRuns.orgId, orgId),\n eq(automationRuns.automationId, automationId),\n eq(automationRuns.status, \"completed\"),\n gte(automationRuns.finishedAt, since),\n sql`${automationRuns.event}->'data'->>'chatId' = ${chatId}`,\n ),\n );\n const ids: string[] = [];\n for (const row of rows) {\n for (const result of row.actionResults) {\n if (result.recipients) {\n ids.push(...result.recipients);\n }\n }\n }\n return [...new Set(ids)];\n }\n}\ntranslateDbErrors(DrizzleAutomationsRepository);\ntranslateDbErrors(DrizzleAutomationRunsRepository);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"automations.js","names":[],"sources":["../../src/repositories/automations.ts"],"sourcesContent":["// automations — Drizzle repositories (implement the core outbound ports).\n// Org-scoped: every method takes the domain `organizations.id` and constrains\n// its queries to that tenant. `Automation`/`AutomationRun` carry no `orgId`\n// column of their own in the domain type (mirrors the `Entitlement` shape) —\n// `AutomationRun` is the one exception, since it's the run's own `orgId`\n// field the service reads back off `handle`'s event.\nimport { and, arrayContains, asc, desc, eq, gte, ne, sql, type SQL } from \"drizzle-orm\";\nimport type { DbExecutor } from \"../client.js\";\nimport type {\n Automation,\n AutomationKind,\n AutomationRun,\n AutomationRunsQuery,\n AutomationRunsRepository,\n AutomationsRepository,\n NewAutomation,\n NewAutomationRun,\n Page,\n UpdateAutomationInput,\n} from \"@headless-lms/core/automations\";\nimport type { ScheduledAutomation } from \"@headless-lms/core/types\";\nimport { automations, automationRuns } from \"../schema/index.js\";\nimport type { Logger } from \"@headless-lms/core/shared/ports\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport { translateDbErrors } from \"./pg-errors.js\";\n\nconst automationSelection = {\n orgId: automations.orgId,\n id: automations.id,\n name: automations.name,\n kind: automations.kind,\n description: automations.description,\n triggers: automations.triggers,\n actions: automations.actions,\n enabled: automations.enabled,\n filter: automations.filter,\n createdAt: automations.createdAt,\n updatedAt: automations.updatedAt,\n};\n\ninterface AutomationRow {\n orgId: string;\n id: string;\n name: string;\n kind: AutomationKind;\n description: string | null;\n triggers: string[];\n actions: Automation[\"actions\"];\n enabled: boolean;\n filter: Automation[\"filter\"];\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction toAutomation(row: AutomationRow): Automation {\n return {\n orgId: row.orgId,\n id: row.id,\n name: row.name,\n kind: row.kind,\n description: row.description,\n triggers: row.triggers,\n actions: row.actions,\n enabled: row.enabled,\n filter: row.filter,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport class DrizzleAutomationsRepository implements AutomationsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, input: NewAutomation): Promise<Automation> {\n const [inserted] = await this.db\n .insert(automations)\n .values({\n orgId,\n name: input.name,\n kind: input.kind,\n description: input.description,\n triggers: input.triggers,\n actions: input.actions,\n filter: input.filter ?? null,\n })\n .returning({ id: automations.id });\n if (!inserted) {\n throw new Error(\"failed to insert automation\");\n }\n const created = await this.findById(orgId, inserted.id);\n if (!created) {\n throw new Error(\"failed to load created automation\");\n }\n return created;\n }\n\n async update(\n orgId: string,\n id: string,\n patch: UpdateAutomationInput,\n ): Promise<Automation | null> {\n const set: Record<string, unknown> = { updatedAt: new Date() };\n if (patch.name !== undefined) {\n set.name = patch.name;\n }\n if (patch.description !== undefined) {\n set.description = patch.description;\n }\n if (patch.triggers !== undefined) {\n set.triggers = patch.triggers;\n }\n if (patch.actions !== undefined) {\n set.actions = patch.actions;\n }\n if (patch.enabled !== undefined) {\n set.enabled = patch.enabled;\n }\n if (patch.filter !== undefined) {\n set.filter = patch.filter;\n }\n\n const [updated] = await this.db\n .update(automations)\n .set(set)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .returning({ id: automations.id });\n if (!updated) {\n return null;\n }\n return this.findById(orgId, id);\n }\n\n async delete(orgId: string, id: string): Promise<Automation | null> {\n const [deleted] = await this.db\n .delete(automations)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .returning(automationSelection);\n return deleted ? toAutomation(deleted) : null;\n }\n\n async findById(orgId: string, id: string): Promise<Automation | null> {\n const [row] = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(eq(automations.orgId, orgId), eq(automations.id, id)))\n .limit(1);\n return row ? toAutomation(row) : null;\n }\n\n async listByOrg(orgId: string, kind?: AutomationKind): Promise<Automation[]> {\n const conditions: SQL[] = [eq(automations.orgId, orgId)];\n if (kind) {\n conditions.push(eq(automations.kind, kind));\n }\n const rows = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(...conditions))\n .orderBy(desc(automations.createdAt));\n return rows.map(toAutomation);\n }\n\n async listByTrigger(orgId: string, trigger: string): Promise<Automation[]> {\n const rows = await this.db\n .select(automationSelection)\n .from(automations)\n .where(and(eq(automations.orgId, orgId), arrayContains(automations.triggers, [trigger])))\n .orderBy(desc(automations.createdAt));\n return rows.map(toAutomation);\n }\n\n async listSchedules(): Promise<ScheduledAutomation[]> {\n const rows = await this.db\n .select({\n orgId: automations.orgId,\n id: automations.id,\n triggers: automations.triggers,\n })\n .from(automations)\n .where(\n and(\n eq(automations.enabled, true),\n sql`exists (select 1 from unnest(${automations.triggers}) as t(trigger) where t.trigger like 'schedule:%')`,\n ),\n );\n const schedules: ScheduledAutomation[] = [];\n for (const row of rows) {\n for (const trigger of row.triggers) {\n if (!trigger.startsWith(\"schedule:\")) {\n continue;\n }\n schedules.push({\n orgId: row.orgId,\n automationId: row.id,\n cron: trigger.slice(\"schedule:\".length),\n });\n }\n }\n return schedules;\n }\n}\n\nconst runSelection = {\n id: automationRuns.id,\n orgId: automationRuns.orgId,\n automationId: automationRuns.automationId,\n trigger: automationRuns.trigger,\n eventId: automationRuns.eventId,\n event: automationRuns.event,\n status: automationRuns.status,\n actionResults: automationRuns.actionResults,\n startedAt: automationRuns.startedAt,\n finishedAt: automationRuns.finishedAt,\n createdAt: automationRuns.createdAt,\n updatedAt: automationRuns.updatedAt,\n};\n\ninterface RunRow {\n id: string;\n orgId: string;\n automationId: string;\n trigger: string;\n eventId: string;\n event: AutomationRun[\"event\"];\n status: AutomationRun[\"status\"];\n actionResults: AutomationRun[\"actionResults\"];\n startedAt: Date;\n finishedAt: Date | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nfunction toAutomationRun(row: RunRow): AutomationRun {\n return {\n id: row.id,\n orgId: row.orgId,\n automationId: row.automationId,\n trigger: row.trigger,\n eventId: row.eventId,\n event: row.event,\n status: row.status,\n actionResults: row.actionResults,\n startedAt: row.startedAt,\n finishedAt: row.finishedAt,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\n// Sortable columns by the client-facing field name. `-field` = desc.\nconst sortColumns = {\n startedAt: automationRuns.startedAt,\n finishedAt: automationRuns.finishedAt,\n status: automationRuns.status,\n} as const;\n\nexport class DrizzleAutomationRunsRepository implements AutomationRunsRepository {\n constructor(\n private readonly db: DbExecutor,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, run: NewAutomationRun): Promise<AutomationRun | null> {\n const [inserted] = await this.db\n .insert(automationRuns)\n .values({\n orgId,\n automationId: run.automationId,\n trigger: run.trigger,\n eventId: run.event.id,\n event: run.event,\n status: run.status,\n actionResults: run.actionResults,\n startedAt: run.startedAt,\n finishedAt: run.finishedAt,\n })\n // A redelivered trigger event hits the unique (org, automation, event) index; no row comes back.\n .onConflictDoNothing({\n target: [automationRuns.orgId, automationRuns.automationId, automationRuns.eventId],\n })\n .returning(runSelection);\n return inserted ? toAutomationRun(inserted) : null;\n }\n\n async findById(orgId: string, id: string): Promise<AutomationRun | null> {\n const [row] = await this.db\n .select(runSelection)\n .from(automationRuns)\n .where(and(eq(automationRuns.orgId, orgId), eq(automationRuns.id, id)))\n .limit(1);\n return row ? toAutomationRun(row) : null;\n }\n\n async restart(orgId: string, id: string, startedAt: Date): Promise<AutomationRun | null> {\n const [restarted] = await this.db\n .update(automationRuns)\n .set({ status: \"running\", actionResults: [], startedAt, finishedAt: null })\n .where(\n and(\n eq(automationRuns.orgId, orgId),\n eq(automationRuns.id, id),\n ne(automationRuns.status, \"running\"),\n ),\n )\n .returning(runSelection);\n return restarted ? toAutomationRun(restarted) : null;\n }\n\n async recordOutcome(\n orgId: string,\n id: string,\n outcome: {\n status: AutomationRun[\"status\"];\n actionResults: AutomationRun[\"actionResults\"];\n finishedAt: Date;\n },\n ): Promise<AutomationRun | null> {\n const [updated] = await this.db\n .update(automationRuns)\n .set({\n status: outcome.status,\n actionResults: outcome.actionResults,\n finishedAt: outcome.finishedAt,\n })\n .where(and(eq(automationRuns.orgId, orgId), eq(automationRuns.id, id)))\n .returning(runSelection);\n return updated ? toAutomationRun(updated) : null;\n }\n\n async list(\n orgId: string,\n automationId: string,\n query: AutomationRunsQuery,\n ): Promise<Page<AutomationRun>> {\n const conditions: SQL[] = [\n eq(automationRuns.orgId, orgId),\n eq(automationRuns.automationId, automationId),\n ];\n if (query.status) {\n conditions.push(eq(automationRuns.status, query.status));\n }\n const where = and(...conditions);\n\n // Sort: `-field` for descending; default to most-recently started first.\n let orderBy: SQL;\n if (query.sort) {\n const isDesc = query.sort.startsWith(\"-\");\n const field = (isDesc ? query.sort.slice(1) : query.sort) as keyof typeof sortColumns;\n const col = sortColumns[field] ?? automationRuns.startedAt;\n orderBy = isDesc ? desc(col) : asc(col);\n } else {\n orderBy = desc(automationRuns.startedAt);\n }\n\n const offset = (query.page - 1) * query.pageSize;\n\n const rows = await this.db\n .select(runSelection)\n .from(automationRuns)\n .where(where)\n .orderBy(orderBy)\n .limit(query.pageSize)\n .offset(offset);\n\n const [{ total } = { total: 0 }] = await this.db\n .select({ total: sql<number>`cast(count(*) as int)` })\n .from(automationRuns)\n .where(where);\n\n return {\n rows: rows.map(toAutomationRun),\n total,\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async recentRecipients(\n orgId: string,\n automationId: string,\n chatId: string,\n since: Date,\n ): Promise<string[]> {\n const rows = await this.db\n .select({\n actionResults: automationRuns.actionResults,\n })\n .from(automationRuns)\n .where(\n and(\n eq(automationRuns.orgId, orgId),\n eq(automationRuns.automationId, automationId),\n eq(automationRuns.status, \"completed\"),\n gte(automationRuns.finishedAt, since),\n sql`${automationRuns.event}->'data'->>'chatId' = ${chatId}`,\n ),\n );\n const ids: string[] = [];\n for (const row of rows) {\n for (const result of row.actionResults) {\n if (result.recipients) {\n ids.push(...result.recipients);\n }\n }\n }\n return [...new Set(ids)];\n }\n}\ntranslateDbErrors(DrizzleAutomationsRepository);\ntranslateDbErrors(DrizzleAutomationRunsRepository);\n"],"mappings":"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"}
|
|
@@ -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 {
|
|
3
|
+
import { ChatInboxRow, ChatRepository, ChatUnread, FeedQuery, MessageReactions, PageQuery } 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,19 +11,19 @@ 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: PageQuery): Promise<Page<
|
|
14
|
+
listForUser(orgId: string, communityId: string, orgUserId: string, query: PageQuery): Promise<Page<ChatInboxRow>>;
|
|
15
15
|
participants(orgId: string, chatId: string): Promise<ChatUser[]>;
|
|
16
16
|
join(orgId: string, user: ChatUser): Promise<void>;
|
|
17
17
|
markLeft(orgId: string, chatId: string, orgUserId: string, leftAt: Date): Promise<void>;
|
|
18
18
|
setRead(orgId: string, chatId: string, orgUserId: string, messageId: string): Promise<void>;
|
|
19
|
-
|
|
19
|
+
unreadFor(orgId: string, chatId: string, orgUserId: string): Promise<ChatUnread>;
|
|
20
20
|
insertMessage(orgId: string, message: Message): Promise<Message>;
|
|
21
21
|
updateMessage(orgId: string, id: string, patch: Partial<Pick<Message, "body" | "editedAt" | "deletedAt">>): Promise<Message | null>;
|
|
22
22
|
findMessage(orgId: string, id: string): Promise<Message | null>;
|
|
23
23
|
listMessages(orgId: string, chatId: string, query: Pick<FeedQuery, "page" | "pageSize">): Promise<Page<Message>>;
|
|
24
24
|
attach(orgId: string, communityId: string, messageId: string, assetIds: string[]): Promise<void>;
|
|
25
25
|
attachmentsOf(orgId: string, messageIds: string[]): Promise<Record<string, string[]>>;
|
|
26
|
-
setReaction(orgId: string, communityId: string, messageId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<
|
|
26
|
+
setReaction(orgId: string, communityId: string, messageId: string, orgUserId: string, emoji: ReactionEmoji | null): Promise<boolean>;
|
|
27
27
|
reactionsOf(orgId: string, messageIds: string[], viewerId: string): Promise<Record<string, MessageReactions>>;
|
|
28
28
|
block(orgId: string, communityId: string, blockerOrgUserId: string, blockedOrgUserId: string): Promise<void>;
|
|
29
29
|
unblock(orgId: string, communityId: string, blockerOrgUserId: string, blockedOrgUserId: string): Promise<void>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { blockedUsers, chatUsers, chats, messageAttachments, messageFlags, messageReactions, messages } from "../schema/discussion.js";
|
|
2
2
|
import "../schema/index.js";
|
|
3
3
|
import { translateDbErrors } from "./pg-errors.js";
|
|
4
|
-
import {
|
|
4
|
+
import { insertedRow } from "./inserted-row.js";
|
|
5
|
+
import { and, asc, desc, eq, gt, inArray, isNull, ne, or, sql } from "drizzle-orm";
|
|
5
6
|
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
6
7
|
//#region src/repositories/chats.ts
|
|
7
8
|
function toChat(row) {
|
|
@@ -27,8 +28,6 @@ function toChatUser(row) {
|
|
|
27
28
|
joinedAt: row.joinedAt,
|
|
28
29
|
leftAt: row.leftAt,
|
|
29
30
|
lastReadMessageId: row.lastReadMessageId,
|
|
30
|
-
muted: row.muted,
|
|
31
|
-
mutedUntil: row.mutedUntil,
|
|
32
31
|
createdAt: row.createdAt,
|
|
33
32
|
updatedAt: row.updatedAt
|
|
34
33
|
};
|
|
@@ -133,6 +132,7 @@ var DrizzleChatRepository = class {
|
|
|
133
132
|
where m.org_id = ${chats.orgId}
|
|
134
133
|
and m.chat_id = ${chats.id}
|
|
135
134
|
and m.org_user_id <> ${orgUserId}
|
|
135
|
+
and m.deleted_at is null
|
|
136
136
|
and (
|
|
137
137
|
${chatUsers.lastReadMessageId} is null
|
|
138
138
|
or m.created_at > coalesce((
|
|
@@ -154,8 +154,6 @@ var DrizzleChatRepository = class {
|
|
|
154
154
|
const rows = await this.db.select({
|
|
155
155
|
chat: chats,
|
|
156
156
|
lastReadMessageId: chatUsers.lastReadMessageId,
|
|
157
|
-
muted: chatUsers.muted,
|
|
158
|
-
mutedUntil: chatUsers.mutedUntil,
|
|
159
157
|
unread,
|
|
160
158
|
lastId: latest.id,
|
|
161
159
|
lastOrgUserId: latest.orgUserId,
|
|
@@ -185,8 +183,6 @@ var DrizzleChatRepository = class {
|
|
|
185
183
|
chat: toChat(row.chat),
|
|
186
184
|
participantIds: (byChat.get(row.chat.id) ?? []).sort(),
|
|
187
185
|
lastReadMessageId: row.lastReadMessageId,
|
|
188
|
-
muted: row.muted,
|
|
189
|
-
mutedUntil: row.mutedUntil,
|
|
190
186
|
unread: row.unread,
|
|
191
187
|
lastMessage: row.lastId !== null && row.lastOrgUserId !== null && row.lastCreatedAt !== null ? {
|
|
192
188
|
id: row.lastId,
|
|
@@ -211,8 +207,6 @@ var DrizzleChatRepository = class {
|
|
|
211
207
|
joinedAt: user.joinedAt,
|
|
212
208
|
leftAt: user.leftAt,
|
|
213
209
|
lastReadMessageId: user.lastReadMessageId,
|
|
214
|
-
muted: user.muted,
|
|
215
|
-
mutedUntil: user.mutedUntil,
|
|
216
210
|
createdAt: user.createdAt,
|
|
217
211
|
updatedAt: user.updatedAt
|
|
218
212
|
}).onConflictDoUpdate({
|
|
@@ -241,12 +235,32 @@ var DrizzleChatRepository = class {
|
|
|
241
235
|
updatedAt: /* @__PURE__ */ new Date()
|
|
242
236
|
}).where(and(eq(chatUsers.orgId, orgId), eq(chatUsers.chatId, chatId), eq(chatUsers.orgUserId, orgUserId)));
|
|
243
237
|
}
|
|
244
|
-
async
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
238
|
+
async unreadFor(orgId, chatId, orgUserId) {
|
|
239
|
+
const readUpTo = sql`coalesce((
|
|
240
|
+
select lr.created_at
|
|
241
|
+
from chat_users cu
|
|
242
|
+
join messages lr on lr.org_id = cu.org_id and lr.id = cu.last_read_message_id
|
|
243
|
+
where cu.org_id = ${orgId}
|
|
244
|
+
and cu.chat_id = ${chatId}
|
|
245
|
+
and cu.org_user_id = ${orgUserId}
|
|
246
|
+
), '-infinity'::timestamptz)`;
|
|
247
|
+
const [row] = await this.db.select({
|
|
248
|
+
count: sql`cast(count(*) over () as int)`,
|
|
249
|
+
id: messages.id,
|
|
250
|
+
orgUserId: messages.orgUserId,
|
|
251
|
+
body: messages.body
|
|
252
|
+
}).from(messages).where(and(eq(messages.orgId, orgId), eq(messages.chatId, chatId), ne(messages.orgUserId, orgUserId), isNull(messages.deletedAt), gt(messages.createdAt, readUpTo))).orderBy(desc(messages.createdAt), desc(messages.id)).limit(1);
|
|
253
|
+
return row ? {
|
|
254
|
+
count: row.count,
|
|
255
|
+
latest: {
|
|
256
|
+
messageId: row.id,
|
|
257
|
+
senderOrgUserId: row.orgUserId,
|
|
258
|
+
body: row.body
|
|
259
|
+
}
|
|
260
|
+
} : {
|
|
261
|
+
count: 0,
|
|
262
|
+
latest: null
|
|
263
|
+
};
|
|
250
264
|
}
|
|
251
265
|
async insertMessage(orgId, message) {
|
|
252
266
|
const [row] = await this.db.insert(messages).values({
|
|
@@ -316,9 +330,9 @@ var DrizzleChatRepository = class {
|
|
|
316
330
|
async setReaction(orgId, communityId, messageId, orgUserId, emoji) {
|
|
317
331
|
if (emoji === null) {
|
|
318
332
|
await this.db.delete(messageReactions).where(and(eq(messageReactions.orgId, orgId), eq(messageReactions.messageId, messageId), eq(messageReactions.orgUserId, orgUserId)));
|
|
319
|
-
return;
|
|
333
|
+
return false;
|
|
320
334
|
}
|
|
321
|
-
await this.db.insert(messageReactions).values({
|
|
335
|
+
const [row] = await this.db.insert(messageReactions).values({
|
|
322
336
|
orgId,
|
|
323
337
|
communityId,
|
|
324
338
|
messageId,
|
|
@@ -334,7 +348,8 @@ var DrizzleChatRepository = class {
|
|
|
334
348
|
emoji,
|
|
335
349
|
updatedAt: /* @__PURE__ */ new Date()
|
|
336
350
|
}
|
|
337
|
-
});
|
|
351
|
+
}).returning({ inserted: insertedRow });
|
|
352
|
+
return row?.inserted === true;
|
|
338
353
|
}
|
|
339
354
|
async reactionsOf(orgId, messageIds, viewerId) {
|
|
340
355
|
if (messageIds.length === 0) return {};
|