@headless-lms/adapter-db 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/README.md +79 -0
  2. package/dist/client.d.ts +1 -1
  3. package/dist/client.js.map +1 -1
  4. package/dist/index.d.ts +7 -5
  5. package/dist/index.js +8 -6
  6. package/dist/migrate.js.map +1 -1
  7. package/dist/repositories/admin.d.ts +23 -0
  8. package/dist/repositories/admin.js +393 -0
  9. package/dist/repositories/admin.js.map +1 -0
  10. package/dist/repositories/assets.d.ts +6 -2
  11. package/dist/repositories/assets.js +53 -9
  12. package/dist/repositories/assets.js.map +1 -1
  13. package/dist/repositories/automations.d.ts +12 -6
  14. package/dist/repositories/automations.js +79 -11
  15. package/dist/repositories/automations.js.map +1 -1
  16. package/dist/repositories/chats.d.ts +55 -0
  17. package/dist/repositories/chats.js +454 -0
  18. package/dist/repositories/chats.js.map +1 -0
  19. package/dist/repositories/content.d.ts +44 -7
  20. package/dist/repositories/content.js +458 -24
  21. package/dist/repositories/content.js.map +1 -1
  22. package/dist/repositories/credentials.js +2 -0
  23. package/dist/repositories/credentials.js.map +1 -1
  24. package/dist/repositories/discussion.d.ts +11 -4
  25. package/dist/repositories/discussion.js +72 -15
  26. package/dist/repositories/discussion.js.map +1 -1
  27. package/dist/repositories/entitlements.d.ts +36 -10
  28. package/dist/repositories/entitlements.js +238 -23
  29. package/dist/repositories/entitlements.js.map +1 -1
  30. package/dist/repositories/grant-covers.d.ts +20 -0
  31. package/dist/repositories/grant-covers.js +55 -0
  32. package/dist/repositories/grant-covers.js.map +1 -0
  33. package/dist/repositories/identity.d.ts +4 -2
  34. package/dist/repositories/identity.js +34 -1
  35. package/dist/repositories/identity.js.map +1 -1
  36. package/dist/repositories/integrations.d.ts +1 -1
  37. package/dist/repositories/integrations.js +2 -0
  38. package/dist/repositories/integrations.js.map +1 -1
  39. package/dist/repositories/member.d.ts +16 -0
  40. package/dist/repositories/member.js +63 -0
  41. package/dist/repositories/member.js.map +1 -0
  42. package/dist/repositories/members.js +4 -2
  43. package/dist/repositories/members.js.map +1 -1
  44. package/dist/repositories/notifications.d.ts +23 -0
  45. package/dist/repositories/notifications.js +70 -0
  46. package/dist/repositories/notifications.js.map +1 -0
  47. package/dist/repositories/org-user-profile.d.ts +214 -1
  48. package/dist/repositories/org-user-profile.js +23 -2
  49. package/dist/repositories/org-user-profile.js.map +1 -1
  50. package/dist/repositories/organizations.d.ts +6 -7
  51. package/dist/repositories/organizations.js +45 -52
  52. package/dist/repositories/organizations.js.map +1 -1
  53. package/dist/repositories/outbox.js +3 -0
  54. package/dist/repositories/outbox.js.map +1 -1
  55. package/dist/repositories/pg-errors.d.ts +53 -5
  56. package/dist/repositories/pg-errors.js +99 -8
  57. package/dist/repositories/pg-errors.js.map +1 -1
  58. package/dist/repositories/posts.d.ts +39 -0
  59. package/dist/repositories/posts.js +255 -0
  60. package/dist/repositories/posts.js.map +1 -0
  61. package/dist/repositories/progress.d.ts +1 -1
  62. package/dist/repositories/progress.js +3 -0
  63. package/dist/repositories/progress.js.map +1 -1
  64. package/dist/repositories/settings.js +2 -0
  65. package/dist/repositories/settings.js.map +1 -1
  66. package/dist/schema/assets.d.ts +36 -2
  67. package/dist/schema/assets.js +12 -5
  68. package/dist/schema/assets.js.map +1 -1
  69. package/dist/schema/automations.d.ts +123 -8
  70. package/dist/schema/automations.js +11 -4
  71. package/dist/schema/automations.js.map +1 -1
  72. package/dist/schema/better-auth.d.ts +1613 -238
  73. package/dist/schema/better-auth.js +107 -22
  74. package/dist/schema/better-auth.js.map +1 -1
  75. package/dist/schema/content.d.ts +909 -22
  76. package/dist/schema/content.js +157 -13
  77. package/dist/schema/content.js.map +1 -1
  78. package/dist/schema/credentials.js +1 -1
  79. package/dist/schema/credentials.js.map +1 -1
  80. package/dist/schema/discussion.d.ts +2079 -55
  81. package/dist/schema/discussion.js +430 -13
  82. package/dist/schema/discussion.js.map +1 -1
  83. package/dist/schema/entitlements.d.ts +484 -2
  84. package/dist/schema/entitlements.js +121 -6
  85. package/dist/schema/entitlements.js.map +1 -1
  86. package/dist/schema/identity.d.ts +517 -1
  87. package/dist/schema/identity.js +118 -2
  88. package/dist/schema/identity.js.map +1 -1
  89. package/dist/schema/index.d.ts +6 -6
  90. package/dist/schema/index.js +32 -7
  91. package/dist/schema/index.js.map +1 -1
  92. package/dist/schema/integrations.js +1 -1
  93. package/dist/schema/integrations.js.map +1 -1
  94. package/dist/schema/organizations.d.ts +21 -4
  95. package/dist/schema/organizations.js +4 -1
  96. package/dist/schema/organizations.js.map +1 -1
  97. package/dist/schema/outbox.d.ts +17 -0
  98. package/dist/schema/outbox.js +1 -0
  99. package/dist/schema/outbox.js.map +1 -1
  100. package/dist/schema/progress.d.ts +17 -0
  101. package/dist/schema/progress.js +1 -0
  102. package/dist/schema/progress.js.map +1 -1
  103. package/dist/schema/settings.js.map +1 -1
  104. package/dist/unit-of-work.js +7 -2
  105. package/dist/unit-of-work.js.map +1 -1
  106. package/drizzle/0000_baseline.sql +0 -1
  107. package/drizzle/0001_new_sentry.sql +43 -0
  108. package/drizzle/0002_automation_kinds.sql +7 -0
  109. package/drizzle/0003_community.sql +340 -0
  110. package/drizzle/0004_assets_sources.sql +1 -0
  111. package/drizzle/0005_notices_and_seed_index.sql +12 -0
  112. package/drizzle/0006_community_review_fixes.sql +24 -0
  113. package/drizzle/0007_notifications.sql +37 -0
  114. package/drizzle/0008_content_access.sql +13 -0
  115. package/drizzle/0009_community_indexes.sql +56 -0
  116. package/drizzle/0010_mcp_access.sql +114 -0
  117. package/drizzle/meta/0000_snapshot.json +167 -597
  118. package/drizzle/meta/0001_snapshot.json +3769 -0
  119. package/drizzle/meta/0002_snapshot.json +3770 -0
  120. package/drizzle/meta/0003_snapshot.json +7061 -0
  121. package/drizzle/meta/0004_snapshot.json +7068 -0
  122. package/drizzle/meta/0005_snapshot.json +7180 -0
  123. package/drizzle/meta/0006_snapshot.json +7298 -0
  124. package/drizzle/meta/0007_snapshot.json +7620 -0
  125. package/drizzle/meta/0008_snapshot.json +7666 -0
  126. package/drizzle/meta/0009_snapshot.json +8185 -0
  127. package/drizzle/meta/0010_snapshot.json +8813 -0
  128. package/drizzle/meta/_journal.json +71 -1
  129. package/package.json +3 -2
  130. package/dist/repositories/dashboard.d.ts +0 -14
  131. package/dist/repositories/dashboard.js +0 -55
  132. package/dist/repositories/dashboard.js.map +0 -1
  133. package/dist/repositories/learn.d.ts +0 -19
  134. package/dist/repositories/learn.js +0 -54
  135. package/dist/repositories/learn.js.map +0 -1
  136. package/dist/repositories/students.d.ts +0 -15
  137. package/dist/repositories/students.js +0 -83
  138. package/dist/repositories/students.js.map +0 -1
