@headless-lms/adapter-db 0.1.0 → 0.2.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/README.md +79 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/migrate.js.map +1 -1
- package/dist/repositories/assets.d.ts +1 -1
- package/dist/repositories/assets.js +4 -1
- package/dist/repositories/assets.js.map +1 -1
- package/dist/repositories/automations.d.ts +7 -5
- package/dist/repositories/automations.js +35 -10
- package/dist/repositories/automations.js.map +1 -1
- package/dist/repositories/content.d.ts +21 -7
- package/dist/repositories/content.js +168 -18
- package/dist/repositories/content.js.map +1 -1
- package/dist/repositories/course-analytics.d.ts +15 -0
- package/dist/repositories/course-analytics.js +137 -0
- package/dist/repositories/course-analytics.js.map +1 -0
- package/dist/repositories/credentials.js +2 -0
- package/dist/repositories/credentials.js.map +1 -1
- package/dist/repositories/dashboard.js +2 -0
- package/dist/repositories/dashboard.js.map +1 -1
- package/dist/repositories/discussion.d.ts +1 -1
- package/dist/repositories/discussion.js +2 -0
- package/dist/repositories/discussion.js.map +1 -1
- package/dist/repositories/entitlements.d.ts +11 -5
- package/dist/repositories/entitlements.js +42 -17
- package/dist/repositories/entitlements.js.map +1 -1
- package/dist/repositories/identity.js +2 -0
- package/dist/repositories/identity.js.map +1 -1
- package/dist/repositories/integrations.d.ts +1 -1
- package/dist/repositories/integrations.js +2 -0
- package/dist/repositories/integrations.js.map +1 -1
- package/dist/repositories/learn.js +6 -4
- package/dist/repositories/learn.js.map +1 -1
- package/dist/repositories/members.js +2 -0
- package/dist/repositories/members.js.map +1 -1
- package/dist/repositories/org-user-profile.js.map +1 -1
- package/dist/repositories/organizations.d.ts +4 -6
- package/dist/repositories/organizations.js +26 -45
- package/dist/repositories/organizations.js.map +1 -1
- package/dist/repositories/outbox.js +3 -0
- package/dist/repositories/outbox.js.map +1 -1
- package/dist/repositories/pg-errors.d.ts +53 -5
- package/dist/repositories/pg-errors.js +97 -8
- package/dist/repositories/pg-errors.js.map +1 -1
- package/dist/repositories/progress.d.ts +1 -1
- package/dist/repositories/progress.js +3 -0
- package/dist/repositories/progress.js.map +1 -1
- package/dist/repositories/settings.js +2 -0
- package/dist/repositories/settings.js.map +1 -1
- package/dist/repositories/students.d.ts +2 -1
- package/dist/repositories/students.js +80 -3
- package/dist/repositories/students.js.map +1 -1
- package/dist/schema/assets.d.ts +17 -0
- package/dist/schema/assets.js +2 -1
- package/dist/schema/assets.js.map +1 -1
- package/dist/schema/automations.d.ts +85 -8
- package/dist/schema/automations.js +7 -4
- package/dist/schema/automations.js.map +1 -1
- package/dist/schema/better-auth.js.map +1 -1
- package/dist/schema/content.d.ts +272 -22
- package/dist/schema/content.js +40 -5
- package/dist/schema/content.js.map +1 -1
- package/dist/schema/credentials.js +1 -1
- package/dist/schema/credentials.js.map +1 -1
- package/dist/schema/discussion.d.ts +34 -0
- package/dist/schema/discussion.js +4 -2
- package/dist/schema/discussion.js.map +1 -1
- package/dist/schema/entitlements.d.ts +52 -1
- package/dist/schema/entitlements.js +15 -5
- package/dist/schema/entitlements.js.map +1 -1
- package/dist/schema/identity.js.map +1 -1
- package/dist/schema/index.d.ts +3 -3
- package/dist/schema/index.js +4 -2
- package/dist/schema/index.js.map +1 -1
- package/dist/schema/integrations.js +1 -1
- package/dist/schema/integrations.js.map +1 -1
- package/dist/schema/organizations.d.ts +17 -0
- package/dist/schema/organizations.js +2 -1
- package/dist/schema/organizations.js.map +1 -1
- package/dist/schema/outbox.d.ts +17 -0
- package/dist/schema/outbox.js +1 -0
- package/dist/schema/outbox.js.map +1 -1
- package/dist/schema/progress.d.ts +17 -0
- package/dist/schema/progress.js +1 -0
- package/dist/schema/progress.js.map +1 -1
- package/dist/schema/settings.js.map +1 -1
- package/dist/unit-of-work.js +7 -2
- package/dist/unit-of-work.js.map +1 -1
- package/drizzle/0000_baseline.sql +0 -1
- package/drizzle/0001_new_sentry.sql +43 -0
- package/drizzle/0002_automation_kinds.sql +7 -0
- package/drizzle/meta/0000_snapshot.json +167 -597
- package/drizzle/meta/0001_snapshot.json +3769 -0
- package/drizzle/meta/0002_snapshot.json +3770 -0
- package/drizzle/meta/_journal.json +16 -2
- package/package.json +3 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { progressRecords } from "../schema/progress.js";
|
|
2
|
+
import { translateDbErrors } from "./pg-errors.js";
|
|
2
3
|
import { and, eq, inArray } from "drizzle-orm";
|
|
3
4
|
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
4
5
|
//#region src/repositories/progress.ts
|
|
@@ -12,6 +13,7 @@ function toRecord(row) {
|
|
|
12
13
|
startedAt: row.startedAt,
|
|
13
14
|
position: row.position ?? null,
|
|
14
15
|
completedAt: row.completedAt,
|
|
16
|
+
createdAt: row.createdAt,
|
|
15
17
|
updatedAt: row.updatedAt
|
|
16
18
|
};
|
|
17
19
|
}
|
|
@@ -53,6 +55,7 @@ var DrizzleProgressRepository = class {
|
|
|
53
55
|
return row ? toRecord(row) : null;
|
|
54
56
|
}
|
|
55
57
|
};
|
|
58
|
+
translateDbErrors(DrizzleProgressRepository);
|
|
56
59
|
//#endregion
|
|
57
60
|
export { DrizzleProgressRepository };
|
|
58
61
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"progress.js","names":[],"sources":["../../src/repositories/progress.ts"],"sourcesContent":["// progress — Drizzle repository (implements the core outbound port).\nimport { and, eq, inArray } from
|
|
1
|
+
{"version":3,"file":"progress.js","names":[],"sources":["../../src/repositories/progress.ts"],"sourcesContent":["// progress — Drizzle repository (implements the core outbound port).\nimport { and, eq, inArray } from \"drizzle-orm\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type {\n ProgressRecord,\n ProgressRepository,\n ProgressTarget,\n ProgressTargetType,\n} from \"@headless-lms/core/progress\";\nimport { progressRecords } from \"../schema/progress.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\ntype Row = typeof progressRecords.$inferSelect;\n\nfunction toRecord(row: Row): ProgressRecord {\n return {\n id: row.id,\n orgId: row.orgId,\n orgUserId: row.orgUserId,\n targetType: row.targetType as ProgressTargetType,\n targetId: row.targetId,\n startedAt: row.startedAt,\n position: row.position ?? null,\n completedAt: row.completedAt,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nexport class DrizzleProgressRepository implements ProgressRepository {\n constructor(\n private readonly db: NodePgDatabase,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async insert(orgId: string, record: ProgressRecord): Promise<ProgressRecord | null> {\n const [row] = await this.db\n .insert(progressRecords)\n .values({\n id: record.id,\n orgId,\n orgUserId: record.orgUserId,\n targetType: record.targetType,\n targetId: record.targetId,\n startedAt: new Date(record.startedAt),\n position: record.position ?? null,\n completedAt: record.completedAt ? new Date(record.completedAt) : null,\n updatedAt: new Date(record.updatedAt),\n })\n .onConflictDoNothing()\n .returning();\n return row ? toRecord(row) : null;\n }\n\n async findByTarget(orgId: string, target: ProgressTarget): Promise<ProgressRecord | null> {\n const [row] = await this.db\n .select()\n .from(progressRecords)\n .where(\n and(\n eq(progressRecords.orgId, orgId),\n eq(progressRecords.orgUserId, target.orgUserId),\n eq(progressRecords.targetType, target.targetType),\n eq(progressRecords.targetId, target.targetId),\n ),\n )\n .limit(1);\n return row ? toRecord(row) : null;\n }\n\n async findByTargets(\n orgId: string,\n orgUserId: string,\n targetIds: string[],\n opts?: { forUpdate?: boolean },\n ): Promise<ProgressRecord[]> {\n if (targetIds.length === 0) {\n return [];\n }\n const base = this.db\n .select()\n .from(progressRecords)\n .where(\n and(\n eq(progressRecords.orgId, orgId),\n eq(progressRecords.orgUserId, orgUserId),\n inArray(progressRecords.targetId, targetIds),\n ),\n );\n // Deterministic lock order prevents deadlock between overlapping reports.\n const rows = opts?.forUpdate\n ? await base.orderBy(progressRecords.id).for(\"update\")\n : await base;\n return rows.map(toRecord);\n }\n\n async update(\n orgId: string,\n id: string,\n patch: Partial<Pick<ProgressRecord, \"position\" | \"completedAt\">>,\n ): Promise<ProgressRecord | null> {\n const [row] = await this.db\n .update(progressRecords)\n .set({\n ...(\"position\" in patch ? { position: patch.position ?? null } : {}),\n ...(\"completedAt\" in patch\n ? { completedAt: patch.completedAt ? new Date(patch.completedAt) : null }\n : {}),\n })\n .where(and(eq(progressRecords.orgId, orgId), eq(progressRecords.id, id)))\n .returning();\n return row ? toRecord(row) : null;\n }\n}\ntranslateDbErrors(DrizzleProgressRepository);\n"],"mappings":";;;;;AAgBA,SAAS,SAAS,KAA0B;CAC1C,OAAO;EACL,IAAI,IAAI;EACR,OAAO,IAAI;EACX,WAAW,IAAI;EACf,YAAY,IAAI;EAChB,UAAU,IAAI;EACd,WAAW,IAAI;EACf,UAAU,IAAI,YAAY;EAC1B,aAAa,IAAI;EACjB,WAAW,IAAI;EACf,WAAW,IAAI;CACjB;AACF;AAEA,IAAa,4BAAb,MAAqE;CAEhD;CACA;CAFnB,YACE,IACA,SAAkC,YAClC;EAFiB,KAAA,KAAA;EACA,KAAA,SAAA;CAChB;CAEH,MAAM,OAAO,OAAe,QAAwD;EAClF,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO,eAAe,CAAC,CACvB,OAAO;GACN,IAAI,OAAO;GACX;GACA,WAAW,OAAO;GAClB,YAAY,OAAO;GACnB,UAAU,OAAO;GACjB,WAAW,IAAI,KAAK,OAAO,SAAS;GACpC,UAAU,OAAO,YAAY;GAC7B,aAAa,OAAO,cAAc,IAAI,KAAK,OAAO,WAAW,IAAI;GACjE,WAAW,IAAI,KAAK,OAAO,SAAS;EACtC,CAAC,CAAC,CACD,oBAAoB,CAAC,CACrB,UAAU;EACb,OAAO,MAAM,SAAS,GAAG,IAAI;CAC/B;CAEA,MAAM,aAAa,OAAe,QAAwD;EACxF,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO,CAAC,CACR,KAAK,eAAe,CAAC,CACrB,MACC,IACE,GAAG,gBAAgB,OAAO,KAAK,GAC/B,GAAG,gBAAgB,WAAW,OAAO,SAAS,GAC9C,GAAG,gBAAgB,YAAY,OAAO,UAAU,GAChD,GAAG,gBAAgB,UAAU,OAAO,QAAQ,CAC9C,CACF,CAAC,CACA,MAAM,CAAC;EACV,OAAO,MAAM,SAAS,GAAG,IAAI;CAC/B;CAEA,MAAM,cACJ,OACA,WACA,WACA,MAC2B;EAC3B,IAAI,UAAU,WAAW,GACvB,OAAO,CAAC;EAEV,MAAM,OAAO,KAAK,GACf,OAAO,CAAC,CACR,KAAK,eAAe,CAAC,CACrB,MACC,IACE,GAAG,gBAAgB,OAAO,KAAK,GAC/B,GAAG,gBAAgB,WAAW,SAAS,GACvC,QAAQ,gBAAgB,UAAU,SAAS,CAC7C,CACF;EAKF,QAHa,MAAM,YACf,MAAM,KAAK,QAAQ,gBAAgB,EAAE,CAAC,CAAC,IAAI,QAAQ,IACnD,MAAM,KAAA,CACE,IAAI,QAAQ;CAC1B;CAEA,MAAM,OACJ,OACA,IACA,OACgC;EAChC,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO,eAAe,CAAC,CACvB,IAAI;GACH,GAAI,cAAc,QAAQ,EAAE,UAAU,MAAM,YAAY,KAAK,IAAI,CAAC;GAClE,GAAI,iBAAiB,QACjB,EAAE,aAAa,MAAM,cAAc,IAAI,KAAK,MAAM,WAAW,IAAI,KAAK,IACtE,CAAC;EACP,CAAC,CAAC,CACD,MAAM,IAAI,GAAG,gBAAgB,OAAO,KAAK,GAAG,GAAG,gBAAgB,IAAI,EAAE,CAAC,CAAC,CAAC,CACxE,UAAU;EACb,OAAO,MAAM,SAAS,GAAG,IAAI;CAC/B;AACF;AACA,kBAAkB,yBAAyB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { settings } from "../schema/settings.js";
|
|
2
|
+
import { translateDbErrors } from "./pg-errors.js";
|
|
2
3
|
import { and, eq } from "drizzle-orm";
|
|
3
4
|
import { noopLogger } from "@headless-lms/core/shared/logger";
|
|
4
5
|
//#region src/repositories/settings.ts
|
|
@@ -67,6 +68,7 @@ var DrizzleSettingsRepository = class {
|
|
|
67
68
|
return toRecord(row);
|
|
68
69
|
}
|
|
69
70
|
};
|
|
71
|
+
translateDbErrors(DrizzleSettingsRepository);
|
|
70
72
|
//#endregion
|
|
71
73
|
export { DrizzleSettingsRepository, deepMerge };
|
|
72
74
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings.js","names":[],"sources":["../../src/repositories/settings.ts"],"sourcesContent":["import { and, eq } from
|
|
1
|
+
{"version":3,"file":"settings.js","names":[],"sources":["../../src/repositories/settings.ts"],"sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport type { Setting } from \"@headless-lms/core/schemas\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type { SettingsRecord, SettingsRepository } from \"@headless-lms/core/shared/settings\";\nimport { settings } from \"../schema/settings.js\";\nimport type { Logger } from \"@headless-lms/core/types\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport { translateDbErrors } from \"./pg-errors.js\";\n\ntype Row = typeof settings.$inferSelect;\n\nfunction toRecord(row: Row): SettingsRecord {\n return {\n namespace: row.namespace,\n scopeId: row.scopeId,\n value: row.value,\n };\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Recursive merge of a partial patch into a stored value: a patch cannot\n * clobber sibling keys at any depth, only the leaves it names. Arrays and\n * scalars are replaced outright — merging arrays element-wise has no meaning\n * for settings, and null is a value a caller may legitimately store.\n * Exported for tests.\n */\nexport function deepMerge(base: unknown, patch: unknown): unknown {\n if (!isPlainObject(base) || !isPlainObject(patch)) {\n return patch;\n }\n const merged: Record<string, unknown> = { ...base };\n for (const [key, value] of Object.entries(patch)) {\n merged[key] = key in base ? deepMerge(base[key], value) : value;\n }\n return merged;\n}\n\nexport class DrizzleSettingsRepository implements SettingsRepository {\n constructor(\n private readonly db: NodePgDatabase,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async find(orgId: string, scopeId: string, namespace?: string): Promise<SettingsRecord[]> {\n const baseConditions = [eq(settings.orgId, orgId), eq(settings.scopeId, scopeId)];\n const where = namespace\n ? and(...baseConditions, eq(settings.namespace, namespace))\n : and(...baseConditions);\n\n const rows = await this.db.select().from(settings).where(where);\n return rows.map(toRecord);\n }\n\n async patch(\n orgId: string,\n namespace: string,\n scopeId: string,\n patch: Record<string, unknown>,\n ): Promise<SettingsRecord> {\n const key = and(\n eq(settings.orgId, orgId),\n eq(settings.namespace, namespace),\n eq(settings.scopeId, scopeId),\n );\n\n // The merge happens in JS, so the read and the write must be one atomic\n // unit or concurrent patches would clobber each other's sibling keys:\n // SELECT ... FOR UPDATE holds the row for the rest of the transaction.\n const row = await this.db.transaction(async (tx) => {\n let [existing] = await tx.select().from(settings).where(key).for(\"update\");\n\n if (!existing) {\n const [inserted] = await tx\n .insert(settings)\n .values({ orgId, namespace, scopeId, value: patch as Setting[\"value\"] })\n .onConflictDoNothing()\n .returning();\n if (inserted) {\n return inserted;\n }\n // Lost the race: a concurrent transaction inserted the row and has\n // committed by the time onConflictDoNothing returned. Take the lock\n // and merge onto what it wrote.\n [existing] = await tx.select().from(settings).where(key).for(\"update\");\n if (!existing) {\n throw new Error(\"settings patch: row vanished mid-transaction\");\n }\n }\n\n const [updated] = await tx\n .update(settings)\n .set({ value: deepMerge(existing.value, patch) as Setting[\"value\"], updatedAt: new Date() })\n .where(key)\n .returning();\n return updated;\n });\n\n if (!row) {\n throw new Error(\"settings patch returned no row\");\n }\n this.logger.debug(\"settings.patch\", { orgId, namespace, scopeId });\n return toRecord(row);\n }\n}\ntranslateDbErrors(DrizzleSettingsRepository);\n"],"mappings":";;;;;AAWA,SAAS,SAAS,KAA0B;CAC1C,OAAO;EACL,WAAW,IAAI;EACf,SAAS,IAAI;EACb,OAAO,IAAI;CACb;AACF;AAEA,SAAS,cAAc,OAAkD;CACvE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;;;;;;AASA,SAAgB,UAAU,MAAe,OAAyB;CAChE,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,cAAc,KAAK,GAC9C,OAAO;CAET,MAAM,SAAkC,EAAE,GAAG,KAAK;CAClD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAC7C,OAAO,OAAO,OAAO,OAAO,UAAU,KAAK,MAAM,KAAK,IAAI;CAE5D,OAAO;AACT;AAEA,IAAa,4BAAb,MAAqE;CAEhD;CACA;CAFnB,YACE,IACA,SAAkC,YAClC;EAFiB,KAAA,KAAA;EACA,KAAA,SAAA;CAChB;CAEH,MAAM,KAAK,OAAe,SAAiB,WAA+C;EACxF,MAAM,iBAAiB,CAAC,GAAG,SAAS,OAAO,KAAK,GAAG,GAAG,SAAS,SAAS,OAAO,CAAC;EAChF,MAAM,QAAQ,YACV,IAAI,GAAG,gBAAgB,GAAG,SAAS,WAAW,SAAS,CAAC,IACxD,IAAI,GAAG,cAAc;EAGzB,QAAO,MADY,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,MAAM,KAAK,EAAA,CAClD,IAAI,QAAQ;CAC1B;CAEA,MAAM,MACJ,OACA,WACA,SACA,OACyB;EACzB,MAAM,MAAM,IACV,GAAG,SAAS,OAAO,KAAK,GACxB,GAAG,SAAS,WAAW,SAAS,GAChC,GAAG,SAAS,SAAS,OAAO,CAC9B;EAKA,MAAM,MAAM,MAAM,KAAK,GAAG,YAAY,OAAO,OAAO;GAClD,IAAI,CAAC,YAAY,MAAM,GAAG,OAAO,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,QAAQ;GAEzE,IAAI,CAAC,UAAU;IACb,MAAM,CAAC,YAAY,MAAM,GACtB,OAAO,QAAQ,CAAC,CAChB,OAAO;KAAE;KAAO;KAAW;KAAS,OAAO;IAA0B,CAAC,CAAC,CACvE,oBAAoB,CAAC,CACrB,UAAU;IACb,IAAI,UACF,OAAO;IAKT,CAAC,YAAY,MAAM,GAAG,OAAO,CAAC,CAAC,KAAK,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,QAAQ;IACrE,IAAI,CAAC,UACH,MAAM,IAAI,MAAM,8CAA8C;GAElE;GAEA,MAAM,CAAC,WAAW,MAAM,GACrB,OAAO,QAAQ,CAAC,CAChB,IAAI;IAAE,OAAO,UAAU,SAAS,OAAO,KAAK;IAAuB,2BAAW,IAAI,KAAK;GAAE,CAAC,CAAC,CAC3F,MAAM,GAAG,CAAC,CACV,UAAU;GACb,OAAO;EACT,CAAC;EAED,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,gCAAgC;EAElD,KAAK,OAAO,MAAM,kBAAkB;GAAE;GAAO;GAAW;EAAQ,CAAC;EACjE,OAAO,SAAS,GAAG;CACrB;AACF;AACA,kBAAkB,yBAAyB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NodePgDatabase } from "drizzle-orm/node-postgres";
|
|
2
2
|
import { Logger } from "@headless-lms/core/shared/ports";
|
|
3
|
-
import { Page, Student, StudentsQuery, StudentsReportRepository } from "@headless-lms/core/reporting/students";
|
|
3
|
+
import { Page, Student, StudentAnalytics, StudentsQuery, StudentsReportRepository } from "@headless-lms/core/reporting/students";
|
|
4
4
|
//#region src/repositories/students.d.ts
|
|
5
5
|
declare class DrizzleStudentsRepository implements StudentsReportRepository {
|
|
6
6
|
private readonly db;
|
|
@@ -8,6 +8,7 @@ declare class DrizzleStudentsRepository implements StudentsReportRepository {
|
|
|
8
8
|
constructor(db: NodePgDatabase, logger?: Logger);
|
|
9
9
|
list(orgId: string, query: StudentsQuery): Promise<Page<Student>>;
|
|
10
10
|
findById(orgId: string, id: string): Promise<Student | null>;
|
|
11
|
+
analytics(orgId: string, id: string): Promise<StudentAnalytics | null>;
|
|
11
12
|
private resolveOrder;
|
|
12
13
|
}
|
|
13
14
|
//#endregion
|
|
@@ -2,6 +2,7 @@ import { users } from "../schema/identity.js";
|
|
|
2
2
|
import { orgUsers } from "../schema/organizations.js";
|
|
3
3
|
import { entitlements } from "../schema/entitlements.js";
|
|
4
4
|
import "../schema/index.js";
|
|
5
|
+
import { translateDbErrors } from "./pg-errors.js";
|
|
5
6
|
import { user } from "../schema/better-auth.js";
|
|
6
7
|
import { orgUserProfileColumns } from "./org-user-profile.js";
|
|
7
8
|
import { and, asc, desc, eq, ilike, or, sql } from "drizzle-orm";
|
|
@@ -10,6 +11,10 @@ import { STUDENT_ROLE } from "@headless-lms/core/organizations";
|
|
|
10
11
|
//#region src/repositories/students.ts
|
|
11
12
|
const entitlementCountExpr = sql`count(${entitlements.id})`;
|
|
12
13
|
const avgProgressExpr = sql`0`;
|
|
14
|
+
const lastActiveExpr = sql`(
|
|
15
|
+
select max(pr.updated_at) from progress_records pr
|
|
16
|
+
where pr.org_id = ${orgUsers.orgId} and pr.org_user_id = ${orgUsers.id}
|
|
17
|
+
)`;
|
|
13
18
|
function toStudent(row) {
|
|
14
19
|
return {
|
|
15
20
|
id: row.id,
|
|
@@ -21,7 +26,7 @@ function toStudent(row) {
|
|
|
21
26
|
entitlementCount: Number(row.entitlementCount),
|
|
22
27
|
avgProgress: Number(row.avgProgress),
|
|
23
28
|
joinedAt: row.createdAt.toISOString(),
|
|
24
|
-
lastActiveAt: null
|
|
29
|
+
lastActiveAt: row.lastActiveAt ? new Date(row.lastActiveAt).toISOString() : null
|
|
25
30
|
};
|
|
26
31
|
}
|
|
27
32
|
var DrizzleStudentsRepository = class {
|
|
@@ -46,7 +51,8 @@ var DrizzleStudentsRepository = class {
|
|
|
46
51
|
status: orgUsers.status,
|
|
47
52
|
createdAt: orgUsers.createdAt,
|
|
48
53
|
entitlementCount: entitlementCountExpr,
|
|
49
|
-
avgProgress: avgProgressExpr
|
|
54
|
+
avgProgress: avgProgressExpr,
|
|
55
|
+
lastActiveAt: lastActiveExpr
|
|
50
56
|
}).from(orgUsers).leftJoin(entitlements, and(eq(entitlements.orgId, orgUsers.orgId), eq(entitlements.orgUserId, orgUsers.id))).innerJoin(users, eq(users.id, orgUsers.userId)).leftJoin(user, eq(user.id, users.externalId)).where(where).groupBy(orgUsers.orgId, orgUsers.id, orgUsers.status, users.email, users.firstName, users.lastName, user.image).orderBy(...this.resolveOrder(query.sort)).limit(query.pageSize).offset((query.page - 1) * query.pageSize)).map(toStudent),
|
|
51
57
|
total: Number(totals?.total ?? 0),
|
|
52
58
|
page: query.page,
|
|
@@ -59,10 +65,79 @@ var DrizzleStudentsRepository = class {
|
|
|
59
65
|
status: orgUsers.status,
|
|
60
66
|
createdAt: orgUsers.createdAt,
|
|
61
67
|
entitlementCount: entitlementCountExpr,
|
|
62
|
-
avgProgress: avgProgressExpr
|
|
68
|
+
avgProgress: avgProgressExpr,
|
|
69
|
+
lastActiveAt: lastActiveExpr
|
|
63
70
|
}).from(orgUsers).leftJoin(entitlements, and(eq(entitlements.orgId, orgUsers.orgId), eq(entitlements.orgUserId, orgUsers.id))).innerJoin(users, eq(users.id, orgUsers.userId)).leftJoin(user, eq(user.id, users.externalId)).where(and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, id), eq(orgUsers.role, STUDENT_ROLE))).groupBy(orgUsers.orgId, orgUsers.id, orgUsers.status, users.email, users.firstName, users.lastName, user.image).limit(1);
|
|
64
71
|
return row ? toStudent(row) : null;
|
|
65
72
|
}
|
|
73
|
+
async analytics(orgId, id) {
|
|
74
|
+
const [exists] = await this.db.select({ id: orgUsers.id }).from(orgUsers).where(and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, id), eq(orgUsers.role, STUDENT_ROLE)));
|
|
75
|
+
if (!exists) return null;
|
|
76
|
+
const courses = (await this.db.execute(sql`
|
|
77
|
+
with cohort_courses as (
|
|
78
|
+
select c.id, c.title
|
|
79
|
+
from entitlements e
|
|
80
|
+
join courses c on c.org_id = e.org_id and c.id = e.content_id
|
|
81
|
+
where e.org_id = ${orgId}
|
|
82
|
+
and e.org_user_id = ${id}
|
|
83
|
+
and e.status = 'active'
|
|
84
|
+
and (e.expires_at is null or e.expires_at >= now())
|
|
85
|
+
),
|
|
86
|
+
acts as (
|
|
87
|
+
select a.course_id, a.id
|
|
88
|
+
from activities a
|
|
89
|
+
where a.org_id = ${orgId}
|
|
90
|
+
and a.course_id in (select id from cohort_courses)
|
|
91
|
+
and coalesce((a.settings ->> 'published')::boolean, true)
|
|
92
|
+
)
|
|
93
|
+
select
|
|
94
|
+
cc.id as course_id,
|
|
95
|
+
cc.title,
|
|
96
|
+
count(a.id)::int as total_activities,
|
|
97
|
+
count(pr.id) filter (where pr.completed_at is not null)::int as completed_activities,
|
|
98
|
+
min(pr.started_at) as started_at,
|
|
99
|
+
max(pr.updated_at) as last_activity_at,
|
|
100
|
+
(
|
|
101
|
+
select prc.completed_at from progress_records prc
|
|
102
|
+
where prc.org_id = ${orgId}
|
|
103
|
+
and prc.org_user_id = ${id}
|
|
104
|
+
and prc.target_type = 'course'
|
|
105
|
+
and prc.target_id = cc.id
|
|
106
|
+
) as completed_at
|
|
107
|
+
from cohort_courses cc
|
|
108
|
+
left join acts a on a.course_id = cc.id
|
|
109
|
+
left join progress_records pr
|
|
110
|
+
on pr.org_id = ${orgId}
|
|
111
|
+
and pr.org_user_id = ${id}
|
|
112
|
+
and pr.target_type = 'activity'
|
|
113
|
+
and pr.target_id = a.id
|
|
114
|
+
group by cc.id, cc.title
|
|
115
|
+
order by cc.title
|
|
116
|
+
`)).rows.map((r) => {
|
|
117
|
+
const total = Number(r.total_activities);
|
|
118
|
+
const done = Number(r.completed_activities);
|
|
119
|
+
return {
|
|
120
|
+
courseId: r.course_id,
|
|
121
|
+
title: r.title,
|
|
122
|
+
totalActivities: total,
|
|
123
|
+
completedActivities: done,
|
|
124
|
+
progress: total > 0 ? Math.round(Math.min(done / total, 1) * 100) : 0,
|
|
125
|
+
startedAt: r.started_at ? new Date(r.started_at).toISOString() : null,
|
|
126
|
+
lastActivityAt: r.last_activity_at ? new Date(r.last_activity_at).toISOString() : null,
|
|
127
|
+
completedAt: r.completed_at ? new Date(r.completed_at).toISOString() : null
|
|
128
|
+
};
|
|
129
|
+
});
|
|
130
|
+
const started = courses.filter((c) => c.startedAt !== null).length;
|
|
131
|
+
const completed = courses.filter((c) => c.completedAt !== null).length;
|
|
132
|
+
const avgProgress = courses.length > 0 ? Math.round(courses.reduce((sum, c) => sum + c.progress, 0) / courses.length) : 0;
|
|
133
|
+
return {
|
|
134
|
+
enrolled: courses.length,
|
|
135
|
+
started,
|
|
136
|
+
completed,
|
|
137
|
+
avgProgress,
|
|
138
|
+
courses
|
|
139
|
+
};
|
|
140
|
+
}
|
|
66
141
|
resolveOrder(sort) {
|
|
67
142
|
const descending = sort?.startsWith("-") ?? false;
|
|
68
143
|
const field = sort ? descending ? sort.slice(1) : sort : "firstName";
|
|
@@ -72,11 +147,13 @@ var DrizzleStudentsRepository = class {
|
|
|
72
147
|
case "entitlementCount": return [dir(entitlementCountExpr)];
|
|
73
148
|
case "avgProgress": return [dir(avgProgressExpr)];
|
|
74
149
|
case "joinedAt": return [dir(orgUsers.createdAt)];
|
|
150
|
+
case "lastActiveAt": return [dir(lastActiveExpr)];
|
|
75
151
|
case "lastName": return [dir(users.lastName), dir(users.firstName)];
|
|
76
152
|
default: return [dir(users.firstName), dir(users.lastName)];
|
|
77
153
|
}
|
|
78
154
|
}
|
|
79
155
|
};
|
|
156
|
+
translateDbErrors(DrizzleStudentsRepository);
|
|
80
157
|
//#endregion
|
|
81
158
|
export { DrizzleStudentsRepository };
|
|
82
159
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"students.js","names":[],"sources":["../../src/repositories/students.ts"],"sourcesContent":["// students — Drizzle repository (implements the core outbound port).\n// A read-model over org users + entitlements: a \"student\" row in the report is\n// any org_users row with role 'student', with or without entitlements. Rooted at\n// `org_users` and scoped by its org, with entitlements LEFT JOINed in for the\n// aggregated count. Name/email come from the identity `users` row (INNER, since\n// user_id is NOT NULL); the avatar comes from the better-auth `user` table,\n// LEFT JOINed via that same identity row.\nimport { and, asc, desc, eq, ilike, or, sql, type SQL } from 'drizzle-orm';\nimport type { NodePgDatabase } from 'drizzle-orm/node-postgres';\nimport type {\n Page,\n Student,\n StudentsQuery,\n StudentsReportRepository,\n} from '@headless-lms/core/reporting/students';\nimport { orgUsers, users, entitlements } from '../schema/index.js';\nimport { STUDENT_ROLE } from '@headless-lms/core/organizations';\nimport { user } from '../schema/better-auth.js';\nimport type { OrgUserStatus } from '@headless-lms/core/types';\nimport type { Logger } from '@headless-lms/core/shared/ports';\nimport { noopLogger } from '@headless-lms/core/shared/logger';\nimport { orgUserProfileColumns } from './org-user-profile.js';\n\nconst entitlementCountExpr = sql<number>`count(${entitlements.id})`;\n// Completion now lives in the progress domain; the students report no longer\n// derives a percentage from entitlements. Placeholder until wired to progress.\nconst avgProgressExpr = sql<number>`0`;\n\ninterface StudentRow {\n id: string;\n email: string;\n image: string | null;\n firstName: string | null;\n lastName: string | null;\n status: OrgUserStatus;\n createdAt: Date;\n entitlementCount: number;\n avgProgress: number;\n}\n\nfunction toStudent(row: StudentRow): Student {\n return {\n id: row.id,\n email: row.email,\n image: row.image ?? null,\n firstName: row.firstName,\n lastName: row.lastName,\n status: row.status,\n entitlementCount: Number(row.entitlementCount),\n avgProgress: Number(row.avgProgress),\n joinedAt: row.createdAt.toISOString(),\n lastActiveAt: null,\n };\n}\n\nexport class DrizzleStudentsRepository implements StudentsReportRepository {\n constructor(\n private readonly db: NodePgDatabase,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async list(orgId: string, query: StudentsQuery): Promise<Page<Student>> {\n // Staff share this table now — the role filter is what keeps them out of\n // the students report.\n const filters: SQL[] = [eq(orgUsers.orgId, orgId), eq(orgUsers.role, STUDENT_ROLE)];\n const q = query.search?.trim();\n if (q) {\n const like = `%${q}%`;\n filters.push(\n or(\n ilike(users.firstName, like),\n ilike(users.lastName, like),\n ilike(users.email, like),\n )!,\n );\n }\n const where = and(...filters);\n\n const [totals] = await this.db\n .select({ total: sql<number>`count(*)` })\n .from(orgUsers)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .where(where);\n\n const rows = await this.db\n .select({\n ...orgUserProfileColumns,\n status: orgUsers.status,\n createdAt: orgUsers.createdAt,\n entitlementCount: entitlementCountExpr,\n avgProgress: avgProgressExpr,\n })\n .from(orgUsers)\n .leftJoin(\n entitlements,\n and(eq(entitlements.orgId, orgUsers.orgId), eq(entitlements.orgUserId, orgUsers.id)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(where)\n // Group by the full composite PK (orgId, id): grouping by id alone gives\n // Postgres no functional dependency for the other students columns.\n .groupBy(\n orgUsers.orgId,\n orgUsers.id,\n orgUsers.status,\n users.email,\n users.firstName,\n users.lastName,\n user.image,\n )\n .orderBy(...this.resolveOrder(query.sort))\n .limit(query.pageSize)\n .offset((query.page - 1) * query.pageSize);\n\n return {\n rows: rows.map(toStudent),\n total: Number(totals?.total ?? 0),\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async findById(orgId: string, id: string): Promise<Student | null> {\n const [row] = await this.db\n .select({\n ...orgUserProfileColumns,\n status: orgUsers.status,\n createdAt: orgUsers.createdAt,\n entitlementCount: entitlementCountExpr,\n avgProgress: avgProgressExpr,\n })\n .from(orgUsers)\n .leftJoin(\n entitlements,\n and(eq(entitlements.orgId, orgUsers.orgId), eq(entitlements.orgUserId, orgUsers.id)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, id), eq(orgUsers.role, STUDENT_ROLE)))\n .groupBy(\n orgUsers.orgId,\n orgUsers.id,\n orgUsers.status,\n users.email,\n users.firstName,\n users.lastName,\n user.image,\n )\n .limit(1);\n return row ? toStudent(row) : null;\n }\n\n private resolveOrder(sort?: string): SQL[] {\n const descending = sort?.startsWith('-') ?? false;\n const field = sort ? (descending ? sort.slice(1) : sort) : 'firstName';\n const dir = descending ? desc : asc;\n switch (field) {\n case 'email':\n return [dir(users.email)];\n case 'entitlementCount':\n return [dir(entitlementCountExpr)];\n case 'avgProgress':\n return [dir(avgProgressExpr)];\n case 'joinedAt':\n return [dir(orgUsers.createdAt)];\n case 'lastName':\n return [dir(users.lastName), dir(users.firstName)];\n case 'firstName':\n default:\n return [dir(users.firstName), dir(users.lastName)];\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"students.js","names":[],"sources":["../../src/repositories/students.ts"],"sourcesContent":["// students — Drizzle repository (implements the core outbound port).\n// A read-model over org users + entitlements: a \"student\" row in the report is\n// any org_users row with role 'student', with or without entitlements. Rooted at\n// `org_users` and scoped by its org, with entitlements LEFT JOINed in for the\n// aggregated count. Name/email come from the identity `users` row (INNER, since\n// user_id is NOT NULL); the avatar comes from the better-auth `user` table,\n// LEFT JOINed via that same identity row.\nimport { and, asc, desc, eq, ilike, or, sql, type SQL } from \"drizzle-orm\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport type {\n Page,\n Student,\n StudentAnalytics,\n StudentCourseProgress,\n StudentsQuery,\n StudentsReportRepository,\n} from \"@headless-lms/core/reporting/students\";\nimport { orgUsers, users, entitlements } from \"../schema/index.js\";\nimport { STUDENT_ROLE } from \"@headless-lms/core/organizations\";\nimport { user } from \"../schema/better-auth.js\";\nimport type { OrgUserStatus } from \"@headless-lms/core/types\";\nimport type { Logger } from \"@headless-lms/core/shared/ports\";\nimport { noopLogger } from \"@headless-lms/core/shared/logger\";\nimport { orgUserProfileColumns } from \"./org-user-profile.js\";\nimport { translateDbErrors } from \"./pg-errors.js\";\n\nconst entitlementCountExpr = sql<number>`count(${entitlements.id})`;\n// Completion now lives in the progress domain; the students report no longer\n// derives a percentage from entitlements. Placeholder until wired to progress.\nconst avgProgressExpr = sql<number>`0`;\n// Last learning activity: the newest touch on any of the student's progress\n// records. A scalar subquery so it neither fans out the entitlement count nor\n// widens the GROUP BY.\nconst lastActiveExpr = sql<Date | null>`(\n select max(pr.updated_at) from progress_records pr\n where pr.org_id = ${orgUsers.orgId} and pr.org_user_id = ${orgUsers.id}\n)`;\n\ninterface StudentRow {\n id: string;\n email: string;\n image: string | null;\n firstName: string | null;\n lastName: string | null;\n status: OrgUserStatus;\n createdAt: Date;\n entitlementCount: number;\n avgProgress: number;\n lastActiveAt: Date | null;\n}\n\nfunction toStudent(row: StudentRow): Student {\n return {\n id: row.id,\n email: row.email,\n image: row.image ?? null,\n firstName: row.firstName,\n lastName: row.lastName,\n status: row.status,\n entitlementCount: Number(row.entitlementCount),\n avgProgress: Number(row.avgProgress),\n joinedAt: row.createdAt.toISOString(),\n lastActiveAt: row.lastActiveAt ? new Date(row.lastActiveAt).toISOString() : null,\n };\n}\n\nexport class DrizzleStudentsRepository implements StudentsReportRepository {\n constructor(\n private readonly db: NodePgDatabase,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async list(orgId: string, query: StudentsQuery): Promise<Page<Student>> {\n // Staff share this table now — the role filter is what keeps them out of\n // the students report.\n const filters: SQL[] = [eq(orgUsers.orgId, orgId), eq(orgUsers.role, STUDENT_ROLE)];\n const q = query.search?.trim();\n if (q) {\n const like = `%${q}%`;\n filters.push(\n or(ilike(users.firstName, like), ilike(users.lastName, like), ilike(users.email, like))!,\n );\n }\n const where = and(...filters);\n\n const [totals] = await this.db\n .select({ total: sql<number>`count(*)` })\n .from(orgUsers)\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .where(where);\n\n const rows = await this.db\n .select({\n ...orgUserProfileColumns,\n status: orgUsers.status,\n createdAt: orgUsers.createdAt,\n entitlementCount: entitlementCountExpr,\n avgProgress: avgProgressExpr,\n lastActiveAt: lastActiveExpr,\n })\n .from(orgUsers)\n .leftJoin(\n entitlements,\n and(eq(entitlements.orgId, orgUsers.orgId), eq(entitlements.orgUserId, orgUsers.id)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(where)\n // Group by the full composite PK (orgId, id): grouping by id alone gives\n // Postgres no functional dependency for the other students columns.\n .groupBy(\n orgUsers.orgId,\n orgUsers.id,\n orgUsers.status,\n users.email,\n users.firstName,\n users.lastName,\n user.image,\n )\n .orderBy(...this.resolveOrder(query.sort))\n .limit(query.pageSize)\n .offset((query.page - 1) * query.pageSize);\n\n return {\n rows: rows.map(toStudent),\n total: Number(totals?.total ?? 0),\n page: query.page,\n pageSize: query.pageSize,\n };\n }\n\n async findById(orgId: string, id: string): Promise<Student | null> {\n const [row] = await this.db\n .select({\n ...orgUserProfileColumns,\n status: orgUsers.status,\n createdAt: orgUsers.createdAt,\n entitlementCount: entitlementCountExpr,\n avgProgress: avgProgressExpr,\n lastActiveAt: lastActiveExpr,\n })\n .from(orgUsers)\n .leftJoin(\n entitlements,\n and(eq(entitlements.orgId, orgUsers.orgId), eq(entitlements.orgUserId, orgUsers.id)),\n )\n .innerJoin(users, eq(users.id, orgUsers.userId))\n .leftJoin(user, eq(user.id, users.externalId))\n .where(and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, id), eq(orgUsers.role, STUDENT_ROLE)))\n .groupBy(\n orgUsers.orgId,\n orgUsers.id,\n orgUsers.status,\n users.email,\n users.firstName,\n users.lastName,\n user.image,\n )\n .limit(1);\n return row ? toStudent(row) : null;\n }\n\n // Learner record: one row per course the student holds an effective-active\n // entitlement to (mirroring the course-analytics cohort rule), with progress\n // computed over published activities the same way reporting/courses does.\n async analytics(orgId: string, id: string): Promise<StudentAnalytics | null> {\n const [exists] = await this.db\n .select({ id: orgUsers.id })\n .from(orgUsers)\n .where(and(eq(orgUsers.orgId, orgId), eq(orgUsers.id, id), eq(orgUsers.role, STUDENT_ROLE)));\n if (!exists) {\n return null;\n }\n\n const result = await this.db.execute(sql`\n with cohort_courses as (\n select c.id, c.title\n from entitlements e\n join courses c on c.org_id = e.org_id and c.id = e.content_id\n where e.org_id = ${orgId}\n and e.org_user_id = ${id}\n and e.status = 'active'\n and (e.expires_at is null or e.expires_at >= now())\n ),\n acts as (\n select a.course_id, a.id\n from activities a\n where a.org_id = ${orgId}\n and a.course_id in (select id from cohort_courses)\n and coalesce((a.settings ->> 'published')::boolean, true)\n )\n select\n cc.id as course_id,\n cc.title,\n count(a.id)::int as total_activities,\n count(pr.id) filter (where pr.completed_at is not null)::int as completed_activities,\n min(pr.started_at) as started_at,\n max(pr.updated_at) as last_activity_at,\n (\n select prc.completed_at from progress_records prc\n where prc.org_id = ${orgId}\n and prc.org_user_id = ${id}\n and prc.target_type = 'course'\n and prc.target_id = cc.id\n ) as completed_at\n from cohort_courses cc\n left join acts a on a.course_id = cc.id\n left join progress_records pr\n on pr.org_id = ${orgId}\n and pr.org_user_id = ${id}\n and pr.target_type = 'activity'\n and pr.target_id = a.id\n group by cc.id, cc.title\n order by cc.title\n `);\n\n const courses: StudentCourseProgress[] = (\n result.rows as {\n course_id: string;\n title: string;\n total_activities: number;\n completed_activities: number;\n started_at: Date | string | null;\n last_activity_at: Date | string | null;\n completed_at: Date | string | null;\n }[]\n ).map((r) => {\n const total = Number(r.total_activities);\n const done = Number(r.completed_activities);\n return {\n courseId: r.course_id,\n title: r.title,\n totalActivities: total,\n completedActivities: done,\n progress: total > 0 ? Math.round(Math.min(done / total, 1) * 100) : 0,\n startedAt: r.started_at ? new Date(r.started_at).toISOString() : null,\n lastActivityAt: r.last_activity_at ? new Date(r.last_activity_at).toISOString() : null,\n completedAt: r.completed_at ? new Date(r.completed_at).toISOString() : null,\n };\n });\n\n const started = courses.filter((c) => c.startedAt !== null).length;\n const completed = courses.filter((c) => c.completedAt !== null).length;\n const avgProgress =\n courses.length > 0\n ? Math.round(courses.reduce((sum, c) => sum + c.progress, 0) / courses.length)\n : 0;\n\n return { enrolled: courses.length, started, completed, avgProgress, courses };\n }\n\n private resolveOrder(sort?: string): SQL[] {\n const descending = sort?.startsWith(\"-\") ?? false;\n const field = sort ? (descending ? sort.slice(1) : sort) : \"firstName\";\n const dir = descending ? desc : asc;\n switch (field) {\n case \"email\":\n return [dir(users.email)];\n case \"entitlementCount\":\n return [dir(entitlementCountExpr)];\n case \"avgProgress\":\n return [dir(avgProgressExpr)];\n case \"joinedAt\":\n return [dir(orgUsers.createdAt)];\n case \"lastActiveAt\":\n return [dir(lastActiveExpr)];\n case \"lastName\":\n return [dir(users.lastName), dir(users.firstName)];\n case \"firstName\":\n default:\n return [dir(users.firstName), dir(users.lastName)];\n }\n }\n}\ntranslateDbErrors(DrizzleStudentsRepository);\n"],"mappings":"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"}
|
package/dist/schema/assets.d.ts
CHANGED
|
@@ -173,6 +173,23 @@ declare const assets: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
173
173
|
identity: undefined;
|
|
174
174
|
generated: undefined;
|
|
175
175
|
}, {}, {}>;
|
|
176
|
+
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
177
|
+
name: "updated_at";
|
|
178
|
+
tableName: "assets";
|
|
179
|
+
dataType: "date";
|
|
180
|
+
columnType: "PgTimestamp";
|
|
181
|
+
data: Date;
|
|
182
|
+
driverParam: string;
|
|
183
|
+
notNull: true;
|
|
184
|
+
hasDefault: true;
|
|
185
|
+
isPrimaryKey: false;
|
|
186
|
+
isAutoincrement: false;
|
|
187
|
+
hasRuntimeDefault: false;
|
|
188
|
+
enumValues: undefined;
|
|
189
|
+
baseColumn: never;
|
|
190
|
+
identity: undefined;
|
|
191
|
+
generated: undefined;
|
|
192
|
+
}, {}, {}>;
|
|
176
193
|
};
|
|
177
194
|
dialect: "pg";
|
|
178
195
|
}>;
|
package/dist/schema/assets.js
CHANGED
|
@@ -16,7 +16,8 @@ const assets = pgTable("assets", {
|
|
|
16
16
|
size: bigint("size", { mode: "number" }).notNull().default(0),
|
|
17
17
|
status: text("status", { enum: ["pending", "ready"] }).notNull().default("pending"),
|
|
18
18
|
uploadedBy: text("uploaded_by").notNull(),
|
|
19
|
-
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
|
|
19
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
20
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
20
21
|
}, (t) => ({ pk: primaryKey({ columns: [t.orgId, t.id] }) }));
|
|
21
22
|
//#endregion
|
|
22
23
|
export { assets };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.js","names":[],"sources":["../../src/schema/assets.ts"],"sourcesContent":["// assets table — the org's media library. Org-scoped: composite (org_id, id) PK\n// with org_id → organizations.id, mirroring the multi-tenant table shape.\nimport { pgTable, text, bigint, timestamp, primaryKey } from
|
|
1
|
+
{"version":3,"file":"assets.js","names":[],"sources":["../../src/schema/assets.ts"],"sourcesContent":["// assets table — the org's media library. Org-scoped: composite (org_id, id) PK\n// with org_id → organizations.id, mirroring the multi-tenant table shape.\nimport { pgTable, text, bigint, timestamp, primaryKey } from \"drizzle-orm/pg-core\";\nimport type { Asset } from \"@headless-lms/core/schemas\";\nimport { genId } from \"@headless-lms/core/shared/id\";\nimport { organizations } from \"./organizations.js\";\nimport type { Expect, NoDrift } from \"./drift.js\";\n\nexport const assets = pgTable(\n \"assets\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\")\n .notNull()\n .$defaultFn(() => genId(\"asset\")),\n key: text(\"key\").notNull(),\n kind: text(\"kind\", { enum: [\"video\", \"download\", \"content\"] }).notNull(),\n filename: text(\"filename\").notNull(),\n contentType: text(\"content_type\").notNull(),\n // Bytes; bigint because media files exceed int4. 0 until upload confirmed.\n size: bigint(\"size\", { mode: \"number\" }).notNull().default(0),\n status: text(\"status\", { enum: [\"pending\", \"ready\"] })\n .notNull()\n .default(\"pending\"),\n uploadedBy: text(\"uploaded_by\").notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true })\n .notNull()\n .defaultNow()\n .$onUpdate(() => new Date()),\n },\n (t) => ({ pk: primaryKey({ columns: [t.orgId, t.id] }) }),\n);\n\ntype _AssetsDrift = Expect<NoDrift<typeof assets.$inferSelect, Asset>>;\n"],"mappings":";;;;AAQA,MAAa,SAAS,QACpB,UACA;CACE,OAAO,KAAK,QAAQ,CAAC,CAClB,QAAQ,CAAC,CACT,iBAAiB,cAAc,EAAE;CACpC,IAAI,KAAK,IAAI,CAAC,CACX,QAAQ,CAAC,CACT,iBAAiB,MAAM,OAAO,CAAC;CAClC,KAAK,KAAK,KAAK,CAAC,CAAC,QAAQ;CACzB,MAAM,KAAK,QAAQ,EAAE,MAAM;EAAC;EAAS;EAAY;CAAS,EAAE,CAAC,CAAC,CAAC,QAAQ;CACvE,UAAU,KAAK,UAAU,CAAC,CAAC,QAAQ;CACnC,aAAa,KAAK,cAAc,CAAC,CAAC,QAAQ;CAE1C,MAAM,OAAO,QAAQ,EAAE,MAAM,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;CAC5D,QAAQ,KAAK,UAAU,EAAE,MAAM,CAAC,WAAW,OAAO,EAAE,CAAC,CAAC,CACnD,QAAQ,CAAC,CACT,QAAQ,SAAS;CACpB,YAAY,KAAK,aAAa,CAAC,CAAC,QAAQ;CACxC,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW;CAChF,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CACvD,QAAQ,CAAC,CACT,WAAW,CAAC,CACZ,gCAAgB,IAAI,KAAK,CAAC;AAC/B,IACC,OAAO,EAAE,IAAI,WAAW,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,EACzD"}
|
|
@@ -55,6 +55,23 @@ declare const automations: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
55
55
|
identity: undefined;
|
|
56
56
|
generated: undefined;
|
|
57
57
|
}, {}, {}>;
|
|
58
|
+
kind: import("drizzle-orm/pg-core").PgColumn<{
|
|
59
|
+
name: "kind";
|
|
60
|
+
tableName: "automations";
|
|
61
|
+
dataType: "string";
|
|
62
|
+
columnType: "PgText";
|
|
63
|
+
data: "workflow" | "webhook";
|
|
64
|
+
driverParam: string;
|
|
65
|
+
notNull: true;
|
|
66
|
+
hasDefault: true;
|
|
67
|
+
isPrimaryKey: false;
|
|
68
|
+
isAutoincrement: false;
|
|
69
|
+
hasRuntimeDefault: false;
|
|
70
|
+
enumValues: ["workflow", "webhook"];
|
|
71
|
+
baseColumn: never;
|
|
72
|
+
identity: undefined;
|
|
73
|
+
generated: undefined;
|
|
74
|
+
}, {}, {}>;
|
|
58
75
|
description: import("drizzle-orm/pg-core").PgColumn<{
|
|
59
76
|
name: "description";
|
|
60
77
|
tableName: "automations";
|
|
@@ -72,23 +89,49 @@ declare const automations: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
72
89
|
identity: undefined;
|
|
73
90
|
generated: undefined;
|
|
74
91
|
}, {}, {}>;
|
|
75
|
-
|
|
76
|
-
name: "
|
|
92
|
+
triggers: import("drizzle-orm/pg-core").PgColumn<{
|
|
93
|
+
name: "triggers";
|
|
77
94
|
tableName: "automations";
|
|
78
|
-
dataType: "
|
|
79
|
-
columnType: "
|
|
80
|
-
data: string;
|
|
81
|
-
driverParam: string;
|
|
95
|
+
dataType: "array";
|
|
96
|
+
columnType: "PgArray";
|
|
97
|
+
data: string[];
|
|
98
|
+
driverParam: string | string[];
|
|
82
99
|
notNull: true;
|
|
83
100
|
hasDefault: false;
|
|
84
101
|
isPrimaryKey: false;
|
|
85
102
|
isAutoincrement: false;
|
|
86
103
|
hasRuntimeDefault: false;
|
|
87
104
|
enumValues: [string, ...string[]];
|
|
88
|
-
baseColumn:
|
|
105
|
+
baseColumn: import("drizzle-orm").Column<{
|
|
106
|
+
name: "triggers";
|
|
107
|
+
tableName: "automations";
|
|
108
|
+
dataType: "string";
|
|
109
|
+
columnType: "PgText";
|
|
110
|
+
data: string;
|
|
111
|
+
driverParam: string;
|
|
112
|
+
notNull: false;
|
|
113
|
+
hasDefault: false;
|
|
114
|
+
isPrimaryKey: false;
|
|
115
|
+
isAutoincrement: false;
|
|
116
|
+
hasRuntimeDefault: false;
|
|
117
|
+
enumValues: [string, ...string[]];
|
|
118
|
+
baseColumn: never;
|
|
119
|
+
identity: undefined;
|
|
120
|
+
generated: undefined;
|
|
121
|
+
}, {}, {}>;
|
|
89
122
|
identity: undefined;
|
|
90
123
|
generated: undefined;
|
|
91
|
-
}, {}, {
|
|
124
|
+
}, {}, {
|
|
125
|
+
size: undefined;
|
|
126
|
+
baseBuilder: import("drizzle-orm/pg-core").PgColumnBuilder<{
|
|
127
|
+
name: "triggers";
|
|
128
|
+
dataType: "string";
|
|
129
|
+
columnType: "PgText";
|
|
130
|
+
data: string;
|
|
131
|
+
enumValues: [string, ...string[]];
|
|
132
|
+
driverParam: string;
|
|
133
|
+
}, {}, {}, import("drizzle-orm").ColumnBuilderExtraConfig>;
|
|
134
|
+
}>;
|
|
92
135
|
actions: import("drizzle-orm/pg-core").PgColumn<{
|
|
93
136
|
name: "actions";
|
|
94
137
|
tableName: "automations";
|
|
@@ -356,6 +399,40 @@ declare const automationRuns: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
|
356
399
|
identity: undefined;
|
|
357
400
|
generated: undefined;
|
|
358
401
|
}, {}, {}>;
|
|
402
|
+
createdAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
403
|
+
name: "created_at";
|
|
404
|
+
tableName: "automation_runs";
|
|
405
|
+
dataType: "date";
|
|
406
|
+
columnType: "PgTimestamp";
|
|
407
|
+
data: Date;
|
|
408
|
+
driverParam: string;
|
|
409
|
+
notNull: true;
|
|
410
|
+
hasDefault: true;
|
|
411
|
+
isPrimaryKey: false;
|
|
412
|
+
isAutoincrement: false;
|
|
413
|
+
hasRuntimeDefault: false;
|
|
414
|
+
enumValues: undefined;
|
|
415
|
+
baseColumn: never;
|
|
416
|
+
identity: undefined;
|
|
417
|
+
generated: undefined;
|
|
418
|
+
}, {}, {}>;
|
|
419
|
+
updatedAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
420
|
+
name: "updated_at";
|
|
421
|
+
tableName: "automation_runs";
|
|
422
|
+
dataType: "date";
|
|
423
|
+
columnType: "PgTimestamp";
|
|
424
|
+
data: Date;
|
|
425
|
+
driverParam: string;
|
|
426
|
+
notNull: true;
|
|
427
|
+
hasDefault: true;
|
|
428
|
+
isPrimaryKey: false;
|
|
429
|
+
isAutoincrement: false;
|
|
430
|
+
hasRuntimeDefault: false;
|
|
431
|
+
enumValues: undefined;
|
|
432
|
+
baseColumn: never;
|
|
433
|
+
identity: undefined;
|
|
434
|
+
generated: undefined;
|
|
435
|
+
}, {}, {}>;
|
|
359
436
|
};
|
|
360
437
|
dialect: "pg";
|
|
361
438
|
}>;
|
|
@@ -6,15 +6,16 @@ const automations = pgTable("automations", {
|
|
|
6
6
|
orgId: text("org_id").notNull().references(() => organizations.id),
|
|
7
7
|
id: text("id").notNull().$defaultFn(() => genId("automation")),
|
|
8
8
|
name: text("name").notNull(),
|
|
9
|
+
kind: text("kind", { enum: ["workflow", "webhook"] }).notNull().default("workflow"),
|
|
9
10
|
description: text("description"),
|
|
10
|
-
|
|
11
|
+
triggers: text("triggers").array().notNull(),
|
|
11
12
|
actions: jsonb("actions").$type().notNull(),
|
|
12
13
|
enabled: boolean("enabled").notNull().default(true),
|
|
13
14
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
14
|
-
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
15
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
15
16
|
}, (t) => ({
|
|
16
17
|
pk: primaryKey({ columns: [t.orgId, t.id] }),
|
|
17
|
-
|
|
18
|
+
triggersIdx: index("automations_triggers_idx").using("gin", t.triggers)
|
|
18
19
|
}));
|
|
19
20
|
const automationRuns = pgTable("automation_runs", {
|
|
20
21
|
orgId: text("org_id").notNull().references(() => organizations.id),
|
|
@@ -30,7 +31,9 @@ const automationRuns = pgTable("automation_runs", {
|
|
|
30
31
|
] }).notNull(),
|
|
31
32
|
actionResults: jsonb("action_results").$type().notNull().default([]),
|
|
32
33
|
startedAt: timestamp("started_at", { withTimezone: true }).notNull(),
|
|
33
|
-
finishedAt: timestamp("finished_at", { withTimezone: true })
|
|
34
|
+
finishedAt: timestamp("finished_at", { withTimezone: true }),
|
|
35
|
+
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
36
|
+
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
|
|
34
37
|
}, (t) => ({
|
|
35
38
|
pk: primaryKey({ columns: [t.orgId, t.id] }),
|
|
36
39
|
automationIdx: index("automation_runs_org_automation_idx").on(t.orgId, t.automationId),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"automations.js","names":[],"sources":["../../src/schema/automations.ts"],"sourcesContent":["// automations tables — org-scoped automation definitions and their run\n// history. `automations.actions` and `automation_runs.action_results` are\n// ordered jsonb blobs (contract types owned by @headless-lms/core/types); a run's\n// `event` is the triggering DomainEvent snapshot, stored verbatim.\nimport {
|
|
1
|
+
{"version":3,"file":"automations.js","names":[],"sources":["../../src/schema/automations.ts"],"sourcesContent":["// automations tables — org-scoped automation definitions and their run\n// history. `automations.actions` and `automation_runs.action_results` are\n// ordered jsonb blobs (contract types owned by @headless-lms/core/types); a run's\n// `event` is the triggering DomainEvent snapshot, stored verbatim.\nimport {\n pgTable,\n text,\n boolean,\n jsonb,\n timestamp,\n primaryKey,\n index,\n uniqueIndex,\n} from \"drizzle-orm/pg-core\";\nimport { genId } from \"@headless-lms/core/shared/id\";\nimport { organizations } from \"./organizations.js\";\nimport type {\n Automation,\n AutomationAction,\n AutomationActionResult,\n AutomationRun,\n} from \"@headless-lms/core/schemas\";\nimport type { DomainEvent } from \"@headless-lms/core/shared/ports\";\nimport type { Expect, NoDrift } from \"./drift.js\";\n\nexport const automations = pgTable(\n \"automations\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\")\n .notNull()\n .$defaultFn(() => genId(\"automation\")),\n name: text(\"name\").notNull(),\n kind: text(\"kind\", { enum: [\"workflow\", \"webhook\"] })\n .notNull()\n .default(\"workflow\"),\n description: text(\"description\"),\n triggers: text(\"triggers\").array().notNull(),\n actions: jsonb(\"actions\").$type<AutomationAction[]>().notNull(),\n enabled: boolean(\"enabled\").notNull().default(true),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true })\n .notNull()\n .defaultNow()\n .$onUpdate(() => new Date()),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n triggersIdx: index(\"automations_triggers_idx\").using(\"gin\", t.triggers),\n }),\n);\n\nexport const automationRuns = pgTable(\n \"automation_runs\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\")\n .notNull()\n .$defaultFn(() => genId(\"automationRun\")),\n automationId: text(\"automation_id\").notNull(),\n trigger: text(\"trigger\").notNull(),\n // At-least-once dedupe key for the triggering event; DB-internal, not part of the domain `AutomationRun` type.\n eventId: text(\"event_id\").notNull(),\n event: jsonb(\"event\").$type<DomainEvent>().notNull(),\n status: text(\"status\", { enum: [\"running\", \"completed\", \"failed\"] }).notNull(),\n actionResults: jsonb(\"action_results\").$type<AutomationActionResult[]>().notNull().default([]),\n startedAt: timestamp(\"started_at\", { withTimezone: true }).notNull(),\n finishedAt: timestamp(\"finished_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true })\n .notNull()\n .defaultNow()\n .$onUpdate(() => new Date()),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n automationIdx: index(\"automation_runs_org_automation_idx\").on(t.orgId, t.automationId),\n eventDedupeIdx: uniqueIndex(\"automation_runs_org_automation_event_idx\").on(\n t.orgId,\n t.automationId,\n t.eventId,\n ),\n }),\n);\n\ntype _AutomationsDrift = Expect<NoDrift<typeof automations.$inferSelect, Automation>>;\ntype _AutomationRunsDrift = Expect<NoDrift<typeof automationRuns.$inferSelect, AutomationRun>>;\n"],"mappings":";;;;AAyBA,MAAa,cAAc,QACzB,eACA;CACE,OAAO,KAAK,QAAQ,CAAC,CAClB,QAAQ,CAAC,CACT,iBAAiB,cAAc,EAAE;CACpC,IAAI,KAAK,IAAI,CAAC,CACX,QAAQ,CAAC,CACT,iBAAiB,MAAM,YAAY,CAAC;CACvC,MAAM,KAAK,MAAM,CAAC,CAAC,QAAQ;CAC3B,MAAM,KAAK,QAAQ,EAAE,MAAM,CAAC,YAAY,SAAS,EAAE,CAAC,CAAC,CAClD,QAAQ,CAAC,CACT,QAAQ,UAAU;CACrB,aAAa,KAAK,aAAa;CAC/B,UAAU,KAAK,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ;CAC3C,SAAS,MAAM,SAAS,CAAC,CAAC,MAA0B,CAAC,CAAC,QAAQ;CAC9D,SAAS,QAAQ,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,IAAI;CAClD,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW;CAChF,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CACvD,QAAQ,CAAC,CACT,WAAW,CAAC,CACZ,gCAAgB,IAAI,KAAK,CAAC;AAC/B,IACC,OAAO;CACN,IAAI,WAAW,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;CAC3C,aAAa,MAAM,0BAA0B,CAAC,CAAC,MAAM,OAAO,EAAE,QAAQ;AACxE,EACF;AAEA,MAAa,iBAAiB,QAC5B,mBACA;CACE,OAAO,KAAK,QAAQ,CAAC,CAClB,QAAQ,CAAC,CACT,iBAAiB,cAAc,EAAE;CACpC,IAAI,KAAK,IAAI,CAAC,CACX,QAAQ,CAAC,CACT,iBAAiB,MAAM,eAAe,CAAC;CAC1C,cAAc,KAAK,eAAe,CAAC,CAAC,QAAQ;CAC5C,SAAS,KAAK,SAAS,CAAC,CAAC,QAAQ;CAEjC,SAAS,KAAK,UAAU,CAAC,CAAC,QAAQ;CAClC,OAAO,MAAM,OAAO,CAAC,CAAC,MAAmB,CAAC,CAAC,QAAQ;CACnD,QAAQ,KAAK,UAAU,EAAE,MAAM;EAAC;EAAW;EAAa;CAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ;CAC7E,eAAe,MAAM,gBAAgB,CAAC,CAAC,MAAgC,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC7F,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CAAC,QAAQ;CACnE,YAAY,UAAU,eAAe,EAAE,cAAc,KAAK,CAAC;CAC3D,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW;CAChF,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CACvD,QAAQ,CAAC,CACT,WAAW,CAAC,CACZ,gCAAgB,IAAI,KAAK,CAAC;AAC/B,IACC,OAAO;CACN,IAAI,WAAW,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;CAC3C,eAAe,MAAM,oCAAoC,CAAC,CAAC,GAAG,EAAE,OAAO,EAAE,YAAY;CACrF,gBAAgB,YAAY,0CAA0C,CAAC,CAAC,GACtE,EAAE,OACF,EAAE,cACF,EAAE,OACJ;AACF,EACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"better-auth.js","names":[],"sources":["../../src/schema/better-auth.ts"],"sourcesContent":["// Better Auth's own tables. Every physical table is prefixed `ba_` so the auth\n// engine's storage is visibly separate from the domain's own tables (`users`,\n// `organizations`, …) it is mirrored into. The exported identifiers stay the\n// better-auth model names — that is the key `drizzleAdapter` looks each model\n// up by, so it must not carry the prefix.\nimport { pgTable, text, timestamp, boolean, index } from 'drizzle-orm/pg-core';\n\nexport const user = pgTable('ba_user', {\n id: text('id').primaryKey(),\n name: text('name').notNull(),\n email: text('email').notNull().unique(),\n emailVerified: boolean('email_verified')\n .$defaultFn(() => false)\n .notNull(),\n image: text('image'),\n createdAt: timestamp('created_at', { withTimezone: true })\n .$defaultFn(() => new Date())\n .notNull(),\n updatedAt: timestamp('updated_at', { withTimezone: true })\n .$defaultFn(() => new Date())\n .notNull(),\n});\n\nexport const session = pgTable('ba_session', {\n id: text('id').primaryKey(),\n expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),\n token: text('token').notNull().unique(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull(),\n ipAddress: text('ip_address'),\n userAgent: text('user_agent'),\n userId: text('user_id')\n .notNull()\n .references(() => user.id, { onDelete: 'cascade' }),\n // organization plugin: the org the session is currently acting in.\n activeOrganizationId: text('active_organization_id'),\n});\n\nexport const account = pgTable('ba_account', {\n id: text('id').primaryKey(),\n accountId: text('account_id').notNull(),\n providerId: text('provider_id').notNull(),\n userId: text('user_id')\n .notNull()\n .references(() => user.id, { onDelete: 'cascade' }),\n accessToken: text('access_token'),\n refreshToken: text('refresh_token'),\n idToken: text('id_token'),\n accessTokenExpiresAt: timestamp('access_token_expires_at', { withTimezone: true }),\n refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { withTimezone: true }),\n scope: text('scope'),\n password: text('password'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull(),\n});\n\nexport const verification = pgTable('ba_verification', {\n id: text('id').primaryKey(),\n identifier: text('identifier').notNull(),\n value: text('value').notNull(),\n expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).$defaultFn(() => new Date()),\n updatedAt: timestamp('updated_at', { withTimezone: true }).$defaultFn(() => new Date()),\n});\n\n// --- organization plugin tables ---\n\nexport const organization = pgTable('ba_organization', {\n id: text('id').primaryKey(),\n name: text('name').notNull(),\n slug: text('slug').notNull().unique(),\n logo: text('logo'),\n metadata: text('metadata'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n updatedAt: timestamp('updated_at', { withTimezone: true }),\n});\n\nexport const member = pgTable('ba_member', {\n id: text('id').primaryKey(),\n organizationId: text('organization_id')\n .notNull()\n .references(() => organization.id, { onDelete: 'cascade' }),\n userId: text('user_id')\n .notNull()\n .references(() => user.id, { onDelete: 'cascade' }),\n role: text('role').notNull().default('member'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n});\n\nexport const invitation = pgTable('ba_invitation', {\n id: text('id').primaryKey(),\n organizationId: text('organization_id')\n .notNull()\n .references(() => organization.id, { onDelete: 'cascade' }),\n email: text('email').notNull(),\n role: text('role').notNull(),\n status: text('status').notNull().default('pending'),\n expiresAt: timestamp('expires_at', { withTimezone: true }),\n inviterId: text('inviter_id')\n .notNull()\n .references(() => user.id, { onDelete: 'cascade' }),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n});\n\n// --- OAuth 2.1 provider tables (MCP). Owned by @better-auth/oauth-provider. ---\n// `reference_id` is the plugin's tenant slot: the organization the person chose\n// at consent. It is the only thing that tells a token which org it acts in.\nexport const oauthClient = pgTable(\n 'ba_oauth_client',\n {\n id: text('id').primaryKey(),\n clientId: text('client_id').notNull().unique(),\n clientSecret: text('client_secret'),\n disabled: boolean('disabled').default(false),\n skipConsent: boolean('skip_consent'),\n enableEndSession: boolean('enable_end_session'),\n subjectType: text('subject_type'),\n scopes: text('scopes').array(),\n userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),\n name: text('name'),\n uri: text('uri'),\n icon: text('icon'),\n contacts: text('contacts').array(),\n tos: text('tos'),\n policy: text('policy'),\n softwareId: text('software_id'),\n softwareVersion: text('software_version'),\n softwareStatement: text('software_statement'),\n redirectUris: text('redirect_uris').array().notNull(),\n postLogoutRedirectUris: text('post_logout_redirect_uris').array(),\n tokenEndpointAuthMethod: text('token_endpoint_auth_method'),\n grantTypes: text('grant_types').array(),\n responseTypes: text('response_types').array(),\n public: boolean('public'),\n type: text('type'),\n requirePkce: boolean('require_pkce'),\n referenceId: text('reference_id'),\n metadata: text('metadata'),\n createdAt: timestamp('created_at', { withTimezone: true }),\n updatedAt: timestamp('updated_at', { withTimezone: true }),\n },\n (t) => [index('ba_oauth_client_user_id_idx').on(t.userId)],\n);\n\nexport const oauthRefreshToken = pgTable(\n 'ba_oauth_refresh_token',\n {\n id: text('id').primaryKey(),\n token: text('token').notNull().unique(),\n clientId: text('client_id')\n .notNull()\n .references(() => oauthClient.clientId),\n sessionId: text('session_id').references(() => session.id, { onDelete: 'set null' }),\n userId: text('user_id')\n .notNull()\n .references(() => user.id, { onDelete: 'cascade' }),\n referenceId: text('reference_id'),\n expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n revoked: timestamp('revoked', { withTimezone: true }),\n authTime: timestamp('auth_time', { withTimezone: true }),\n scopes: text('scopes').array().notNull(),\n },\n (t) => [\n index('ba_oauth_refresh_token_client_id_idx').on(t.clientId),\n index('ba_oauth_refresh_token_session_id_idx').on(t.sessionId),\n index('ba_oauth_refresh_token_user_id_idx').on(t.userId),\n ],\n);\n\nexport const oauthAccessToken = pgTable(\n 'ba_oauth_access_token',\n {\n id: text('id').primaryKey(),\n token: text('token').notNull().unique(),\n clientId: text('client_id')\n .notNull()\n .references(() => oauthClient.clientId),\n sessionId: text('session_id').references(() => session.id, { onDelete: 'set null' }),\n userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),\n referenceId: text('reference_id'),\n refreshId: text('refresh_id').references(() => oauthRefreshToken.id),\n expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n scopes: text('scopes').array().notNull(),\n },\n (t) => [\n index('ba_oauth_access_token_client_id_idx').on(t.clientId),\n index('ba_oauth_access_token_session_id_idx').on(t.sessionId),\n index('ba_oauth_access_token_user_id_idx').on(t.userId),\n index('ba_oauth_access_token_refresh_id_idx').on(t.refreshId),\n ],\n);\n\nexport const oauthConsent = pgTable(\n 'ba_oauth_consent',\n {\n id: text('id').primaryKey(),\n clientId: text('client_id')\n .notNull()\n .references(() => oauthClient.clientId),\n userId: text('user_id').references(() => user.id, { onDelete: 'cascade' }),\n referenceId: text('reference_id'),\n scopes: text('scopes').array().notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull(),\n },\n (t) => [\n index('ba_oauth_consent_client_id_idx').on(t.clientId),\n index('ba_oauth_consent_user_id_idx').on(t.userId),\n ],\n);\n\n// better-auth's jwt plugin: the signing keys behind JWT access tokens.\nexport const jwks = pgTable('ba_jwks', {\n id: text('id').primaryKey(),\n publicKey: text('public_key').notNull(),\n privateKey: text('private_key').notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull(),\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"better-auth.js","names":[],"sources":["../../src/schema/better-auth.ts"],"sourcesContent":["// Better Auth's own tables. Every physical table is prefixed `ba_` so the auth\n// engine's storage is visibly separate from the domain's own tables (`users`,\n// `organizations`, …) it is mirrored into. The exported identifiers stay the\n// better-auth model names — that is the key `drizzleAdapter` looks each model\n// up by, so it must not carry the prefix.\nimport { pgTable, text, timestamp, boolean, index } from \"drizzle-orm/pg-core\";\n\nexport const user = pgTable(\"ba_user\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n email: text(\"email\").notNull().unique(),\n emailVerified: boolean(\"email_verified\")\n .$defaultFn(() => false)\n .notNull(),\n image: text(\"image\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true })\n .$defaultFn(() => new Date())\n .notNull(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true })\n .$defaultFn(() => new Date())\n .notNull(),\n});\n\nexport const session = pgTable(\"ba_session\", {\n id: text(\"id\").primaryKey(),\n expiresAt: timestamp(\"expires_at\", { withTimezone: true }).notNull(),\n token: text(\"token\").notNull().unique(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull(),\n ipAddress: text(\"ip_address\"),\n userAgent: text(\"user_agent\"),\n userId: text(\"user_id\")\n .notNull()\n .references(() => user.id, { onDelete: \"cascade\" }),\n // organization plugin: the org the session is currently acting in.\n activeOrganizationId: text(\"active_organization_id\"),\n});\n\nexport const account = pgTable(\"ba_account\", {\n id: text(\"id\").primaryKey(),\n accountId: text(\"account_id\").notNull(),\n providerId: text(\"provider_id\").notNull(),\n userId: text(\"user_id\")\n .notNull()\n .references(() => user.id, { onDelete: \"cascade\" }),\n accessToken: text(\"access_token\"),\n refreshToken: text(\"refresh_token\"),\n idToken: text(\"id_token\"),\n accessTokenExpiresAt: timestamp(\"access_token_expires_at\", { withTimezone: true }),\n refreshTokenExpiresAt: timestamp(\"refresh_token_expires_at\", { withTimezone: true }),\n scope: text(\"scope\"),\n password: text(\"password\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull(),\n});\n\nexport const verification = pgTable(\"ba_verification\", {\n id: text(\"id\").primaryKey(),\n identifier: text(\"identifier\").notNull(),\n value: text(\"value\").notNull(),\n expiresAt: timestamp(\"expires_at\", { withTimezone: true }).notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).$defaultFn(() => new Date()),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).$defaultFn(() => new Date()),\n});\n\n// --- organization plugin tables ---\n\nexport const organization = pgTable(\"ba_organization\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n slug: text(\"slug\").notNull().unique(),\n logo: text(\"logo\"),\n metadata: text(\"metadata\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }),\n});\n\nexport const member = pgTable(\"ba_member\", {\n id: text(\"id\").primaryKey(),\n organizationId: text(\"organization_id\")\n .notNull()\n .references(() => organization.id, { onDelete: \"cascade\" }),\n userId: text(\"user_id\")\n .notNull()\n .references(() => user.id, { onDelete: \"cascade\" }),\n role: text(\"role\").notNull().default(\"member\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n});\n\nexport const invitation = pgTable(\"ba_invitation\", {\n id: text(\"id\").primaryKey(),\n organizationId: text(\"organization_id\")\n .notNull()\n .references(() => organization.id, { onDelete: \"cascade\" }),\n email: text(\"email\").notNull(),\n role: text(\"role\").notNull(),\n status: text(\"status\").notNull().default(\"pending\"),\n expiresAt: timestamp(\"expires_at\", { withTimezone: true }),\n inviterId: text(\"inviter_id\")\n .notNull()\n .references(() => user.id, { onDelete: \"cascade\" }),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n});\n\n// --- OAuth 2.1 provider tables (MCP). Owned by @better-auth/oauth-provider. ---\n// `reference_id` is the plugin's tenant slot: the organization the person chose\n// at consent. It is the only thing that tells a token which org it acts in.\nexport const oauthClient = pgTable(\n \"ba_oauth_client\",\n {\n id: text(\"id\").primaryKey(),\n clientId: text(\"client_id\").notNull().unique(),\n clientSecret: text(\"client_secret\"),\n disabled: boolean(\"disabled\").default(false),\n skipConsent: boolean(\"skip_consent\"),\n enableEndSession: boolean(\"enable_end_session\"),\n subjectType: text(\"subject_type\"),\n scopes: text(\"scopes\").array(),\n userId: text(\"user_id\").references(() => user.id, { onDelete: \"cascade\" }),\n name: text(\"name\"),\n uri: text(\"uri\"),\n icon: text(\"icon\"),\n contacts: text(\"contacts\").array(),\n tos: text(\"tos\"),\n policy: text(\"policy\"),\n softwareId: text(\"software_id\"),\n softwareVersion: text(\"software_version\"),\n softwareStatement: text(\"software_statement\"),\n redirectUris: text(\"redirect_uris\").array().notNull(),\n postLogoutRedirectUris: text(\"post_logout_redirect_uris\").array(),\n tokenEndpointAuthMethod: text(\"token_endpoint_auth_method\"),\n grantTypes: text(\"grant_types\").array(),\n responseTypes: text(\"response_types\").array(),\n public: boolean(\"public\"),\n type: text(\"type\"),\n requirePkce: boolean(\"require_pkce\"),\n referenceId: text(\"reference_id\"),\n metadata: text(\"metadata\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }),\n },\n (t) => [index(\"ba_oauth_client_user_id_idx\").on(t.userId)],\n);\n\nexport const oauthRefreshToken = pgTable(\n \"ba_oauth_refresh_token\",\n {\n id: text(\"id\").primaryKey(),\n token: text(\"token\").notNull().unique(),\n clientId: text(\"client_id\")\n .notNull()\n .references(() => oauthClient.clientId),\n sessionId: text(\"session_id\").references(() => session.id, { onDelete: \"set null\" }),\n userId: text(\"user_id\")\n .notNull()\n .references(() => user.id, { onDelete: \"cascade\" }),\n referenceId: text(\"reference_id\"),\n expiresAt: timestamp(\"expires_at\", { withTimezone: true }).notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n revoked: timestamp(\"revoked\", { withTimezone: true }),\n authTime: timestamp(\"auth_time\", { withTimezone: true }),\n scopes: text(\"scopes\").array().notNull(),\n },\n (t) => [\n index(\"ba_oauth_refresh_token_client_id_idx\").on(t.clientId),\n index(\"ba_oauth_refresh_token_session_id_idx\").on(t.sessionId),\n index(\"ba_oauth_refresh_token_user_id_idx\").on(t.userId),\n ],\n);\n\nexport const oauthAccessToken = pgTable(\n \"ba_oauth_access_token\",\n {\n id: text(\"id\").primaryKey(),\n token: text(\"token\").notNull().unique(),\n clientId: text(\"client_id\")\n .notNull()\n .references(() => oauthClient.clientId),\n sessionId: text(\"session_id\").references(() => session.id, { onDelete: \"set null\" }),\n userId: text(\"user_id\").references(() => user.id, { onDelete: \"cascade\" }),\n referenceId: text(\"reference_id\"),\n refreshId: text(\"refresh_id\").references(() => oauthRefreshToken.id),\n expiresAt: timestamp(\"expires_at\", { withTimezone: true }).notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n scopes: text(\"scopes\").array().notNull(),\n },\n (t) => [\n index(\"ba_oauth_access_token_client_id_idx\").on(t.clientId),\n index(\"ba_oauth_access_token_session_id_idx\").on(t.sessionId),\n index(\"ba_oauth_access_token_user_id_idx\").on(t.userId),\n index(\"ba_oauth_access_token_refresh_id_idx\").on(t.refreshId),\n ],\n);\n\nexport const oauthConsent = pgTable(\n \"ba_oauth_consent\",\n {\n id: text(\"id\").primaryKey(),\n clientId: text(\"client_id\")\n .notNull()\n .references(() => oauthClient.clientId),\n userId: text(\"user_id\").references(() => user.id, { onDelete: \"cascade\" }),\n referenceId: text(\"reference_id\"),\n scopes: text(\"scopes\").array().notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull(),\n },\n (t) => [\n index(\"ba_oauth_consent_client_id_idx\").on(t.clientId),\n index(\"ba_oauth_consent_user_id_idx\").on(t.userId),\n ],\n);\n\n// better-auth's jwt plugin: the signing keys behind JWT access tokens.\nexport const jwks = pgTable(\"ba_jwks\", {\n id: text(\"id\").primaryKey(),\n publicKey: text(\"public_key\").notNull(),\n privateKey: text(\"private_key\").notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull(),\n});\n"],"mappings":"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"}
|