@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.
Files changed (98) 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 +2 -1
  5. package/dist/index.js +3 -2
  6. package/dist/migrate.js.map +1 -1
  7. package/dist/repositories/assets.d.ts +1 -1
  8. package/dist/repositories/assets.js +4 -1
  9. package/dist/repositories/assets.js.map +1 -1
  10. package/dist/repositories/automations.d.ts +7 -5
  11. package/dist/repositories/automations.js +35 -10
  12. package/dist/repositories/automations.js.map +1 -1
  13. package/dist/repositories/content.d.ts +21 -7
  14. package/dist/repositories/content.js +168 -18
  15. package/dist/repositories/content.js.map +1 -1
  16. package/dist/repositories/course-analytics.d.ts +15 -0
  17. package/dist/repositories/course-analytics.js +137 -0
  18. package/dist/repositories/course-analytics.js.map +1 -0
  19. package/dist/repositories/credentials.js +2 -0
  20. package/dist/repositories/credentials.js.map +1 -1
  21. package/dist/repositories/dashboard.js +2 -0
  22. package/dist/repositories/dashboard.js.map +1 -1
  23. package/dist/repositories/discussion.d.ts +1 -1
  24. package/dist/repositories/discussion.js +2 -0
  25. package/dist/repositories/discussion.js.map +1 -1
  26. package/dist/repositories/entitlements.d.ts +11 -5
  27. package/dist/repositories/entitlements.js +42 -17
  28. package/dist/repositories/entitlements.js.map +1 -1
  29. package/dist/repositories/identity.js +2 -0
  30. package/dist/repositories/identity.js.map +1 -1
  31. package/dist/repositories/integrations.d.ts +1 -1
  32. package/dist/repositories/integrations.js +2 -0
  33. package/dist/repositories/integrations.js.map +1 -1
  34. package/dist/repositories/learn.js +6 -4
  35. package/dist/repositories/learn.js.map +1 -1
  36. package/dist/repositories/members.js +2 -0
  37. package/dist/repositories/members.js.map +1 -1
  38. package/dist/repositories/org-user-profile.js.map +1 -1
  39. package/dist/repositories/organizations.d.ts +4 -6
  40. package/dist/repositories/organizations.js +26 -45
  41. package/dist/repositories/organizations.js.map +1 -1
  42. package/dist/repositories/outbox.js +3 -0
  43. package/dist/repositories/outbox.js.map +1 -1
  44. package/dist/repositories/pg-errors.d.ts +53 -5
  45. package/dist/repositories/pg-errors.js +97 -8
  46. package/dist/repositories/pg-errors.js.map +1 -1
  47. package/dist/repositories/progress.d.ts +1 -1
  48. package/dist/repositories/progress.js +3 -0
  49. package/dist/repositories/progress.js.map +1 -1
  50. package/dist/repositories/settings.js +2 -0
  51. package/dist/repositories/settings.js.map +1 -1
  52. package/dist/repositories/students.d.ts +2 -1
  53. package/dist/repositories/students.js +80 -3
  54. package/dist/repositories/students.js.map +1 -1
  55. package/dist/schema/assets.d.ts +17 -0
  56. package/dist/schema/assets.js +2 -1
  57. package/dist/schema/assets.js.map +1 -1
  58. package/dist/schema/automations.d.ts +85 -8
  59. package/dist/schema/automations.js +7 -4
  60. package/dist/schema/automations.js.map +1 -1
  61. package/dist/schema/better-auth.js.map +1 -1
  62. package/dist/schema/content.d.ts +272 -22
  63. package/dist/schema/content.js +40 -5
  64. package/dist/schema/content.js.map +1 -1
  65. package/dist/schema/credentials.js +1 -1
  66. package/dist/schema/credentials.js.map +1 -1
  67. package/dist/schema/discussion.d.ts +34 -0
  68. package/dist/schema/discussion.js +4 -2
  69. package/dist/schema/discussion.js.map +1 -1
  70. package/dist/schema/entitlements.d.ts +52 -1
  71. package/dist/schema/entitlements.js +15 -5
  72. package/dist/schema/entitlements.js.map +1 -1
  73. package/dist/schema/identity.js.map +1 -1
  74. package/dist/schema/index.d.ts +3 -3
  75. package/dist/schema/index.js +4 -2
  76. package/dist/schema/index.js.map +1 -1
  77. package/dist/schema/integrations.js +1 -1
  78. package/dist/schema/integrations.js.map +1 -1
  79. package/dist/schema/organizations.d.ts +17 -0
  80. package/dist/schema/organizations.js +2 -1
  81. package/dist/schema/organizations.js.map +1 -1
  82. package/dist/schema/outbox.d.ts +17 -0
  83. package/dist/schema/outbox.js +1 -0
  84. package/dist/schema/outbox.js.map +1 -1
  85. package/dist/schema/progress.d.ts +17 -0
  86. package/dist/schema/progress.js +1 -0
  87. package/dist/schema/progress.js.map +1 -1
  88. package/dist/schema/settings.js.map +1 -1
  89. package/dist/unit-of-work.js +7 -2
  90. package/dist/unit-of-work.js.map +1 -1
  91. package/drizzle/0000_baseline.sql +0 -1
  92. package/drizzle/0001_new_sentry.sql +43 -0
  93. package/drizzle/0002_automation_kinds.sql +7 -0
  94. package/drizzle/meta/0000_snapshot.json +167 -597
  95. package/drizzle/meta/0001_snapshot.json +3769 -0
  96. package/drizzle/meta/0002_snapshot.json +3770 -0
  97. package/drizzle/meta/_journal.json +16 -2
  98. package/package.json +3 -2
@@ -1,4 +1,96 @@
1
1
  //#region src/schema/content.d.ts