@@ -5,9 +5,79 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "7",
8
- "when": 1785756396376,
8
+ "when": 1786612033493,
9
9
  "tag": "0000_baseline",
10
10
  "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "7",
15
+ "when": 1786692996002,
16
+ "tag": "0001_new_sentry",
17
+ "breakpoints": true
18
+ },
19
+ {
20
+ "idx": 2,
21
+ "version": "7",
22
+ "when": 1790675689629,
23
+ "tag": "0002_automation_kinds",
24
+ "breakpoints": true
25
+ },
26
+ {
27
+ "idx": 3,
28
+ "version": "7",
29
+ "when": 1792574273608,
30
+ "tag": "0003_community",
31
+ "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "7",
36
+ "when": 1792652068527,
37
+ "tag": "0004_assets_sources",
38
+ "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "7",
43
+ "when": 1792654657973,
44
+ "tag": "0005_notices_and_seed_index",
45
+ "breakpoints": true
46
+ },
47
+ {
48
+ "idx": 6,
49
+ "version": "7",
50
+ "when": 1792664309207,
51
+ "tag": "0006_community_review_fixes",
52
+ "breakpoints": true
53
+ },
54
+ {
55
+ "idx": 7,
56
+ "version": "7",
57
+ "when": 1793025210130,
58
+ "tag": "0007_notifications",
59
+ "breakpoints": true
60
+ },
61
+ {
62
+ "idx": 8,
63
+ "version": "7",
64
+ "when": 1793267692172,
65
+ "tag": "0008_content_access",
66
+ "breakpoints": true
67
+ },
68
+ {
69
+ "idx": 9,
70
+ "version": "7",
71
+ "when": 1793271619441,
72
+ "tag": "0009_community_indexes",
73
+ "breakpoints": true
74
+ },
75
+ {
76
+ "idx": 10,
77
+ "version": "7",
78
+ "when": 1793328918383,
79
+ "tag": "0010_mcp_access",
80
+ "breakpoints": true
11
81
  }
12
82
  ]
13
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@headless-lms/adapter-db",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -21,7 +21,7 @@
21
21
  "dependencies": {
22
22
  "drizzle-orm": "^0.45.2",
23
23
  "pg": "^8.13.1",
24
- "@headless-lms/core": "0.1.0"
24
+ "@headless-lms/core": "0.3.0"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "^22.10.2",
@@ -50,6 +50,7 @@
50
50
  "build": "tsdown",
51
51
  "test": "vitest run",
52
52
  "typecheck": "tsc -p tsconfig.json --noEmit",
53
+ "db:push": "tsx --env-file=../../.env node_modules/drizzle-kit/bin.cjs push",
53
54
  "db:generate": "tsx --env-file=../../.env node_modules/drizzle-kit/bin.cjs generate",
54
55
  "db:migrate": "tsx --env-file=../../.env node_modules/drizzle-kit/bin.cjs migrate"
55
56
  }
@@ -1,14 +0,0 @@
1
- import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
- import { Logger } from "@headless-lms/core/shared/ports";
3
- import { DashboardReportRepository, EnrollmentPoint, OverviewStats } from "@headless-lms/core/reporting/dashboard";
4
- //#region src/repositories/dashboard.d.ts
5
- declare class DrizzleDashboardRepository implements DashboardReportRepository {
6
- private readonly db;
7
- private readonly logger;
8
- constructor(db: NodePgDatabase, logger?: Logger);
9
- overview(orgId: string): Promise<OverviewStats>;
10
- enrollments(orgId: string, days: number): Promise<EnrollmentPoint[]>;
11
- }
12
- //#endregion
13
- export { DrizzleDashboardRepository };
14
- //# sourceMappingURL=dashboard.d.ts.map
@@ -1,55 +0,0 @@
1
- import { courses } from "../schema/content.js";
2
- import { entitlements } from "../schema/entitlements.js";
3
- import "../schema/index.js";
4
- import { and, eq, gte, isNull, or, sql } from "drizzle-orm";
5
- import { noopLogger } from "@headless-lms/core/shared/logger";
6
- //#region src/repositories/dashboard.ts
7
- var DrizzleDashboardRepository = class {
8
- db;
9
- logger;
10
- constructor(db, logger = noopLogger) {
11
- this.db = db;
12
- this.logger = logger;
13
- }
14
- async overview(orgId) {
15
- const [courseCounts] = await this.db.select({
16
- published: sql`count(*) filter (where ${courses.status} = 'published')`,
17
- draft: sql`count(*) filter (where ${courses.status} = 'draft')`
18
- }).from(courses).where(eq(courses.orgId, orgId));
19
- const effectiveActive = and(eq(entitlements.orgId, orgId), eq(entitlements.status, "active"), or(isNull(entitlements.expiresAt), gte(entitlements.expiresAt, sql`now()`)));
20
- const [entitlementCounts] = await this.db.select({
21
- activeStudents: sql`count(distinct ${entitlements.orgUserId})`,
22
- expiringSoon: sql`count(*) filter (where ${entitlements.expiresAt} is not null and ${entitlements.expiresAt} < now() + interval '14 days')`
23
- }).from(entitlements).where(effectiveActive);
24
- return {
25
- publishedCourses: Number(courseCounts?.published ?? 0),
26
- draftCourses: Number(courseCounts?.draft ?? 0),
27
- activeStudents: Number(entitlementCounts?.activeStudents ?? 0),
28
- expiringSoon: Number(entitlementCounts?.expiringSoon ?? 0)
29
- };
30
- }
31
- async enrollments(orgId, days) {
32
- return (await this.db.execute(sql`
33
- select
34
- to_char(d.day, 'YYYY-MM-DD') as date,
35
- count(${entitlements.id})::int as count
36
- from generate_series(
37
- (now() at time zone 'utc')::date - (${days} - 1),
38
- (now() at time zone 'utc')::date,
39
- interval '1 day'
40
- ) as d(day)
41
- left join ${entitlements}
42
- on ${entitlements.orgId} = ${orgId}
43
- and (${entitlements.grantedAt} at time zone 'utc')::date = d.day::date
44
- group by d.day
45
- order by d.day
46
- `)).rows.map((r) => ({
47
- date: r.date,
48
- count: Number(r.count)
49
- }));
50
- }
51
- };
52
- //#endregion
53
- export { DrizzleDashboardRepository };
54
-
55
- //# sourceMappingURL=dashboard.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dashboard.js","names":[],"sources":["../../src/repositories/dashboard.ts"],"sourcesContent":["// dashboard — Drizzle repository (implements the core outbound port).\n// Back-office overview counts, every figure scoped to the active org. An\n// entitlement is \"effective-active\" when status='active' and it has not expired\n// (expires_at null or in the future) — expiry is derived at read time.\nimport { and, eq, gte, isNull, or, sql } from 'drizzle-orm';\nimport type { NodePgDatabase } from 'drizzle-orm/node-postgres';\nimport type {\n DashboardReportRepository,\n EnrollmentPoint,\n OverviewStats,\n} from '@headless-lms/core/reporting/dashboard';\nimport { courses, entitlements } from '../schema/index.js';\nimport type { Logger } from '@headless-lms/core/shared/ports';\nimport { noopLogger } from '@headless-lms/core/shared/logger';\n\nexport class DrizzleDashboardRepository implements DashboardReportRepository {\n constructor(\n private readonly db: NodePgDatabase,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n async overview(orgId: string): Promise<OverviewStats> {\n const [courseCounts] = await this.db\n .select({\n published: sql<number>`count(*) filter (where ${courses.status} = 'published')`,\n draft: sql<number>`count(*) filter (where ${courses.status} = 'draft')`,\n })\n .from(courses)\n .where(eq(courses.orgId, orgId));\n\n const effectiveActive = and(\n eq(entitlements.orgId, orgId),\n eq(entitlements.status, 'active'),\n or(isNull(entitlements.expiresAt), gte(entitlements.expiresAt, sql`now()`)),\n );\n\n const [entitlementCounts] = await this.db\n .select({\n activeStudents: sql<number>`count(distinct ${entitlements.orgUserId})`,\n expiringSoon: sql<number>`count(*) filter (where ${entitlements.expiresAt} is not null and ${entitlements.expiresAt} < now() + interval '14 days')`,\n })\n .from(entitlements)\n .where(effectiveActive);\n\n return {\n publishedCourses: Number(courseCounts?.published ?? 0),\n draftCourses: Number(courseCounts?.draft ?? 0),\n activeStudents: Number(entitlementCounts?.activeStudents ?? 0),\n expiringSoon: Number(entitlementCounts?.expiringSoon ?? 0),\n };\n }\n\n // Grants per day over the trailing window, zero-filled so the series has one\n // point per calendar day (UTC) even when nothing was granted.\n async enrollments(orgId: string, days: number): Promise<EnrollmentPoint[]> {\n const result = await this.db.execute(sql`\n select\n to_char(d.day, 'YYYY-MM-DD') as date,\n count(${entitlements.id})::int as count\n from generate_series(\n (now() at time zone 'utc')::date - (${days} - 1),\n (now() at time zone 'utc')::date,\n interval '1 day'\n ) as d(day)\n left join ${entitlements}\n on ${entitlements.orgId} = ${orgId}\n and (${entitlements.grantedAt} at time zone 'utc')::date = d.day::date\n group by d.day\n order by d.day\n `);\n const rows = result.rows as { date: string; count: number }[];\n return rows.map((r) => ({ date: r.date, count: Number(r.count) }));\n }\n}\n"],"mappings":";;;;;;AAeA,IAAa,6BAAb,MAA6E;CAExD;CACA;CAFnB,YACE,IACA,SAAkC,YAClC;EAFiB,KAAA,KAAA;EACA,KAAA,SAAA;CAChB;CAEH,MAAM,SAAS,OAAuC;EACpD,MAAM,CAAC,gBAAgB,MAAM,KAAK,GAC/B,OAAO;GACN,WAAW,GAAW,0BAA0B,QAAQ,OAAO;GAC/D,OAAO,GAAW,0BAA0B,QAAQ,OAAO;EAC7D,CAAC,CAAC,CACD,KAAK,OAAO,CAAC,CACb,MAAM,GAAG,QAAQ,OAAO,KAAK,CAAC;EAEjC,MAAM,kBAAkB,IACtB,GAAG,aAAa,OAAO,KAAK,GAC5B,GAAG,aAAa,QAAQ,QAAQ,GAChC,GAAG,OAAO,aAAa,SAAS,GAAG,IAAI,aAAa,WAAW,GAAG,OAAO,CAAC,CAC5E;EAEA,MAAM,CAAC,qBAAqB,MAAM,KAAK,GACpC,OAAO;GACN,gBAAgB,GAAW,kBAAkB,aAAa,UAAU;GACpE,cAAc,GAAW,0BAA0B,aAAa,UAAU,mBAAmB,aAAa,UAAU;EACtH,CAAC,CAAC,CACD,KAAK,YAAY,CAAC,CAClB,MAAM,eAAe;EAExB,OAAO;GACL,kBAAkB,OAAO,cAAc,aAAa,CAAC;GACrD,cAAc,OAAO,cAAc,SAAS,CAAC;GAC7C,gBAAgB,OAAO,mBAAmB,kBAAkB,CAAC;GAC7D,cAAc,OAAO,mBAAmB,gBAAgB,CAAC;EAC3D;CACF;CAIA,MAAM,YAAY,OAAe,MAA0C;EAiBzE,QADa,MAfQ,KAAK,GAAG,QAAQ,GAAG;;;gBAG5B,aAAa,GAAG;;8CAEc,KAAK;;;;kBAIjC,aAAa;aAClB,aAAa,MAAM,KAAK,MAAM;eAC5B,aAAa,UAAU;;;KAGjC,EAAA,CACmB,KACR,KAAK,OAAO;GAAE,MAAM,EAAE;GAAM,OAAO,OAAO,EAAE,KAAK;EAAE,EAAE;CACnE;AACF"}
@@ -1,19 +0,0 @@
1
- import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
- import { Logger } from "@headless-lms/core/shared/ports";
3
- import { ContentRef, LearnEntitlementReader } from "@headless-lms/core/reporting/learn";
4
- //#region src/repositories/learn.d.ts
5
- declare class DrizzleLearnRepository implements LearnEntitlementReader {
6
- private readonly db;
7
- private readonly logger;
8
- constructor(db: NodePgDatabase, logger?: Logger);
9
- private baseFilters;
10
- activeRefs(orgId: string, orgUserId: string): Promise<ContentRef[]>;
11
- activeRef(orgId: string, orgUserId: string, courseId: string): Promise<ContentRef | null>;
12
- private downloadFilters;
13
- activeDownloadRefs(orgId: string, orgUserId: string): Promise<ContentRef[]>;
14
- activeDownloadRef(orgId: string, orgUserId: string, downloadId: string): Promise<ContentRef | null>;
15
- downloadHasAsset(orgId: string, downloadId: string, assetId: string): Promise<boolean>;
16
- }
17
- //#endregion
18
- export { DrizzleLearnRepository };
19
- //# sourceMappingURL=learn.d.ts.map
@@ -1,54 +0,0 @@
1
- import { courses, downloadAssets, downloads } from "../schema/content.js";
2
- import { entitlements } from "../schema/entitlements.js";
3
- import "../schema/index.js";
4
- import { and, eq, gt, isNull, or, sql } from "drizzle-orm";
5
- import { noopLogger } from "@headless-lms/core/shared/logger";
6
- //#region src/repositories/learn.ts
7
- var DrizzleLearnRepository = class {
8
- db;
9
- logger;
10
- constructor(db, logger = noopLogger) {
11
- this.db = db;
12
- this.logger = logger;
13
- }
14
- baseFilters(orgId, orgUserId) {
15
- return and(eq(entitlements.orgId, orgId), eq(entitlements.orgUserId, orgUserId), eq(entitlements.status, "active"), or(isNull(entitlements.expiresAt), gt(entitlements.expiresAt, sql`now()`)), eq(courses.status, "published"));
16
- }
17
- async activeRefs(orgId, orgUserId) {
18
- return await this.db.select({
19
- orgId: entitlements.orgId,
20
- contentId: entitlements.contentId
21
- }).from(entitlements).innerJoin(courses, and(eq(courses.orgId, entitlements.orgId), eq(courses.id, entitlements.contentId))).where(this.baseFilters(orgId, orgUserId));
22
- }
23
- async activeRef(orgId, orgUserId, courseId) {
24
- const [row] = await this.db.select({
25
- orgId: entitlements.orgId,
26
- contentId: entitlements.contentId
27
- }).from(entitlements).innerJoin(courses, and(eq(courses.orgId, entitlements.orgId), eq(courses.id, entitlements.contentId))).where(and(this.baseFilters(orgId, orgUserId), eq(entitlements.contentId, courseId))).limit(1);
28
- return row ?? null;
29
- }
30
- downloadFilters(orgId, orgUserId) {
31
- return and(eq(entitlements.orgId, orgId), eq(entitlements.orgUserId, orgUserId), eq(entitlements.status, "active"), or(isNull(entitlements.expiresAt), gt(entitlements.expiresAt, sql`now()`)), eq(downloads.status, "published"));
32
- }
33
- async activeDownloadRefs(orgId, orgUserId) {
34
- return this.db.select({
35
- orgId: entitlements.orgId,
36
- contentId: entitlements.contentId
37
- }).from(entitlements).innerJoin(downloads, and(eq(downloads.orgId, entitlements.orgId), eq(downloads.id, entitlements.contentId))).where(this.downloadFilters(orgId, orgUserId));
38
- }
39
- async activeDownloadRef(orgId, orgUserId, downloadId) {
40
- const [row] = await this.db.select({
41
- orgId: entitlements.orgId,
42
- contentId: entitlements.contentId
43
- }).from(entitlements).innerJoin(downloads, and(eq(downloads.orgId, entitlements.orgId), eq(downloads.id, entitlements.contentId))).where(and(this.downloadFilters(orgId, orgUserId), eq(entitlements.contentId, downloadId))).limit(1);
44
- return row ?? null;
45
- }
46
- async downloadHasAsset(orgId, downloadId, assetId) {
47
- const [row] = await this.db.select({ id: downloadAssets.id }).from(downloadAssets).where(and(eq(downloadAssets.orgId, orgId), eq(downloadAssets.downloadId, downloadId), eq(downloadAssets.assetId, assetId))).limit(1);
48
- return row !== void 0;
49
- }
50
- };
51
- //#endregion
52
- export { DrizzleLearnRepository };
53
-
54
- //# sourceMappingURL=learn.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"learn.js","names":[],"sources":["../../src/repositories/learn.ts"],"sourcesContent":["// learn — Drizzle read repo (implements the reporting/learn outbound port).\n// Returns the (org, content) refs a student is ACTIVELY entitled to and whose\n// course is PUBLISHED. \"Active\" excludes revoked and expired grants (expiry is\n// derived from expires_at at read time — no row flip). Org-scoped: the portal\n// org bounds the read (`entitlements.org_id`), so no cross-org grants leak.\n// The inner join to `courses` on the content id restricts to course grants —\n// no explicit content-type filter needed.\nimport { and, eq, gt, isNull, or, sql, type SQL } from 'drizzle-orm';\nimport type { NodePgDatabase } from 'drizzle-orm/node-postgres';\nimport type { LearnEntitlementReader, ContentRef } from '@headless-lms/core/reporting/learn';\nimport { entitlements, courses, downloads, downloadAssets } from '../schema/index.js';\nimport type { Logger } from '@headless-lms/core/shared/ports';\nimport { noopLogger } from '@headless-lms/core/shared/logger';\n\nexport class DrizzleLearnRepository implements LearnEntitlementReader {\n constructor(\n private readonly db: NodePgDatabase,\n private readonly logger: Logger = noopLogger,\n ) {}\n\n private baseFilters(orgId: string, orgUserId: string): SQL {\n return and(\n eq(entitlements.orgId, orgId),\n eq(entitlements.orgUserId, orgUserId),\n eq(entitlements.status, 'active'),\n or(isNull(entitlements.expiresAt), gt(entitlements.expiresAt, sql`now()`))!,\n eq(courses.status, 'published'),\n )!;\n }\n\n async activeRefs(orgId: string, orgUserId: string): Promise<ContentRef[]> {\n const rows = await this.db\n .select({ orgId: entitlements.orgId, contentId: entitlements.contentId })\n .from(entitlements)\n .innerJoin(\n courses,\n and(eq(courses.orgId, entitlements.orgId), eq(courses.id, entitlements.contentId)),\n )\n .where(this.baseFilters(orgId, orgUserId));\n return rows;\n }\n\n async activeRef(orgId: string, orgUserId: string, courseId: string): Promise<ContentRef | null> {\n const [row] = await this.db\n .select({ orgId: entitlements.orgId, contentId: entitlements.contentId })\n .from(entitlements)\n .innerJoin(\n courses,\n and(eq(courses.orgId, entitlements.orgId), eq(courses.id, entitlements.contentId)),\n )\n .where(and(this.baseFilters(orgId, orgUserId), eq(entitlements.contentId, courseId)))\n .limit(1);\n return row ?? null;\n }\n\n private downloadFilters(orgId: string, orgUserId: string): SQL {\n return and(\n eq(entitlements.orgId, orgId),\n eq(entitlements.orgUserId, orgUserId),\n eq(entitlements.status, 'active'),\n or(isNull(entitlements.expiresAt), gt(entitlements.expiresAt, sql`now()`))!,\n eq(downloads.status, 'published'),\n )!;\n }\n\n async activeDownloadRefs(orgId: string, orgUserId: string): Promise<ContentRef[]> {\n return this.db\n .select({ orgId: entitlements.orgId, contentId: entitlements.contentId })\n .from(entitlements)\n .innerJoin(\n downloads,\n and(eq(downloads.orgId, entitlements.orgId), eq(downloads.id, entitlements.contentId)),\n )\n .where(this.downloadFilters(orgId, orgUserId));\n }\n\n async activeDownloadRef(\n orgId: string,\n orgUserId: string,\n downloadId: string,\n ): Promise<ContentRef | null> {\n const [row] = await this.db\n .select({ orgId: entitlements.orgId, contentId: entitlements.contentId })\n .from(entitlements)\n .innerJoin(\n downloads,\n and(eq(downloads.orgId, entitlements.orgId), eq(downloads.id, entitlements.contentId)),\n )\n .where(and(this.downloadFilters(orgId, orgUserId), eq(entitlements.contentId, downloadId)))\n .limit(1);\n return row ?? null;\n }\n\n async downloadHasAsset(orgId: string, downloadId: string, assetId: string): Promise<boolean> {\n const [row] = await this.db\n .select({ id: downloadAssets.id })\n .from(downloadAssets)\n .where(\n and(\n eq(downloadAssets.orgId, orgId),\n eq(downloadAssets.downloadId, downloadId),\n eq(downloadAssets.assetId, assetId),\n ),\n )\n .limit(1);\n return row !== undefined;\n }\n}\n"],"mappings":";;;;;;AAcA,IAAa,yBAAb,MAAsE;CAEjD;CACA;CAFnB,YACE,IACA,SAAkC,YAClC;EAFiB,KAAA,KAAA;EACA,KAAA,SAAA;CAChB;CAEH,YAAoB,OAAe,WAAwB;EACzD,OAAO,IACL,GAAG,aAAa,OAAO,KAAK,GAC5B,GAAG,aAAa,WAAW,SAAS,GACpC,GAAG,aAAa,QAAQ,QAAQ,GAChC,GAAG,OAAO,aAAa,SAAS,GAAG,GAAG,aAAa,WAAW,GAAG,OAAO,CAAC,GACzE,GAAG,QAAQ,QAAQ,WAAW,CAChC;CACF;CAEA,MAAM,WAAW,OAAe,WAA0C;EASxE,OAAO,MARY,KAAK,GACrB,OAAO;GAAE,OAAO,aAAa;GAAO,WAAW,aAAa;EAAU,CAAC,CAAC,CACxE,KAAK,YAAY,CAAC,CAClB,UACC,SACA,IAAI,GAAG,QAAQ,OAAO,aAAa,KAAK,GAAG,GAAG,QAAQ,IAAI,aAAa,SAAS,CAAC,CACnF,CAAC,CACA,MAAM,KAAK,YAAY,OAAO,SAAS,CAAC;CAE7C;CAEA,MAAM,UAAU,OAAe,WAAmB,UAA8C;EAC9F,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO;GAAE,OAAO,aAAa;GAAO,WAAW,aAAa;EAAU,CAAC,CAAC,CACxE,KAAK,YAAY,CAAC,CAClB,UACC,SACA,IAAI,GAAG,QAAQ,OAAO,aAAa,KAAK,GAAG,GAAG,QAAQ,IAAI,aAAa,SAAS,CAAC,CACnF,CAAC,CACA,MAAM,IAAI,KAAK,YAAY,OAAO,SAAS,GAAG,GAAG,aAAa,WAAW,QAAQ,CAAC,CAAC,CAAC,CACpF,MAAM,CAAC;EACV,OAAO,OAAO;CAChB;CAEA,gBAAwB,OAAe,WAAwB;EAC7D,OAAO,IACL,GAAG,aAAa,OAAO,KAAK,GAC5B,GAAG,aAAa,WAAW,SAAS,GACpC,GAAG,aAAa,QAAQ,QAAQ,GAChC,GAAG,OAAO,aAAa,SAAS,GAAG,GAAG,aAAa,WAAW,GAAG,OAAO,CAAC,GACzE,GAAG,UAAU,QAAQ,WAAW,CAClC;CACF;CAEA,MAAM,mBAAmB,OAAe,WAA0C;EAChF,OAAO,KAAK,GACT,OAAO;GAAE,OAAO,aAAa;GAAO,WAAW,aAAa;EAAU,CAAC,CAAC,CACxE,KAAK,YAAY,CAAC,CAClB,UACC,WACA,IAAI,GAAG,UAAU,OAAO,aAAa,KAAK,GAAG,GAAG,UAAU,IAAI,aAAa,SAAS,CAAC,CACvF,CAAC,CACA,MAAM,KAAK,gBAAgB,OAAO,SAAS,CAAC;CACjD;CAEA,MAAM,kBACJ,OACA,WACA,YAC4B;EAC5B,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO;GAAE,OAAO,aAAa;GAAO,WAAW,aAAa;EAAU,CAAC,CAAC,CACxE,KAAK,YAAY,CAAC,CAClB,UACC,WACA,IAAI,GAAG,UAAU,OAAO,aAAa,KAAK,GAAG,GAAG,UAAU,IAAI,aAAa,SAAS,CAAC,CACvF,CAAC,CACA,MAAM,IAAI,KAAK,gBAAgB,OAAO,SAAS,GAAG,GAAG,aAAa,WAAW,UAAU,CAAC,CAAC,CAAC,CAC1F,MAAM,CAAC;EACV,OAAO,OAAO;CAChB;CAEA,MAAM,iBAAiB,OAAe,YAAoB,SAAmC;EAC3F,MAAM,CAAC,OAAO,MAAM,KAAK,GACtB,OAAO,EAAE,IAAI,eAAe,GAAG,CAAC,CAAC,CACjC,KAAK,cAAc,CAAC,CACpB,MACC,IACE,GAAG,eAAe,OAAO,KAAK,GAC9B,GAAG,eAAe,YAAY,UAAU,GACxC,GAAG,eAAe,SAAS,OAAO,CACpC,CACF,CAAC,CACA,MAAM,CAAC;EACV,OAAO,QAAQ,KAAA;CACjB;AACF"}
@@ -1,15 +0,0 @@
1
- import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
- import { Logger } from "@headless-lms/core/shared/ports";
3
- import { Page, Student, StudentsQuery, StudentsReportRepository } from "@headless-lms/core/reporting/students";
4
- //#region src/repositories/students.d.ts
5
- declare class DrizzleStudentsRepository implements StudentsReportRepository {
6
- private readonly db;
7
- private readonly logger;
8
- constructor(db: NodePgDatabase, logger?: Logger);
9
- list(orgId: string, query: StudentsQuery): Promise<Page<Student>>;
10
- findById(orgId: string, id: string): Promise<Student | null>;
11
- private resolveOrder;
12
- }
13
- //#endregion
14
- export { DrizzleStudentsRepository };
15
- //# sourceMappingURL=students.d.ts.map
@@ -1,83 +0,0 @@
1
- import { users } from "../schema/identity.js";
2
- import { orgUsers } from "../schema/organizations.js";
3
- import { entitlements } from "../schema/entitlements.js";
4
- import "../schema/index.js";
5
- import { user } from "../schema/better-auth.js";
6
- import { orgUserProfileColumns } from "./org-user-profile.js";
7
- import { and, asc, desc, eq, ilike, or, sql } from "drizzle-orm";
8
- import { noopLogger } from "@headless-lms/core/shared/logger";
9
- import { STUDENT_ROLE } from "@headless-lms/core/organizations";
10
- //#region src/repositories/students.ts
11
- const entitlementCountExpr = sql`count(${entitlements.id})`;
12
- const avgProgressExpr = sql`0`;
13
- function toStudent(row) {
14
- return {
15
- id: row.id,
16
- email: row.email,
17
- image: row.image ?? null,
18
- firstName: row.firstName,
19
- lastName: row.lastName,
20
- status: row.status,
21
- entitlementCount: Number(row.entitlementCount),
22
- avgProgress: Number(row.avgProgress),
23
- joinedAt: row.createdAt.toISOString(),
24
- lastActiveAt: null
25
- };
26
- }
27
- var DrizzleStudentsRepository = class {
28
- db;
29
- logger;
30
- constructor(db, logger = noopLogger) {
31
- this.db = db;
32
- this.logger = logger;
33
- }
34
- async list(orgId, query) {
35
- const filters = [eq(orgUsers.orgId, orgId), eq(orgUsers.role, STUDENT_ROLE)];
36
- const q = query.search?.trim();
37
- if (q) {
38
- const like = `%${q}%`;
39
- filters.push(or(ilike(users.firstName, like), ilike(users.lastName, like), ilike(users.email, like)));
40
- }
41
- const where = and(...filters);
42
- const [totals] = await this.db.select({ total: sql`count(*)` }).from(orgUsers).innerJoin(users, eq(users.id, orgUsers.userId)).where(where);
43
- return {
44
- rows: (await this.db.select({
45
- ...orgUserProfileColumns,
46
- status: orgUsers.status,
47
- createdAt: orgUsers.createdAt,
48
- entitlementCount: entitlementCountExpr,
49
- avgProgress: avgProgressExpr
50
- }).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
- total: Number(totals?.total ?? 0),
52
- page: query.page,
53
- pageSize: query.pageSize
54
- };
55
- }
56
- async findById(orgId, id) {
57
- const [row] = await this.db.select({
58
- ...orgUserProfileColumns,
59
- status: orgUsers.status,
60
- createdAt: orgUsers.createdAt,
61
- entitlementCount: entitlementCountExpr,
62
- avgProgress: avgProgressExpr
63
- }).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
- return row ? toStudent(row) : null;
65
- }
66
- resolveOrder(sort) {
67
- const descending = sort?.startsWith("-") ?? false;
68
- const field = sort ? descending ? sort.slice(1) : sort : "firstName";
69
- const dir = descending ? desc : asc;
70
- switch (field) {
71
- case "email": return [dir(users.email)];
72
- case "entitlementCount": return [dir(entitlementCountExpr)];
73
- case "avgProgress": return [dir(avgProgressExpr)];
74
- case "joinedAt": return [dir(orgUsers.createdAt)];
75
- case "lastName": return [dir(users.lastName), dir(users.firstName)];
76
- default: return [dir(users.firstName), dir(users.lastName)];
77
- }
78
- }
79
- };
80
- //#endregion
81
- export { DrizzleStudentsRepository };
82
-
83
- //# sourceMappingURL=students.js.map
@@ -1 +0,0 @@
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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"}