2
+ declare const bundles: import("drizzle-orm/pg-core").PgTableWithColumns<{
3
+ name: "bundles";
4
+ schema: undefined;
5
+ columns: {
6
+ orgId: import("drizzle-orm/pg-core").PgColumn<{
7
+ name: "org_id";
8
+ tableName: "bundles";
9
+ dataType: "string";
10
+ columnType: "PgText";
11
+ data: string;
12
+ driverParam: string;
13
+ notNull: true;
14
+ hasDefault: false;
15
+ isPrimaryKey: false;
16
+ isAutoincrement: false;
17
+ hasRuntimeDefault: false;
18
+ enumValues: [string, ...string[]];
19
+ baseColumn: never;
20
+ identity: undefined;
21
+ generated: undefined;
22
+ }, {}, {}>;
23
+ id: import("drizzle-orm/pg-core").PgColumn<{
24
+ name: "id";
25
+ tableName: "bundles";
26
+ dataType: "string";
27
+ columnType: "PgText";
28
+ data: string;
29
+ driverParam: string;
30
+ notNull: true;
31
+ hasDefault: false;
32
+ isPrimaryKey: false;
33
+ isAutoincrement: false;
34
+ hasRuntimeDefault: false;
35
+ enumValues: [string, ...string[]];
36
+ baseColumn: never;
37
+ identity: undefined;
38
+ generated: undefined;
39
+ }, {}, {}>;
40
+ name: import("drizzle-orm/pg-core").PgColumn<{
41
+ name: "name";
42
+ tableName: "bundles";
43
+ dataType: "string";
44
+ columnType: "PgText";
45
+ data: string;
46
+ driverParam: string;
47
+ notNull: true;
48
+ hasDefault: false;
49
+ isPrimaryKey: false;
50
+ isAutoincrement: false;
51
+ hasRuntimeDefault: false;
52
+ enumValues: [string, ...string[]];
53
+ baseColumn: never;
54
+ identity: undefined;
55
+ generated: undefined;
56
+ }, {}, {}>;
57
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
58
+ name: "created_at";
59
+ tableName: "bundles";
60
+ dataType: "date";
61
+ columnType: "PgTimestamp";
62
+ data: Date;
63
+ driverParam: string;
64
+ notNull: true;
65
+ hasDefault: true;
66
+ isPrimaryKey: false;
67
+ isAutoincrement: false;
68
+ hasRuntimeDefault: false;
69
+ enumValues: undefined;
70
+ baseColumn: never;
71
+ identity: undefined;
72
+ generated: undefined;
73
+ }, {}, {}>;
74
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
75
+ name: "updated_at";
76
+ tableName: "bundles";
77
+ dataType: "date";
78
+ columnType: "PgTimestamp";
79
+ data: Date;
80
+ driverParam: string;
81
+ notNull: true;
82
+ hasDefault: true;
83
+ isPrimaryKey: false;
84
+ isAutoincrement: false;
85
+ hasRuntimeDefault: false;
86
+ enumValues: undefined;
87
+ baseColumn: never;
88
+ identity: undefined;
89
+ generated: undefined;
90
+ }, {}, {}>;
91
+ };
92
+ dialect: "pg";
93
+ }>;
2
94
  declare const contentItems: import("drizzle-orm/pg-core").PgTableWithColumns<{
3
95
  name: "content_items";
4
96
  schema: undefined;
@@ -71,6 +163,115 @@ declare const contentItems: import("drizzle-orm/pg-core").PgTableWithColumns<{
71
163
  identity: undefined;
72
164
  generated: undefined;
73
165
  }, {}, {}>;
166
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
167
+ name: "updated_at";
168
+ tableName: "content_items";
169
+ dataType: "date";
170
+ columnType: "PgTimestamp";
171
+ data: Date;
172
+ driverParam: string;
173
+ notNull: true;
174
+ hasDefault: true;
175
+ isPrimaryKey: false;
176
+ isAutoincrement: false;
177
+ hasRuntimeDefault: false;
178
+ enumValues: undefined;
179
+ baseColumn: never;
180
+ identity: undefined;
181
+ generated: undefined;
182
+ }, {}, {}>;
183
+ };
184
+ dialect: "pg";
185
+ }>;
186
+ declare const bundleItems: import("drizzle-orm/pg-core").PgTableWithColumns<{
187
+ name: "bundle_items";
188
+ schema: undefined;
189
+ columns: {
190
+ orgId: import("drizzle-orm/pg-core").PgColumn<{
191
+ name: "org_id";
192
+ tableName: "bundle_items";
193
+ dataType: "string";
194
+ columnType: "PgText";
195
+ data: string;
196
+ driverParam: string;
197
+ notNull: true;
198
+ hasDefault: false;
199
+ isPrimaryKey: false;
200
+ isAutoincrement: false;
201
+ hasRuntimeDefault: false;
202
+ enumValues: [string, ...string[]];
203
+ baseColumn: never;
204
+ identity: undefined;
205
+ generated: undefined;
206
+ }, {}, {}>;
207
+ bundleId: import("drizzle-orm/pg-core").PgColumn<{
208
+ name: "bundle_id";
209
+ tableName: "bundle_items";
210
+ dataType: "string";
211
+ columnType: "PgText";
212
+ data: string;
213
+ driverParam: string;
214
+ notNull: true;
215
+ hasDefault: false;
216
+ isPrimaryKey: false;
217
+ isAutoincrement: false;
218
+ hasRuntimeDefault: false;
219
+ enumValues: [string, ...string[]];
220
+ baseColumn: never;
221
+ identity: undefined;
222
+ generated: undefined;
223
+ }, {}, {}>;
224
+ contentId: import("drizzle-orm/pg-core").PgColumn<{
225
+ name: "content_id";
226
+ tableName: "bundle_items";
227
+ dataType: "string";
228
+ columnType: "PgText";
229
+ data: string;
230
+ driverParam: string;
231
+ notNull: true;
232
+ hasDefault: false;
233
+ isPrimaryKey: false;
234
+ isAutoincrement: false;
235
+ hasRuntimeDefault: false;
236
+ enumValues: [string, ...string[]];
237
+ baseColumn: never;
238
+ identity: undefined;
239
+ generated: undefined;
240
+ }, {}, {}>;
241
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
242
+ name: "created_at";
243
+ tableName: "bundle_items";
244
+ dataType: "date";
245
+ columnType: "PgTimestamp";
246
+ data: Date;
247
+ driverParam: string;
248
+ notNull: true;
249
+ hasDefault: true;
250
+ isPrimaryKey: false;
251
+ isAutoincrement: false;
252
+ hasRuntimeDefault: false;
253
+ enumValues: undefined;
254
+ baseColumn: never;
255
+ identity: undefined;
256
+ generated: undefined;
257
+ }, {}, {}>;
258
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
259
+ name: "updated_at";
260
+ tableName: "bundle_items";
261
+ dataType: "date";
262
+ columnType: "PgTimestamp";
263
+ data: Date;
264
+ driverParam: string;
265
+ notNull: true;
266
+ hasDefault: true;
267
+ isPrimaryKey: false;
268
+ isAutoincrement: false;
269
+ hasRuntimeDefault: false;
270
+ enumValues: undefined;
271
+ baseColumn: never;
272
+ identity: undefined;
273
+ generated: undefined;
274
+ }, {}, {}>;
74
275
  };
75
276
  dialect: "pg";
76
277
  }>;
@@ -189,7 +390,7 @@ declare const courses: import("drizzle-orm/pg-core").PgTableWithColumns<{
189
390
  tableName: "courses";
190
391
  dataType: "string";
191
392
  columnType: "PgText";
192
- data: "draft" | "published";
393
+ data: "published" | "draft";
193
394
  driverParam: string;
194
395
  notNull: true;
195
396
  hasDefault: true;
@@ -235,25 +436,6 @@ declare const courses: import("drizzle-orm/pg-core").PgTableWithColumns<{
235
436
  identity: undefined;
236
437
  generated: undefined;
237
438
  }, {}, {}>;
238
- settings: import("drizzle-orm/pg-core").PgColumn<{
239
- name: "settings";
240
- tableName: "courses";
241
- dataType: "json";
242
- columnType: "PgJsonb";
243
- data: import("@headless-lms/core/types").JsonValue;
244
- driverParam: unknown;
245
- notNull: true;
246
- hasDefault: true;
247
- isPrimaryKey: false;
248
- isAutoincrement: false;
249
- hasRuntimeDefault: false;
250
- enumValues: undefined;
251
- baseColumn: never;
252
- identity: undefined;
253
- generated: undefined;
254
- }, {}, {
255
- $type: import("@headless-lms/core/types").JsonValue;
256
- }>;
257
439
  createdAt: import("drizzle-orm/pg-core").PgColumn<{
258
440
  name: "created_at";
259
441
  tableName: "courses";
@@ -651,6 +833,40 @@ declare const activityAssets: import("drizzle-orm/pg-core").PgTableWithColumns<{
651
833
  identity: undefined;
652
834
  generated: undefined;
653
835
  }, {}, {}>;
836
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
837
+ name: "created_at";
838
+ tableName: "activity_assets";
839
+ dataType: "date";
840
+ columnType: "PgTimestamp";
841
+ data: Date;
842
+ driverParam: string;
843
+ notNull: true;
844
+ hasDefault: true;
845
+ isPrimaryKey: false;
846
+ isAutoincrement: false;
847
+ hasRuntimeDefault: false;
848
+ enumValues: undefined;
849
+ baseColumn: never;
850
+ identity: undefined;
851
+ generated: undefined;
852
+ }, {}, {}>;
853
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
854
+ name: "updated_at";
855
+ tableName: "activity_assets";
856
+ dataType: "date";
857
+ columnType: "PgTimestamp";
858
+ data: Date;
859
+ driverParam: string;
860
+ notNull: true;
861
+ hasDefault: true;
862
+ isPrimaryKey: false;
863
+ isAutoincrement: false;
864
+ hasRuntimeDefault: false;
865
+ enumValues: undefined;
866
+ baseColumn: never;
867
+ identity: undefined;
868
+ generated: undefined;
869
+ }, {}, {}>;
654
870
  };
655
871
  dialect: "pg";
656
872
  }>;
@@ -769,7 +985,7 @@ declare const downloads: import("drizzle-orm/pg-core").PgTableWithColumns<{
769
985
  tableName: "downloads";
770
986
  dataType: "string";
771
987
  columnType: "PgText";
772
- data: "draft" | "published";
988
+ data: "published" | "draft";
773
989
  driverParam: string;
774
990
  notNull: true;
775
991
  hasDefault: true;
@@ -958,9 +1174,43 @@ declare const downloadAssets: import("drizzle-orm/pg-core").PgTableWithColumns<{
958
1174
  identity: undefined;
959
1175
  generated: undefined;
960
1176
  }, {}, {}>;
1177
+ createdAt: import("drizzle-orm/pg-core").PgColumn<{
1178
+ name: "created_at";
1179
+ tableName: "download_assets";
1180
+ dataType: "date";
1181
+ columnType: "PgTimestamp";
1182
+ data: Date;
1183
+ driverParam: string;
1184
+ notNull: true;
1185
+ hasDefault: true;
1186
+ isPrimaryKey: false;
1187
+ isAutoincrement: false;
1188
+ hasRuntimeDefault: false;
1189
+ enumValues: undefined;
1190
+ baseColumn: never;
1191
+ identity: undefined;
1192
+ generated: undefined;
1193
+ }, {}, {}>;
1194
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
1195
+ name: "updated_at";
1196
+ tableName: "download_assets";
1197
+ dataType: "date";
1198
+ columnType: "PgTimestamp";
1199
+ data: Date;
1200
+ driverParam: string;
1201
+ notNull: true;
1202
+ hasDefault: true;
1203
+ isPrimaryKey: false;
1204
+ isAutoincrement: false;
1205
+ hasRuntimeDefault: false;
1206
+ enumValues: undefined;
1207
+ baseColumn: never;
1208
+ identity: undefined;
1209
+ generated: undefined;
1210
+ }, {}, {}>;
961
1211
  };
962
1212
  dialect: "pg";
963
1213
  }>;
964
1214
  //#endregion
965
- export { activities, activityAssets, contentItems, courses, downloadAssets, downloads, modules };
1215
+ export { activities, activityAssets, bundleItems, bundles, contentItems, courses, downloadAssets, downloads, modules };
966
1216
  //# sourceMappingURL=content.d.ts.map
@@ -4,16 +4,48 @@ import { sql } from "drizzle-orm";
4
4
  import { check, foreignKey, integer, jsonb, pgTable, primaryKey, text, timestamp, unique } from "drizzle-orm/pg-core";
5
5
  import { genId } from "@headless-lms/core/shared/id";
6
6
  //#region src/schema/content.ts
7
+ const bundles = pgTable("bundles", {
8
+ orgId: text("org_id").notNull().references(() => organizations.id),
9
+ id: text("id").notNull(),
10
+ name: text("name").notNull(),
11
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
12
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
13
+ }, (t) => ({
14
+ pk: primaryKey({ columns: [t.orgId, t.id] }),
15
+ typeUq: unique().on(t.orgId, t.id, t.name)
16
+ }));
7
17
  const contentItems = pgTable("content_items", {
8
18
  orgId: text("org_id").notNull().references(() => organizations.id),
9
19
  id: text("id").notNull(),
10
20
  type: text("type", { enum: ["course", "download"] }).notNull(),
11
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
21
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
22
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
12
23
  }, (t) => ({
13
24
  pk: primaryKey({ columns: [t.orgId, t.id] }),
14
25
  typeUq: unique().on(t.orgId, t.id, t.type),
15
26
  typeCk: check("content_items_type_check", sql`${t.type} in ('course', 'download')`)
16
27
  }));
28
+ const bundleItems = pgTable("bundle_items", {
29
+ orgId: text("org_id").notNull().references(() => organizations.id),
30
+ bundleId: text("bundle_id").notNull(),
31
+ contentId: text("content_id").notNull(),
32
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
33
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
34
+ }, (t) => ({
35
+ pk: primaryKey({ columns: [
36
+ t.orgId,
37
+ t.bundleId,
38
+ t.contentId
39
+ ] }),
40
+ contentItemFk: foreignKey({
41
+ columns: [t.orgId, t.contentId],
42
+ foreignColumns: [contentItems.orgId, contentItems.id]
43
+ }).onDelete("cascade"),
44
+ bundleFk: foreignKey({
45
+ columns: [t.orgId, t.bundleId],
46
+ foreignColumns: [bundles.orgId, bundles.id]
47
+ }).onDelete("cascade")
48
+ }));
17
49
  const courses = pgTable("courses", {
18
50
  orgId: text("org_id").notNull().references(() => organizations.id),
19
51
  id: text("id").notNull().$defaultFn(() => genId("course")),
@@ -24,7 +56,6 @@ const courses = pgTable("courses", {
24
56
  status: text("status", { enum: ["draft", "published"] }).notNull().default("draft"),
25
57
  category: text("category").notNull().default(""),
26
58
  thumbnailAssetId: text("thumbnail_asset_id"),
27
- settings: jsonb("settings").$type().notNull().default({}),
28
59
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
29
60
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
30
61
  }, (t) => ({
@@ -93,7 +124,9 @@ const activityAssets = pgTable("activity_assets", {
93
124
  id: text("id").notNull().$defaultFn(() => genId("activityAsset")),
94
125
  activityId: text("activity_id").notNull(),
95
126
  assetId: text("asset_id").notNull(),
96
- seq: integer("seq").notNull().default(0)
127
+ seq: integer("seq").notNull().default(0),
128
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
129
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
97
130
  }, (t) => ({
98
131
  pk: primaryKey({ columns: [t.orgId, t.id] }),
99
132
  activityFk: foreignKey({
@@ -144,7 +177,9 @@ const downloadAssets = pgTable("download_assets", {
144
177
  downloadId: text("download_id").notNull(),
145
178
  assetId: text("asset_id").notNull(),
146
179
  seq: integer("seq").notNull().default(0),
147
- displayName: text("display_name")
180
+ displayName: text("display_name"),
181
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
182
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
148
183
  }, (t) => ({
149
184
  pk: primaryKey({ columns: [t.orgId, t.id] }),
150
185
  downloadFk: foreignKey({
@@ -158,6 +193,6 @@ const downloadAssets = pgTable("download_assets", {
158
193
  downloadAssetUq: unique().on(t.orgId, t.downloadId, t.assetId)
159
194
  }));
160
195
  //#endregion
161
- export { activities, activityAssets, contentItems, courses, downloadAssets, downloads, modules };
196
+ export { activities, activityAssets, bundleItems, bundles, contentItems, courses, downloadAssets, downloads, modules };
162
197
 
163
198
  //# sourceMappingURL=content.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"content.js","names":[],"sources":["../../src/schema/content.ts"],"sourcesContent":["// content tables — the content domain (owns all content types; today: course).\n// Course type: Course → Module → Activity, where an Activity is the leaf sitting\n// directly in a module. An Activity is UNIFORM content — the domain does not\n// categorise it: a `seq`, and an opaque `settings` jsonb blob holding whatever\n// that content needs (title, type, body, completion rule, …). Assets are the one\n// thing kept OUT of the blob (owned by the assets domain) and linked via the\n// many-to-many `activity_assets`. Org-scoped: composite (org_id, id) keys.\nimport {\n pgTable,\n text,\n integer,\n jsonb,\n timestamp,\n primaryKey,\n foreignKey,\n unique,\n check,\n} from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport type {\n Activity,\n ActivityAsset,\n ContentItem,\n Course,\n Download,\n DownloadAsset,\n Module,\n} from '@headless-lms/core/schemas';\nimport { genId } from '@headless-lms/core/shared/id';\nimport { organizations } from './organizations.js';\nimport { assets } from './assets.js';\nimport type { Expect, NoDrift } from './drift.js';\n\n// The content registry (supertype table): one row per piece of content, any\n// type. A concrete content table shares its PK with a registry row (same id)\n// and references it via a type-pinned composite FK; the generic entitlements\n// table FKs here, so it never changes when a content type is added. Deletes go\n// through this table (cascade to the concrete row and the grants).\nexport const contentItems = pgTable(\n 'content_items',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id').notNull(),\n type: text('type', { enum: ['course', 'download'] }).notNull(),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // FK target for type-pinned references from concrete content tables.\n typeUq: unique().on(t.orgId, t.id, t.type),\n // Widened per new content type.\n typeCk: check('content_items_type_check', sql`${t.type} in ('course', 'download')`),\n }),\n);\n\nexport const courses = pgTable(\n 'courses',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('course')),\n // Pinned to 'course' so the composite FK below cannot attach this row to a\n // registry row of another content type.\n type: text('type')\n .$type<Course['type']>()\n .notNull()\n .generatedAlwaysAs(sql`'course'`),\n title: text('title').notNull(),\n slug: text('slug').notNull(),\n description: text('description').notNull().default(''),\n status: text('status', { enum: ['draft', 'published'] })\n .notNull()\n .default('draft'),\n category: text('category').notNull().default(''),\n // Media-library asset rendered as the course's cover. Null = no cover.\n thumbnailAssetId: text('thumbnail_asset_id'),\n // Course-wide delivery toggles (CourseSettings). Modelled, unlike the\n // per-activity blob: a fixed surface the API validates.\n settings: jsonb('settings').$type<Course['settings']>().notNull().default({}),\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 slugUq: unique().on(t.orgId, t.slug),\n contentItemFk: foreignKey({\n columns: [t.orgId, t.id, t.type],\n foreignColumns: [contentItems.orgId, contentItems.id, contentItems.type],\n }).onDelete('cascade'),\n // Restrictive, like `activity_assets.assetFk` — the asset is owned by the\n // assets domain and must be unlinked before it can be deleted. MATCH SIMPLE\n // means the constraint is skipped entirely while the thumbnail is null.\n thumbnailAssetFk: foreignKey({\n columns: [t.orgId, t.thumbnailAssetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n }),\n);\n\nexport const modules = pgTable(\n 'modules',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('module')),\n courseId: text('course_id').notNull(),\n title: text('title').notNull(),\n seq: integer('seq').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) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // Modules are part of the course aggregate: deleting the course deletes them.\n courseFk: foreignKey({\n columns: [t.orgId, t.courseId],\n foreignColumns: [courses.orgId, courses.id],\n }).onDelete('cascade'),\n // FK target for the course-pinned reference from activities.\n courseUq: unique().on(t.orgId, t.id, t.courseId),\n }),\n);\n\n// The leaf, directly in a module. Uniform content: seq + opaque settings blob.\nexport const activities = pgTable(\n 'activities',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('activity')),\n moduleId: text('module_id').notNull(),\n // Denormalised from the module: the course an activity belongs to is asked\n // for far more often than its module (progress, discussion, entitlements),\n // and the FK below pins the pair, so it cannot disagree with the module.\n courseId: text('course_id').notNull(),\n seq: integer('seq').notNull(),\n // Opaque per-activity blob: title, type, body, completion rule — whatever the\n // content needs. Assets are the one thing kept out of the blob.\n settings: jsonb('settings').$type<Activity['settings']>(),\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 // Course-pinned, like `courses.contentItemFk`: the module reference carries\n // `course_id`, so an activity whose stored course disagrees with its\n // module's is rejected by the database rather than silently rotting.\n moduleFk: foreignKey({\n columns: [t.orgId, t.moduleId, t.courseId],\n foreignColumns: [modules.orgId, modules.id, modules.courseId],\n }).onDelete('cascade'),\n seqUq: unique().on(t.orgId, t.moduleId, t.seq),\n }),\n);\n\n// activity ↔ asset: many-to-many (an activity uses many assets; an asset can be\n// reused by many activities). Assets owned by the assets domain, tracked here.\nexport const activityAssets = pgTable(\n 'activity_assets',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('activityAsset')),\n activityId: text('activity_id').notNull(),\n assetId: text('asset_id').notNull(),\n seq: integer('seq').notNull().default(0),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // The link cascades with its activity; the asset itself is owned by the\n // assets domain and survives (assetFk stays restrictive).\n activityFk: foreignKey({\n columns: [t.orgId, t.activityId],\n foreignColumns: [activities.orgId, activities.id],\n }).onDelete('cascade'),\n assetFk: foreignKey({\n columns: [t.orgId, t.assetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n activityAssetUq: unique().on(t.orgId, t.activityId, t.assetId),\n }),\n);\n\n// A download: an ordered set of media-library assets. Shares its PK with a\n// registry row (same id) via the type-pinned composite FK, exactly like courses.\nexport const downloads = pgTable(\n 'downloads',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('download')),\n // Pinned to 'download' so the composite FK below cannot attach this row to\n // a registry row of another content type.\n type: text('type')\n .$type<Download['type']>()\n .notNull()\n .generatedAlwaysAs(sql`'download'`),\n title: text('title').notNull(),\n slug: text('slug').notNull(),\n description: text('description').notNull().default(''),\n status: text('status', { enum: ['draft', 'published'] })\n .notNull()\n .default('draft'),\n category: text('category').notNull().default(''),\n thumbnailAssetId: text('thumbnail_asset_id'),\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 slugUq: unique().on(t.orgId, t.slug),\n contentItemFk: foreignKey({\n columns: [t.orgId, t.id, t.type],\n foreignColumns: [contentItems.orgId, contentItems.id, contentItems.type],\n }).onDelete('cascade'),\n // Restrictive: a thumbnail in use blocks deleting the asset.\n thumbnailFk: foreignKey({\n columns: [t.orgId, t.thumbnailAssetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n }),\n);\n\n// download ↔ asset: the ordered set. Mirrors activity_assets one level\n// shallower — a download has no intermediate structure.\nexport const downloadAssets = pgTable(\n 'download_assets',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id')\n .notNull()\n .$defaultFn(() => genId('downloadAsset')),\n downloadId: text('download_id').notNull(),\n assetId: text('asset_id').notNull(),\n seq: integer('seq').notNull().default(0),\n // Author's label; null falls back to the asset's filename.\n displayName: text('display_name'),\n },\n (t) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // The link cascades with its download; the asset is owned by the assets\n // domain and survives (assetFk stays restrictive).\n downloadFk: foreignKey({\n columns: [t.orgId, t.downloadId],\n foreignColumns: [downloads.orgId, downloads.id],\n }).onDelete('cascade'),\n assetFk: foreignKey({\n columns: [t.orgId, t.assetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n // Lets delivery key on asset_id rather than the link row id.\n downloadAssetUq: unique().on(t.orgId, t.downloadId, t.assetId),\n }),\n);\n\ntype _ContentItemsDrift = Expect<NoDrift<typeof contentItems.$inferSelect, ContentItem>>;\ntype _CoursesDrift = Expect<NoDrift<typeof courses.$inferSelect, Course>>;\ntype _ModulesDrift = Expect<NoDrift<typeof modules.$inferSelect, Module>>;\ntype _ActivitiesDrift = Expect<NoDrift<typeof activities.$inferSelect, Activity>>;\ntype _ActivityAssetsDrift = Expect<NoDrift<typeof activityAssets.$inferSelect, ActivityAsset>>;\ntype _DownloadsDrift = Expect<NoDrift<typeof downloads.$inferSelect, Download>>;\ntype _DownloadAssetsDrift = Expect<NoDrift<typeof downloadAssets.$inferSelect, DownloadAsset>>;\n"],"mappings":"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"}
1
+ {"version":3,"file":"content.js","names":[],"sources":["../../src/schema/content.ts"],"sourcesContent":["import {\n pgTable,\n text,\n integer,\n jsonb,\n timestamp,\n primaryKey,\n foreignKey,\n unique,\n check,\n} from \"drizzle-orm/pg-core\";\nimport { sql } from \"drizzle-orm\";\nimport type {\n Activity,\n ActivityAsset,\n Bundle,\n BundleItem,\n ContentItem,\n Course,\n Download,\n DownloadAsset,\n Module,\n} from \"@headless-lms/core/schemas\";\nimport { genId } from \"@headless-lms/core/shared/id\";\nimport { organizations } from \"./organizations.js\";\nimport { assets } from \"./assets.js\";\nimport type { Expect, NoDrift } from \"./drift.js\";\n\n// Bundles.\nexport const bundles = pgTable(\n \"bundles\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\").notNull(),\n name: text(\"name\").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) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n typeUq: unique().on(t.orgId, t.id, t.name),\n }),\n);\n\n// The content registry one row per piece of content, any\n// type. this is so we can PK to content items of different types.\nexport const contentItems = pgTable(\n \"content_items\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\").notNull(),\n type: text(\"type\", { enum: [\"course\", \"download\"] }).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) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // FK target for type-pinned references from concrete content tables.\n typeUq: unique().on(t.orgId, t.id, t.type),\n // Widened per new content type.\n typeCk: check(\"content_items_type_check\", sql`${t.type} in ('course', 'download')`),\n }),\n);\n\n// The content registry one row per piece of content, any\n// type. this is so we can PK to content items of different types.\nexport const bundleItems = pgTable(\n \"bundle_items\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n bundleId: text(\"bundle_id\").notNull(),\n contentId: text(\"content_id\").notNull(),\n\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.bundleId, t.contentId] }),\n contentItemFk: foreignKey({\n columns: [t.orgId, t.contentId],\n foreignColumns: [contentItems.orgId, contentItems.id],\n }).onDelete(\"cascade\"),\n bundleFk: foreignKey({\n columns: [t.orgId, t.bundleId],\n foreignColumns: [bundles.orgId, bundles.id],\n }).onDelete(\"cascade\"),\n }),\n);\n\nexport const courses = pgTable(\n \"courses\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\")\n .notNull()\n .$defaultFn(() => genId(\"course\")),\n // Pinned to 'course' so the composite FK below cannot attach this row to a\n // registry row of another content type.\n type: text(\"type\")\n .$type<Course[\"type\"]>()\n .notNull()\n .generatedAlwaysAs(sql`'course'`),\n title: text(\"title\").notNull(),\n slug: text(\"slug\").notNull(),\n description: text(\"description\").notNull().default(\"\"),\n status: text(\"status\", { enum: [\"draft\", \"published\"] })\n .notNull()\n .default(\"draft\"),\n category: text(\"category\").notNull().default(\"\"),\n // Media-library asset rendered as the course's cover. Null = no cover.\n thumbnailAssetId: text(\"thumbnail_asset_id\"),\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 slugUq: unique().on(t.orgId, t.slug),\n contentItemFk: foreignKey({\n columns: [t.orgId, t.id, t.type],\n foreignColumns: [contentItems.orgId, contentItems.id, contentItems.type],\n }).onDelete(\"cascade\"),\n // Restrictive, like `activity_assets.assetFk` — the asset is owned by the\n // assets domain and must be unlinked before it can be deleted. MATCH SIMPLE\n // means the constraint is skipped entirely while the thumbnail is null.\n thumbnailAssetFk: foreignKey({\n columns: [t.orgId, t.thumbnailAssetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n }),\n);\n\nexport const modules = pgTable(\n \"modules\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\")\n .notNull()\n .$defaultFn(() => genId(\"module\")),\n courseId: text(\"course_id\").notNull(),\n title: text(\"title\").notNull(),\n seq: integer(\"seq\").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) => ({\n pk: primaryKey({ columns: [t.orgId, t.id] }),\n // Modules are part of the course aggregate: deleting the course deletes them.\n courseFk: foreignKey({\n columns: [t.orgId, t.courseId],\n foreignColumns: [courses.orgId, courses.id],\n }).onDelete(\"cascade\"),\n // FK target for the course-pinned reference from activities.\n courseUq: unique().on(t.orgId, t.id, t.courseId),\n }),\n);\n\n// The leaf, directly in a module. Uniform content: seq + opaque settings blob.\nexport const activities = pgTable(\n \"activities\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\")\n .notNull()\n .$defaultFn(() => genId(\"activity\")),\n moduleId: text(\"module_id\").notNull(),\n // Denormalised from the module: the course an activity belongs to is asked\n // for far more often than its module (progress, discussion, entitlements),\n // and the FK below pins the pair, so it cannot disagree with the module.\n courseId: text(\"course_id\").notNull(),\n seq: integer(\"seq\").notNull(),\n // Opaque per-activity blob: title, type, body, completion rule — whatever the\n // content needs. Assets are the one thing kept out of the blob.\n settings: jsonb(\"settings\").$type<Activity[\"settings\"]>(),\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 // Course-pinned, like `courses.contentItemFk`: the module reference carries\n // `course_id`, so an activity whose stored course disagrees with its\n // module's is rejected by the database rather than silently rotting.\n moduleFk: foreignKey({\n columns: [t.orgId, t.moduleId, t.courseId],\n foreignColumns: [modules.orgId, modules.id, modules.courseId],\n }).onDelete(\"cascade\"),\n seqUq: unique().on(t.orgId, t.moduleId, t.seq),\n }),\n);\n\n// activity ↔ asset: many-to-many (an activity uses many assets; an asset can be\n// reused by many activities). Assets owned by the assets domain, tracked here.\nexport const activityAssets = pgTable(\n \"activity_assets\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\")\n .notNull()\n .$defaultFn(() => genId(\"activityAsset\")),\n activityId: text(\"activity_id\").notNull(),\n assetId: text(\"asset_id\").notNull(),\n seq: integer(\"seq\").notNull().default(0),\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 // The link cascades with its activity; the asset itself is owned by the\n // assets domain and survives (assetFk stays restrictive).\n activityFk: foreignKey({\n columns: [t.orgId, t.activityId],\n foreignColumns: [activities.orgId, activities.id],\n }).onDelete(\"cascade\"),\n assetFk: foreignKey({\n columns: [t.orgId, t.assetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n activityAssetUq: unique().on(t.orgId, t.activityId, t.assetId),\n }),\n);\n\n// A download: an ordered set of media-library assets. Shares its PK with a\n// registry row (same id) via the type-pinned composite FK, exactly like courses.\nexport const downloads = pgTable(\n \"downloads\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\")\n .notNull()\n .$defaultFn(() => genId(\"download\")),\n // Pinned to 'download' so the composite FK below cannot attach this row to\n // a registry row of another content type.\n type: text(\"type\")\n .$type<Download[\"type\"]>()\n .notNull()\n .generatedAlwaysAs(sql`'download'`),\n title: text(\"title\").notNull(),\n slug: text(\"slug\").notNull(),\n description: text(\"description\").notNull().default(\"\"),\n status: text(\"status\", { enum: [\"draft\", \"published\"] })\n .notNull()\n .default(\"draft\"),\n category: text(\"category\").notNull().default(\"\"),\n thumbnailAssetId: text(\"thumbnail_asset_id\"),\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 slugUq: unique().on(t.orgId, t.slug),\n contentItemFk: foreignKey({\n columns: [t.orgId, t.id, t.type],\n foreignColumns: [contentItems.orgId, contentItems.id, contentItems.type],\n }).onDelete(\"cascade\"),\n // Restrictive: a thumbnail in use blocks deleting the asset.\n thumbnailFk: foreignKey({\n columns: [t.orgId, t.thumbnailAssetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n }),\n);\n\n// download ↔ asset: the ordered set. Mirrors activity_assets one level\n// shallower — a download has no intermediate structure.\nexport const downloadAssets = pgTable(\n \"download_assets\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\")\n .notNull()\n .$defaultFn(() => genId(\"downloadAsset\")),\n downloadId: text(\"download_id\").notNull(),\n assetId: text(\"asset_id\").notNull(),\n seq: integer(\"seq\").notNull().default(0),\n // Author's label; null falls back to the asset's filename.\n displayName: text(\"display_name\"),\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 // The link cascades with its download; the asset is owned by the assets\n // domain and survives (assetFk stays restrictive).\n downloadFk: foreignKey({\n columns: [t.orgId, t.downloadId],\n foreignColumns: [downloads.orgId, downloads.id],\n }).onDelete(\"cascade\"),\n assetFk: foreignKey({\n columns: [t.orgId, t.assetId],\n foreignColumns: [assets.orgId, assets.id],\n }),\n // Lets delivery key on asset_id rather than the link row id.\n downloadAssetUq: unique().on(t.orgId, t.downloadId, t.assetId),\n }),\n);\n\ntype _BundlesDrift = Expect<NoDrift<typeof bundles.$inferSelect, Bundle>>;\ntype _BundleItemsDrift = Expect<NoDrift<typeof bundleItems.$inferSelect, BundleItem>>;\ntype _ContentItemsDrift = Expect<NoDrift<typeof contentItems.$inferSelect, ContentItem>>;\ntype _CoursesDrift = Expect<NoDrift<typeof courses.$inferSelect, Omit<Course, \"settings\">>>;\ntype _ModulesDrift = Expect<NoDrift<typeof modules.$inferSelect, Module>>;\ntype _ActivitiesDrift = Expect<NoDrift<typeof activities.$inferSelect, Activity>>;\ntype _ActivityAssetsDrift = Expect<NoDrift<typeof activityAssets.$inferSelect, ActivityAsset>>;\ntype _DownloadsDrift = Expect<NoDrift<typeof downloads.$inferSelect, Download>>;\ntype _DownloadAssetsDrift = Expect<NoDrift<typeof downloadAssets.$inferSelect, DownloadAsset>>;\n"],"mappings":"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"}
@@ -7,7 +7,7 @@ const credentials = pgTable("credentials", {
7
7
  ciphertext: text("ciphertext").notNull(),
8
8
  keyVersion: integer("key_version").notNull().default(1),
9
9
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
10
- updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
10
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
11
11
  }, (t) => ({ pk: primaryKey({ columns: [t.orgId, t.id] }) }));
12
12
  //#endregion
13
13
  export { credentials };
@@ -1 +1 @@
1
- {"version":3,"file":"credentials.js","names":[],"sources":["../../src/schema/credentials.ts"],"sourcesContent":["// credentials — the shared secure credential store. One row per secret,\n// org-scoped with the standard composite (org_id, id) key. `ciphertext` is\n// base64(iv ‖ auth tag ‖ data), AES-256-GCM with AAD = `${orgId}:${id}`, so a\n// row's payload cannot be decrypted if copied to another row or org.\n// `key_version` names the encryption key used, enabling rotation later.\nimport { pgTable, text, integer, timestamp, primaryKey } from 'drizzle-orm/pg-core';\nimport type { Credential } from '@headless-lms/core/schemas';\nimport { organizations } from './organizations.js';\nimport type { Expect, NoDrift } from './drift.js';\n\nexport const credentials = pgTable(\n 'credentials',\n {\n orgId: text('org_id')\n .notNull()\n .references(() => organizations.id),\n id: text('id').notNull(),\n ciphertext: text('ciphertext').notNull(),\n keyVersion: integer('key_version').notNull().default(1),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({ pk: primaryKey({ columns: [t.orgId, t.id] }) }),\n);\n\ntype _CredentialsDrift = Expect<NoDrift<typeof credentials.$inferSelect, Credential>>;\n"],"mappings":";;;AAUA,MAAa,cAAc,QACzB,eACA;CACE,OAAO,KAAK,QAAQ,CAAC,CAClB,QAAQ,CAAC,CACT,iBAAiB,cAAc,EAAE;CACpC,IAAI,KAAK,IAAI,CAAC,CAAC,QAAQ;CACvB,YAAY,KAAK,YAAY,CAAC,CAAC,QAAQ;CACvC,YAAY,QAAQ,aAAa,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;CACtD,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW;CAChF,WAAW,UAAU,cAAc,EAAE,cAAc,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,WAAW;AAClF,IACC,OAAO,EAAE,IAAI,WAAW,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,EACzD"}
1
+ {"version":3,"file":"credentials.js","names":[],"sources":["../../src/schema/credentials.ts"],"sourcesContent":["// credentials — the shared secure credential store. One row per secret,\n// org-scoped with the standard composite (org_id, id) key. `ciphertext` is\n// base64(iv ‖ auth tag ‖ data), AES-256-GCM with AAD = `${orgId}:${id}`, so a\n// row's payload cannot be decrypted if copied to another row or org.\n// `key_version` names the encryption key used, enabling rotation later.\nimport { pgTable, text, integer, timestamp, primaryKey } from \"drizzle-orm/pg-core\";\nimport type { Credential } from \"@headless-lms/core/schemas\";\nimport { organizations } from \"./organizations.js\";\nimport type { Expect, NoDrift } from \"./drift.js\";\n\nexport const credentials = pgTable(\n \"credentials\",\n {\n orgId: text(\"org_id\")\n .notNull()\n .references(() => organizations.id),\n id: text(\"id\").notNull(),\n ciphertext: text(\"ciphertext\").notNull(),\n keyVersion: integer(\"key_version\").notNull().default(1),\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 _CredentialsDrift = Expect<NoDrift<typeof credentials.$inferSelect, Credential>>;\n"],"mappings":";;;AAUA,MAAa,cAAc,QACzB,eACA;CACE,OAAO,KAAK,QAAQ,CAAC,CAClB,QAAQ,CAAC,CACT,iBAAiB,cAAc,EAAE;CACpC,IAAI,KAAK,IAAI,CAAC,CAAC,QAAQ;CACvB,YAAY,KAAK,YAAY,CAAC,CAAC,QAAQ;CACvC,YAAY,QAAQ,aAAa,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC;CACtD,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"}
@@ -265,6 +265,23 @@ declare const commentReactions: import("drizzle-orm/pg-core").PgTableWithColumns
265
265
  identity: undefined;
266
266
  generated: undefined;
267
267
  }, {}, {}>;
268
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
269
+ name: "updated_at";
270
+ tableName: "comment_reactions";
271
+ dataType: "date";
272
+ columnType: "PgTimestamp";
273
+ data: Date;
274
+ driverParam: string;
275
+ notNull: true;
276
+ hasDefault: true;
277
+ isPrimaryKey: false;
278
+ isAutoincrement: false;
279
+ hasRuntimeDefault: false;
280
+ enumValues: undefined;
281
+ baseColumn: never;
282
+ identity: undefined;
283
+ generated: undefined;
284
+ }, {}, {}>;
268
285
  };
269
286
  dialect: "pg";
270
287
  }>;
@@ -391,6 +408,23 @@ declare const commentReports: import("drizzle-orm/pg-core").PgTableWithColumns<{
391
408
  identity: undefined;
392
409
  generated: undefined;
393
410
  }, {}, {}>;
411
+ updatedAt: import("drizzle-orm/pg-core").PgColumn<{
412
+ name: "updated_at";
413
+ tableName: "comment_reports";
414
+ dataType: "date";
415
+ columnType: "PgTimestamp";
416
+ data: Date;
417
+ driverParam: string;
418
+ notNull: true;
419
+ hasDefault: true;
420
+ isPrimaryKey: false;
421
+ isAutoincrement: false;
422
+ hasRuntimeDefault: false;
423
+ enumValues: undefined;
424
+ baseColumn: never;
425
+ identity: undefined;
426
+ generated: undefined;
427
+ }, {}, {}>;
394
428
  };
395
429
  dialect: "pg";
396
430
  }>;
@@ -46,7 +46,8 @@ const commentReactions = pgTable("comment_reactions", {
46
46
  commentId: text("comment_id").notNull(),
47
47
  orgUserId: text("org_user_id").notNull(),
48
48
  emoji: text("emoji").notNull(),
49
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
49
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
50
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
50
51
  }, (t) => ({
51
52
  pk: primaryKey({ columns: [
52
53
  t.orgId,
@@ -69,7 +70,8 @@ const commentReports = pgTable("comment_reports", {
69
70
  orgUserId: text("org_user_id").notNull(),
70
71
  reason: text("reason").notNull().default(""),
71
72
  resolvedAt: timestamp("resolved_at", { withTimezone: true }),
72
- createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow()
73
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
74
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => /* @__PURE__ */ new Date())
73
75
  }, (t) => ({
74
76
  pk: primaryKey({ columns: [t.orgId, t.id] }),
75
77
  reporterUq: unique().on(t.orgId, t.commentId, t.orgUserId),