@happyvertical/smrt-education 0.57.42 → 0.57.44
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/AGENTS.md +12 -0
- package/README.md +5 -0
- package/agents/course-authoring-service.md +179 -0
- package/agents/course-generation-review.md +79 -0
- package/agents/learner-service.md +33 -0
- package/agents/question-records.md +100 -0
- package/dist/chunks/{workbook-photo-service-C6mbUUp6.js → workbook-photo-service-BxvZDqjM.js} +1440 -309
- package/dist/chunks/workbook-photo-service-BxvZDqjM.js.map +1 -0
- package/dist/course-append-only.d.ts +15 -0
- package/dist/course-append-only.d.ts.map +1 -0
- package/dist/course-authoring-contracts.d.ts +464 -0
- package/dist/course-authoring-contracts.d.ts.map +1 -0
- package/dist/course-authoring-licence.d.ts +15 -0
- package/dist/course-authoring-licence.d.ts.map +1 -0
- package/dist/course-authoring-models.d.ts +291 -0
- package/dist/course-authoring-models.d.ts.map +1 -0
- package/dist/course-authoring-service.d.ts +244 -0
- package/dist/course-authoring-service.d.ts.map +1 -0
- package/dist/course-delegation.d.ts +26 -0
- package/dist/course-delegation.d.ts.map +1 -0
- package/dist/course-generation-contracts.d.ts +150 -0
- package/dist/course-generation-contracts.d.ts.map +1 -0
- package/dist/course-generation-models.d.ts +94 -0
- package/dist/course-generation-models.d.ts.map +1 -0
- package/dist/course-generation-output.d.ts +31 -0
- package/dist/course-generation-output.d.ts.map +1 -0
- package/dist/course-generation-proposal-generator.d.ts +94 -0
- package/dist/course-generation-proposal-generator.d.ts.map +1 -0
- package/dist/course-generation-review-service.d.ts +149 -0
- package/dist/course-generation-review-service.d.ts.map +1 -0
- package/dist/course-release-scope.d.ts +37 -0
- package/dist/course-release-scope.d.ts.map +1 -0
- package/dist/course-review-contracts.d.ts +127 -0
- package/dist/course-review-contracts.d.ts.map +1 -0
- package/dist/course-review-digest.d.ts +4 -0
- package/dist/course-review-digest.d.ts.map +1 -0
- package/dist/course-review-evaluation.d.ts +63 -0
- package/dist/course-review-evaluation.d.ts.map +1 -0
- package/dist/course-review-gate.d.ts +15 -0
- package/dist/course-review-gate.d.ts.map +1 -0
- package/dist/course-review-models.d.ts +96 -0
- package/dist/course-review-models.d.ts.map +1 -0
- package/dist/group-sharing-contracts.d.ts +5 -0
- package/dist/group-sharing-contracts.d.ts.map +1 -1
- package/dist/group-sharing-policy.d.ts.map +1 -1
- package/dist/group-sharing-reader.d.ts.map +1 -1
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5658 -116
- package/dist/index.js.map +1 -1
- package/dist/ingestion.js +1 -1
- package/dist/learner-plan-service.d.ts.map +1 -1
- package/dist/learner-service.d.ts +81 -7
- package/dist/learner-service.d.ts.map +1 -1
- package/dist/learners.d.ts +6 -0
- package/dist/learners.d.ts.map +1 -1
- package/dist/manifest.json +14304 -2177
- package/dist/question-authoring-service.d.ts +125 -0
- package/dist/question-authoring-service.d.ts.map +1 -0
- package/dist/question-content.d.ts +25 -0
- package/dist/question-content.d.ts.map +1 -0
- package/dist/question-contracts.d.ts +240 -0
- package/dist/question-contracts.d.ts.map +1 -0
- package/dist/question-models.d.ts +124 -0
- package/dist/question-models.d.ts.map +1 -0
- package/dist/question-render.d.ts +20 -0
- package/dist/question-render.d.ts.map +1 -0
- package/dist/question-response-contracts.d.ts +54 -0
- package/dist/question-response-contracts.d.ts.map +1 -0
- package/dist/question-response-service.d.ts +63 -0
- package/dist/question-response-service.d.ts.map +1 -0
- package/dist/question-rubric.d.ts +23 -0
- package/dist/question-rubric.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +5410 -910
- package/dist/submission-service.d.ts.map +1 -1
- package/dist/submissions.d.ts +5 -0
- package/dist/submissions.d.ts.map +1 -1
- package/dist/workbook-contracts.d.ts +6 -0
- package/dist/workbook-contracts.d.ts.map +1 -1
- package/dist/workbook-models.d.ts.map +1 -1
- package/package.json +8 -8
- package/dist/chunks/workbook-photo-service-C6mbUUp6.js.map +0 -1
package/dist/chunks/{workbook-photo-service-C6mbUUp6.js → workbook-photo-service-BxvZDqjM.js}
RENAMED
|
@@ -4,8 +4,8 @@ import { AssetCollection, createAssetRuntime, withPrivateAssetAccess } from "@ha
|
|
|
4
4
|
import { TenantCollection } from "@happyvertical/smrt-users";
|
|
5
5
|
//#region \0rolldown/runtime.js
|
|
6
6
|
var __create = Object.create;
|
|
7
|
-
var __defProp$
|
|
8
|
-
var __getOwnPropDesc$
|
|
7
|
+
var __defProp$9 = Object.defineProperty;
|
|
8
|
+
var __getOwnPropDesc$9 = Object.getOwnPropertyDescriptor;
|
|
9
9
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
10
|
var __getProtoOf = Object.getPrototypeOf;
|
|
11
11
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
@@ -13,35 +13,35 @@ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).expor
|
|
|
13
13
|
var __copyProps = (to, from, except, desc) => {
|
|
14
14
|
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
15
15
|
key = keys[i];
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except) __defProp$
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp$9(to, key, {
|
|
17
17
|
get: ((k) => from[k]).bind(null, key),
|
|
18
|
-
enumerable: !(desc = __getOwnPropDesc$
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc$9(from, key)) || desc.enumerable
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
return to;
|
|
22
22
|
};
|
|
23
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp$
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp$9(target, "default", {
|
|
24
24
|
value: mod,
|
|
25
25
|
enumerable: true
|
|
26
26
|
}) : target, mod));
|
|
27
27
|
//#endregion
|
|
28
28
|
//#region src/catalog.ts
|
|
29
|
-
var __defProp$
|
|
30
|
-
var __getOwnPropDesc$
|
|
31
|
-
var __defNormalProp$
|
|
29
|
+
var __defProp$8 = Object.defineProperty;
|
|
30
|
+
var __getOwnPropDesc$8 = Object.getOwnPropertyDescriptor;
|
|
31
|
+
var __defNormalProp$8 = (obj, key, value) => key in obj ? __defProp$8(obj, key, {
|
|
32
32
|
enumerable: true,
|
|
33
33
|
configurable: true,
|
|
34
34
|
writable: true,
|
|
35
35
|
value
|
|
36
36
|
}) : obj[key] = value;
|
|
37
|
-
var __decorateClass$
|
|
38
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
37
|
+
var __decorateClass$8 = (decorators, target, key, kind) => {
|
|
38
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$8(target, key) : target;
|
|
39
39
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
40
|
-
if (kind && result) __defProp$
|
|
40
|
+
if (kind && result) __defProp$8(target, key, result);
|
|
41
41
|
return result;
|
|
42
42
|
};
|
|
43
|
-
var __publicField$
|
|
44
|
-
var closed$
|
|
43
|
+
var __publicField$8 = (obj, key, value) => __defNormalProp$8(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
44
|
+
var closed$6 = {
|
|
45
45
|
api: false,
|
|
46
46
|
mcp: false,
|
|
47
47
|
cli: false
|
|
@@ -100,24 +100,24 @@ var Course = class extends SmrtObject {
|
|
|
100
100
|
return await super.save(options);
|
|
101
101
|
}
|
|
102
102
|
};
|
|
103
|
-
__publicField$
|
|
104
|
-
__decorateClass$
|
|
105
|
-
__decorateClass$
|
|
106
|
-
Course = __decorateClass$
|
|
103
|
+
__publicField$8(Course, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
104
|
+
__decorateClass$8([tenantId({ nullable: true })], Course.prototype, "tenantId", 2);
|
|
105
|
+
__decorateClass$8([field({ required: true })], Course.prototype, "title", 2);
|
|
106
|
+
Course = __decorateClass$8([TenantScoped({ mode: "optional" }), smrt({
|
|
107
107
|
tableStrategy: "sti",
|
|
108
|
-
...closed$
|
|
108
|
+
...closed$6
|
|
109
109
|
})], Course);
|
|
110
110
|
var ExternalCourse = class extends Course {};
|
|
111
|
-
__publicField$
|
|
112
|
-
ExternalCourse = __decorateClass$
|
|
111
|
+
__publicField$8(ExternalCourse, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
112
|
+
ExternalCourse = __decorateClass$8([smrt({
|
|
113
113
|
tableName: "courses",
|
|
114
|
-
...closed$
|
|
114
|
+
...closed$6
|
|
115
115
|
})], ExternalCourse);
|
|
116
116
|
var AuthoredCourse = class extends Course {};
|
|
117
|
-
__publicField$
|
|
118
|
-
AuthoredCourse = __decorateClass$
|
|
117
|
+
__publicField$8(AuthoredCourse, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
118
|
+
AuthoredCourse = __decorateClass$8([smrt({
|
|
119
119
|
tableName: "courses",
|
|
120
|
-
...closed$
|
|
120
|
+
...closed$6
|
|
121
121
|
})], AuthoredCourse);
|
|
122
122
|
var CourseSection = class extends SmrtObject {
|
|
123
123
|
tenantId = null;
|
|
@@ -138,11 +138,11 @@ var CourseSection = class extends SmrtObject {
|
|
|
138
138
|
return await super.save(options);
|
|
139
139
|
}
|
|
140
140
|
};
|
|
141
|
-
__publicField$
|
|
142
|
-
__decorateClass$
|
|
143
|
-
__decorateClass$
|
|
144
|
-
__decorateClass$
|
|
145
|
-
CourseSection = __decorateClass$
|
|
141
|
+
__publicField$8(CourseSection, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
142
|
+
__decorateClass$8([tenantId({ nullable: true })], CourseSection.prototype, "tenantId", 2);
|
|
143
|
+
__decorateClass$8([foreignKey("Course", { required: true })], CourseSection.prototype, "courseId", 2);
|
|
144
|
+
__decorateClass$8([field({ required: true })], CourseSection.prototype, "title", 2);
|
|
145
|
+
CourseSection = __decorateClass$8([TenantScoped({ mode: "optional" }), smrt({ ...closed$6 })], CourseSection);
|
|
146
146
|
var Lesson = class extends SmrtObject {
|
|
147
147
|
tenantId = null;
|
|
148
148
|
sectionId = "";
|
|
@@ -169,19 +169,19 @@ var Lesson = class extends SmrtObject {
|
|
|
169
169
|
return await super.save(options);
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
|
-
__publicField$
|
|
173
|
-
__decorateClass$
|
|
174
|
-
__decorateClass$
|
|
175
|
-
__decorateClass$
|
|
176
|
-
Lesson = __decorateClass$
|
|
172
|
+
__publicField$8(Lesson, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
173
|
+
__decorateClass$8([tenantId({ nullable: true })], Lesson.prototype, "tenantId", 2);
|
|
174
|
+
__decorateClass$8([foreignKey("CourseSection", { required: true })], Lesson.prototype, "sectionId", 2);
|
|
175
|
+
__decorateClass$8([field({ required: true })], Lesson.prototype, "title", 2);
|
|
176
|
+
Lesson = __decorateClass$8([TenantScoped({ mode: "optional" }), smrt({
|
|
177
177
|
tableStrategy: "sti",
|
|
178
|
-
...closed$
|
|
178
|
+
...closed$6
|
|
179
179
|
})], Lesson);
|
|
180
180
|
var Reading = class extends Lesson {};
|
|
181
|
-
__publicField$
|
|
182
|
-
Reading = __decorateClass$
|
|
181
|
+
__publicField$8(Reading, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
182
|
+
Reading = __decorateClass$8([smrt({
|
|
183
183
|
tableName: "lessons",
|
|
184
|
-
...closed$
|
|
184
|
+
...closed$6
|
|
185
185
|
})], Reading);
|
|
186
186
|
var Video = class extends Lesson {
|
|
187
187
|
videoUrl = "";
|
|
@@ -190,10 +190,10 @@ var Video = class extends Lesson {
|
|
|
190
190
|
applyModelOptions(this, options);
|
|
191
191
|
}
|
|
192
192
|
};
|
|
193
|
-
__publicField$
|
|
194
|
-
Video = __decorateClass$
|
|
193
|
+
__publicField$8(Video, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
194
|
+
Video = __decorateClass$8([smrt({
|
|
195
195
|
tableName: "lessons",
|
|
196
|
-
...closed$
|
|
196
|
+
...closed$6
|
|
197
197
|
})], Video);
|
|
198
198
|
var Worksheet = class extends Lesson {
|
|
199
199
|
printableUrl = "";
|
|
@@ -205,16 +205,16 @@ var Worksheet = class extends Lesson {
|
|
|
205
205
|
applyModelOptions(this, options);
|
|
206
206
|
}
|
|
207
207
|
};
|
|
208
|
-
__publicField$
|
|
209
|
-
Worksheet = __decorateClass$
|
|
208
|
+
__publicField$8(Worksheet, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
209
|
+
Worksheet = __decorateClass$8([smrt({
|
|
210
210
|
tableName: "lessons",
|
|
211
|
-
...closed$
|
|
211
|
+
...closed$6
|
|
212
212
|
})], Worksheet);
|
|
213
213
|
var Exercise = class extends Lesson {};
|
|
214
|
-
__publicField$
|
|
215
|
-
Exercise = __decorateClass$
|
|
214
|
+
__publicField$8(Exercise, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
215
|
+
Exercise = __decorateClass$8([smrt({
|
|
216
216
|
tableName: "lessons",
|
|
217
|
-
...closed$
|
|
217
|
+
...closed$6
|
|
218
218
|
})], Exercise);
|
|
219
219
|
GlobalInterceptors.register({
|
|
220
220
|
name: "education-catalog-identity",
|
|
@@ -246,22 +246,22 @@ GlobalInterceptors.register({
|
|
|
246
246
|
});
|
|
247
247
|
//#endregion
|
|
248
248
|
//#region src/learners.ts
|
|
249
|
-
var __defProp$
|
|
250
|
-
var __getOwnPropDesc$
|
|
251
|
-
var __defNormalProp$
|
|
249
|
+
var __defProp$7 = Object.defineProperty;
|
|
250
|
+
var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
|
|
251
|
+
var __defNormalProp$7 = (obj, key, value) => key in obj ? __defProp$7(obj, key, {
|
|
252
252
|
enumerable: true,
|
|
253
253
|
configurable: true,
|
|
254
254
|
writable: true,
|
|
255
255
|
value
|
|
256
256
|
}) : obj[key] = value;
|
|
257
|
-
var __decorateClass$
|
|
258
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
257
|
+
var __decorateClass$7 = (decorators, target, key, kind) => {
|
|
258
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
|
|
259
259
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
260
|
-
if (kind && result) __defProp$
|
|
260
|
+
if (kind && result) __defProp$7(target, key, result);
|
|
261
261
|
return result;
|
|
262
262
|
};
|
|
263
|
-
var __publicField$
|
|
264
|
-
var closed$
|
|
263
|
+
var __publicField$7 = (obj, key, value) => __defNormalProp$7(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
264
|
+
var closed$5 = {
|
|
265
265
|
api: false,
|
|
266
266
|
mcp: false,
|
|
267
267
|
cli: false
|
|
@@ -326,11 +326,11 @@ var Learner = class extends SmrtObject {
|
|
|
326
326
|
return super.save(options);
|
|
327
327
|
}
|
|
328
328
|
};
|
|
329
|
-
__publicField$
|
|
330
|
-
__decorateClass$
|
|
331
|
-
__decorateClass$
|
|
332
|
-
__decorateClass$
|
|
333
|
-
Learner = __decorateClass$
|
|
329
|
+
__publicField$7(Learner, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
330
|
+
__decorateClass$7([tenantId()], Learner.prototype, "tenantId", 2);
|
|
331
|
+
__decorateClass$7([crossPackageRef("@happyvertical/smrt-users:User", { required: true })], Learner.prototype, "userId", 2);
|
|
332
|
+
__decorateClass$7([crossPackageRef("@happyvertical/smrt-profiles:Profile", { required: true })], Learner.prototype, "profileId", 2);
|
|
333
|
+
Learner = __decorateClass$7([TenantScoped({ mode: "required" }), smrt({ ...closed$5 })], Learner);
|
|
334
334
|
var Curriculum = class extends SmrtObject {
|
|
335
335
|
tenantId = "";
|
|
336
336
|
learnerId = "";
|
|
@@ -357,12 +357,12 @@ var Curriculum = class extends SmrtObject {
|
|
|
357
357
|
return super.save(options);
|
|
358
358
|
}
|
|
359
359
|
};
|
|
360
|
-
__publicField$
|
|
361
|
-
__decorateClass$
|
|
362
|
-
__decorateClass$
|
|
363
|
-
__decorateClass$
|
|
364
|
-
Curriculum = __decorateClass$
|
|
365
|
-
...closed$
|
|
360
|
+
__publicField$7(Curriculum, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
361
|
+
__decorateClass$7([tenantId()], Curriculum.prototype, "tenantId", 2);
|
|
362
|
+
__decorateClass$7([foreignKey("Learner", { required: true })], Curriculum.prototype, "learnerId", 2);
|
|
363
|
+
__decorateClass$7([field({ required: true })], Curriculum.prototype, "title", 2);
|
|
364
|
+
Curriculum = __decorateClass$7([TenantScoped({ mode: "required" }), smrt({
|
|
365
|
+
...closed$5,
|
|
366
366
|
tableName: "curricula"
|
|
367
367
|
})], Curriculum);
|
|
368
368
|
var Enrollment = class extends SmrtObject {
|
|
@@ -401,13 +401,13 @@ var Enrollment = class extends SmrtObject {
|
|
|
401
401
|
return super.save(options);
|
|
402
402
|
}
|
|
403
403
|
};
|
|
404
|
-
__publicField$
|
|
405
|
-
__decorateClass$
|
|
406
|
-
__decorateClass$
|
|
407
|
-
__decorateClass$
|
|
408
|
-
__decorateClass$
|
|
409
|
-
Enrollment = __decorateClass$
|
|
410
|
-
...closed$
|
|
404
|
+
__publicField$7(Enrollment, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
405
|
+
__decorateClass$7([tenantId()], Enrollment.prototype, "tenantId", 2);
|
|
406
|
+
__decorateClass$7([foreignKey("Curriculum", { required: true })], Enrollment.prototype, "curriculumId", 2);
|
|
407
|
+
__decorateClass$7([foreignKey("Learner", { required: true })], Enrollment.prototype, "learnerId", 2);
|
|
408
|
+
__decorateClass$7([foreignKey("Course", { required: true })], Enrollment.prototype, "courseId", 2);
|
|
409
|
+
Enrollment = __decorateClass$7([TenantScoped({ mode: "required" }), smrt({
|
|
410
|
+
...closed$5,
|
|
411
411
|
conflictColumns: [
|
|
412
412
|
"tenant_id",
|
|
413
413
|
"curriculum_id",
|
|
@@ -429,6 +429,23 @@ var LessonProgress = class extends SmrtObject {
|
|
|
429
429
|
super(options);
|
|
430
430
|
applyOptions$1(this, options);
|
|
431
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* A pinned enrollment may follow a later release, whose lessons live on that
|
|
434
|
+
* release's own materialized Course row. Hosts that have not migrated the
|
|
435
|
+
* authoring tables keep the strict enrollment-course rule.
|
|
436
|
+
*/
|
|
437
|
+
async inPinnedRelease(courseId) {
|
|
438
|
+
if (!await this.db.tableExists("enrollment_release_pins")) return false;
|
|
439
|
+
const pins = await this.db.list("enrollment_release_pins", {
|
|
440
|
+
tenant_id: this.tenantId,
|
|
441
|
+
enrollment_id: this.enrollmentId
|
|
442
|
+
});
|
|
443
|
+
for (const pin of pins) if ((await this.db.get("course_releases", {
|
|
444
|
+
id: pin.release_id,
|
|
445
|
+
tenant_id: this.tenantId
|
|
446
|
+
}))?.release_course_id === courseId) return true;
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
432
449
|
async save(options) {
|
|
433
450
|
await immutableIdentity$1(this, {
|
|
434
451
|
enrollment_id: this.enrollmentId,
|
|
@@ -449,26 +466,24 @@ var LessonProgress = class extends SmrtObject {
|
|
|
449
466
|
tenant_id: this.tenantId
|
|
450
467
|
});
|
|
451
468
|
const lesson = await this.db.get("lessons", { id: this.lessonId });
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
course_id: enrollment.course_id
|
|
455
|
-
})) throw new Error("Progress requires its enrollment learner and course lesson");
|
|
469
|
+
const section = lesson ? await this.db.get("course_sections", { id: lesson.section_id }) : null;
|
|
470
|
+
if (!enrollment || !section || !(section.course_id === enrollment.course_id || await this.inPinnedRelease(String(section.course_id)))) throw new Error("Progress requires its enrollment learner and course lesson");
|
|
456
471
|
for (const value of [this.attempts, this.timeSpentSeconds]) if (!Number.isSafeInteger(value) || value < 0) throw new Error("Progress counters must be nonnegative integers");
|
|
457
472
|
if (this.bestScore !== null && (!Number.isFinite(this.bestScore) || this.bestScore < 0 || this.bestScore > 1)) throw new Error("Score must be null or a number from zero to one");
|
|
458
473
|
return super.save(options);
|
|
459
474
|
}
|
|
460
475
|
};
|
|
461
|
-
__publicField$
|
|
462
|
-
__decorateClass$
|
|
463
|
-
__decorateClass$
|
|
464
|
-
__decorateClass$
|
|
465
|
-
__decorateClass$
|
|
466
|
-
__decorateClass$
|
|
476
|
+
__publicField$7(LessonProgress, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
477
|
+
__decorateClass$7([tenantId()], LessonProgress.prototype, "tenantId", 2);
|
|
478
|
+
__decorateClass$7([foreignKey("Enrollment", { required: true })], LessonProgress.prototype, "enrollmentId", 2);
|
|
479
|
+
__decorateClass$7([foreignKey("Learner", { required: true })], LessonProgress.prototype, "learnerId", 2);
|
|
480
|
+
__decorateClass$7([foreignKey("Lesson", { required: true })], LessonProgress.prototype, "lessonId", 2);
|
|
481
|
+
__decorateClass$7([field({
|
|
467
482
|
type: "decimal",
|
|
468
483
|
nullable: true
|
|
469
484
|
})], LessonProgress.prototype, "bestScore", 2);
|
|
470
|
-
LessonProgress = __decorateClass$
|
|
471
|
-
...closed$
|
|
485
|
+
LessonProgress = __decorateClass$7([TenantScoped({ mode: "required" }), smrt({
|
|
486
|
+
...closed$5,
|
|
472
487
|
tableName: "lesson_progress",
|
|
473
488
|
conflictColumns: [
|
|
474
489
|
"tenant_id",
|
|
@@ -478,21 +493,21 @@ LessonProgress = __decorateClass$6([TenantScoped({ mode: "required" }), smrt({
|
|
|
478
493
|
})], LessonProgress);
|
|
479
494
|
//#endregion
|
|
480
495
|
//#region src/lesson-assets.ts
|
|
481
|
-
var __defProp$
|
|
482
|
-
var __getOwnPropDesc$
|
|
483
|
-
var __defNormalProp$
|
|
496
|
+
var __defProp$6 = Object.defineProperty;
|
|
497
|
+
var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
|
|
498
|
+
var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, {
|
|
484
499
|
enumerable: true,
|
|
485
500
|
configurable: true,
|
|
486
501
|
writable: true,
|
|
487
502
|
value
|
|
488
503
|
}) : obj[key] = value;
|
|
489
|
-
var __decorateClass$
|
|
490
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
504
|
+
var __decorateClass$6 = (decorators, target, key, kind) => {
|
|
505
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
|
|
491
506
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
492
|
-
if (kind && result) __defProp$
|
|
507
|
+
if (kind && result) __defProp$6(target, key, result);
|
|
493
508
|
return result;
|
|
494
509
|
};
|
|
495
|
-
var __publicField$
|
|
510
|
+
var __publicField$6 = (obj, key, value) => __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
496
511
|
var LessonAsset = class extends SmrtObject {
|
|
497
512
|
tenantId = null;
|
|
498
513
|
lessonId = "";
|
|
@@ -526,12 +541,12 @@ var LessonAsset = class extends SmrtObject {
|
|
|
526
541
|
return await super.save(options);
|
|
527
542
|
}
|
|
528
543
|
};
|
|
529
|
-
__publicField$
|
|
530
|
-
__decorateClass$
|
|
531
|
-
__decorateClass$
|
|
532
|
-
__decorateClass$
|
|
533
|
-
__decorateClass$
|
|
534
|
-
LessonAsset = __decorateClass$
|
|
544
|
+
__publicField$6(LessonAsset, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
545
|
+
__decorateClass$6([tenantId({ nullable: true })], LessonAsset.prototype, "tenantId", 2);
|
|
546
|
+
__decorateClass$6([foreignKey("Lesson", { required: true })], LessonAsset.prototype, "lessonId", 2);
|
|
547
|
+
__decorateClass$6([crossPackageRef("@happyvertical/smrt-assets:Asset", { required: true })], LessonAsset.prototype, "assetId", 2);
|
|
548
|
+
__decorateClass$6([field({ required: true })], LessonAsset.prototype, "relationship", 2);
|
|
549
|
+
LessonAsset = __decorateClass$6([TenantScoped({ mode: "optional" }), smrt({
|
|
535
550
|
tableName: "lesson_assets",
|
|
536
551
|
conflictColumns: [
|
|
537
552
|
"tenant_id",
|
|
@@ -544,23 +559,51 @@ LessonAsset = __decorateClass$5([TenantScoped({ mode: "optional" }), smrt({
|
|
|
544
559
|
cli: false
|
|
545
560
|
})], LessonAsset);
|
|
546
561
|
//#endregion
|
|
562
|
+
//#region src/question-rubric.ts
|
|
563
|
+
var CRITERION_ID = /^[A-Za-z0-9._:-]{1,80}$/;
|
|
564
|
+
function parseQuestionRubric(value) {
|
|
565
|
+
const criteria = value?.criteria;
|
|
566
|
+
if (!Array.isArray(criteria) || !criteria.length || criteria.length > 50) throw new Error("Rubric requires between one and fifty criteria");
|
|
567
|
+
const seen = /* @__PURE__ */ new Set();
|
|
568
|
+
return { criteria: criteria.map((c) => {
|
|
569
|
+
if (!c || typeof c.id !== "string" || !CRITERION_ID.test(c.id) || seen.has(c.id) || typeof c.label !== "string" || !c.label.trim() || c.label.length > 500 || !Number.isFinite(c.maxPoints) || c.maxPoints <= 0) throw new Error("Invalid rubric criterion");
|
|
570
|
+
seen.add(c.id);
|
|
571
|
+
return {
|
|
572
|
+
id: c.id,
|
|
573
|
+
label: c.label,
|
|
574
|
+
maxPoints: c.maxPoints
|
|
575
|
+
};
|
|
576
|
+
}) };
|
|
577
|
+
}
|
|
578
|
+
function validateRubricResults(rubricJson, results) {
|
|
579
|
+
const entries = Object.entries(results);
|
|
580
|
+
if (!entries.length) return;
|
|
581
|
+
if (!rubricJson) throw new Error("Rubric results require a rubric on the question revision");
|
|
582
|
+
const rubric = parseQuestionRubric(JSON.parse(rubricJson));
|
|
583
|
+
const max = new Map(rubric.criteria.map((c) => [c.id, c.maxPoints]));
|
|
584
|
+
for (const [id, raw] of entries) {
|
|
585
|
+
const points = raw && typeof raw === "object" && !Array.isArray(raw) ? raw.points : raw;
|
|
586
|
+
if (!max.has(id) || typeof points !== "number" || !Number.isFinite(points) || points < 0 || points > max.get(id)) throw new Error("Rubric results do not match the question rubric");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
//#endregion
|
|
547
590
|
//#region src/submissions.ts
|
|
548
|
-
var __defProp$
|
|
549
|
-
var __getOwnPropDesc$
|
|
550
|
-
var __defNormalProp$
|
|
591
|
+
var __defProp$5 = Object.defineProperty;
|
|
592
|
+
var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
|
|
593
|
+
var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, {
|
|
551
594
|
enumerable: true,
|
|
552
595
|
configurable: true,
|
|
553
596
|
writable: true,
|
|
554
597
|
value
|
|
555
598
|
}) : obj[key] = value;
|
|
556
|
-
var __decorateClass$
|
|
557
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
599
|
+
var __decorateClass$5 = (decorators, target, key, kind) => {
|
|
600
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
|
|
558
601
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
559
|
-
if (kind && result) __defProp$
|
|
602
|
+
if (kind && result) __defProp$5(target, key, result);
|
|
560
603
|
return result;
|
|
561
604
|
};
|
|
562
|
-
var __publicField$
|
|
563
|
-
var closed$
|
|
605
|
+
var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
606
|
+
var closed$4 = {
|
|
564
607
|
api: false,
|
|
565
608
|
mcp: false,
|
|
566
609
|
cli: false
|
|
@@ -650,20 +693,20 @@ var Submission = class extends SmrtObject {
|
|
|
650
693
|
return super.save(options);
|
|
651
694
|
}
|
|
652
695
|
};
|
|
653
|
-
__publicField$
|
|
654
|
-
__decorateClass$
|
|
655
|
-
__decorateClass$
|
|
656
|
-
__decorateClass$
|
|
657
|
-
__decorateClass$
|
|
658
|
-
__decorateClass$
|
|
659
|
-
__decorateClass$
|
|
660
|
-
__decorateClass$
|
|
696
|
+
__publicField$5(Submission, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
697
|
+
__decorateClass$5([tenantId()], Submission.prototype, "tenantId", 2);
|
|
698
|
+
__decorateClass$5([foreignKey("Learner", { required: true })], Submission.prototype, "learnerId", 2);
|
|
699
|
+
__decorateClass$5([foreignKey("Lesson", { required: true })], Submission.prototype, "lessonId", 2);
|
|
700
|
+
__decorateClass$5([foreignKey("LessonProgress", { required: true })], Submission.prototype, "progressId", 2);
|
|
701
|
+
__decorateClass$5([field({ required: true })], Submission.prototype, "kind", 2);
|
|
702
|
+
__decorateClass$5([foreignKey("Submission", { nullable: true })], Submission.prototype, "previousSubmissionId", 2);
|
|
703
|
+
__decorateClass$5([field({
|
|
661
704
|
type: "text",
|
|
662
705
|
nullable: true
|
|
663
706
|
})], Submission.prototype, "intakeItemId", 2);
|
|
664
|
-
Submission = __decorateClass$
|
|
707
|
+
Submission = __decorateClass$5([TenantScoped({ mode: "required" }), smrt({
|
|
665
708
|
tableStrategy: "sti",
|
|
666
|
-
...closed$
|
|
709
|
+
...closed$4
|
|
667
710
|
})], Submission);
|
|
668
711
|
var SubmissionIntakeBinding = class extends SmrtObject {
|
|
669
712
|
tenantId = "";
|
|
@@ -684,16 +727,16 @@ var SubmissionIntakeBinding = class extends SmrtObject {
|
|
|
684
727
|
return super.save(options);
|
|
685
728
|
}
|
|
686
729
|
};
|
|
687
|
-
__publicField$
|
|
688
|
-
__decorateClass$
|
|
689
|
-
__decorateClass$
|
|
730
|
+
__publicField$5(SubmissionIntakeBinding, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
731
|
+
__decorateClass$5([tenantId()], SubmissionIntakeBinding.prototype, "tenantId", 2);
|
|
732
|
+
__decorateClass$5([field({
|
|
690
733
|
required: true,
|
|
691
734
|
type: "text"
|
|
692
735
|
})], SubmissionIntakeBinding.prototype, "intakeItemId", 2);
|
|
693
|
-
__decorateClass$
|
|
694
|
-
SubmissionIntakeBinding = __decorateClass$
|
|
736
|
+
__decorateClass$5([foreignKey("Submission", { required: true })], SubmissionIntakeBinding.prototype, "submissionId", 2);
|
|
737
|
+
SubmissionIntakeBinding = __decorateClass$5([TenantScoped({ mode: "required" }), smrt({
|
|
695
738
|
tableName: "submission_intake_bindings",
|
|
696
|
-
...closed$
|
|
739
|
+
...closed$4,
|
|
697
740
|
conflictColumns: ["tenant_id", "intake_item_id"]
|
|
698
741
|
})], SubmissionIntakeBinding);
|
|
699
742
|
var ScannedSubmission = class extends Submission {
|
|
@@ -715,10 +758,10 @@ var ScannedSubmission = class extends Submission {
|
|
|
715
758
|
return super.save(options);
|
|
716
759
|
}
|
|
717
760
|
};
|
|
718
|
-
__publicField$
|
|
719
|
-
ScannedSubmission = __decorateClass$
|
|
761
|
+
__publicField$5(ScannedSubmission, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
762
|
+
ScannedSubmission = __decorateClass$5([smrt({
|
|
720
763
|
tableName: "submissions",
|
|
721
|
-
...closed$
|
|
764
|
+
...closed$4
|
|
722
765
|
})], ScannedSubmission);
|
|
723
766
|
var ExternalCompletion = class extends Submission {
|
|
724
767
|
reportedBy = "learner";
|
|
@@ -738,17 +781,17 @@ var ExternalCompletion = class extends Submission {
|
|
|
738
781
|
return super.save(options);
|
|
739
782
|
}
|
|
740
783
|
};
|
|
741
|
-
__publicField$
|
|
742
|
-
__decorateClass$
|
|
743
|
-
ExternalCompletion = __decorateClass$
|
|
784
|
+
__publicField$5(ExternalCompletion, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
785
|
+
__decorateClass$5([field({ type: "decimal" })], ExternalCompletion.prototype, "reportedScore", 2);
|
|
786
|
+
ExternalCompletion = __decorateClass$5([smrt({
|
|
744
787
|
tableName: "submissions",
|
|
745
|
-
...closed$
|
|
788
|
+
...closed$4
|
|
746
789
|
})], ExternalCompletion);
|
|
747
790
|
var Attestation = class extends Submission {};
|
|
748
|
-
__publicField$
|
|
749
|
-
Attestation = __decorateClass$
|
|
791
|
+
__publicField$5(Attestation, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
792
|
+
Attestation = __decorateClass$5([smrt({
|
|
750
793
|
tableName: "submissions",
|
|
751
|
-
...closed$
|
|
794
|
+
...closed$4
|
|
752
795
|
})], Attestation);
|
|
753
796
|
var SubmissionAsset = class extends SmrtObject {
|
|
754
797
|
tenantId = "";
|
|
@@ -785,12 +828,12 @@ var SubmissionAsset = class extends SmrtObject {
|
|
|
785
828
|
return super.save(options);
|
|
786
829
|
}
|
|
787
830
|
};
|
|
788
|
-
__publicField$
|
|
789
|
-
__decorateClass$
|
|
790
|
-
__decorateClass$
|
|
791
|
-
__decorateClass$
|
|
792
|
-
__decorateClass$
|
|
793
|
-
SubmissionAsset = __decorateClass$
|
|
831
|
+
__publicField$5(SubmissionAsset, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
832
|
+
__decorateClass$5([tenantId()], SubmissionAsset.prototype, "tenantId", 2);
|
|
833
|
+
__decorateClass$5([foreignKey("Submission", { required: true })], SubmissionAsset.prototype, "submissionId", 2);
|
|
834
|
+
__decorateClass$5([crossPackageRef("@happyvertical/smrt-assets:Asset", { required: true })], SubmissionAsset.prototype, "assetId", 2);
|
|
835
|
+
__decorateClass$5([field({ required: true })], SubmissionAsset.prototype, "relationship", 2);
|
|
836
|
+
SubmissionAsset = __decorateClass$5([TenantScoped({ mode: "required" }), smrt({
|
|
794
837
|
tableName: "submission_assets",
|
|
795
838
|
conflictColumns: [
|
|
796
839
|
"tenant_id",
|
|
@@ -798,7 +841,7 @@ SubmissionAsset = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
|
798
841
|
"asset_id",
|
|
799
842
|
"relationship"
|
|
800
843
|
],
|
|
801
|
-
...closed$
|
|
844
|
+
...closed$4
|
|
802
845
|
})], SubmissionAsset);
|
|
803
846
|
var Grade = class extends SmrtObject {
|
|
804
847
|
tenantId = "";
|
|
@@ -809,6 +852,7 @@ var Grade = class extends SmrtObject {
|
|
|
809
852
|
pointsEarned = null;
|
|
810
853
|
pointsPossible = null;
|
|
811
854
|
rubricResults = "{}";
|
|
855
|
+
questionRevisionId = null;
|
|
812
856
|
feedback = "";
|
|
813
857
|
gradedAt = /* @__PURE__ */ new Date();
|
|
814
858
|
constructor(options = {}) {
|
|
@@ -836,27 +880,39 @@ var Grade = class extends SmrtObject {
|
|
|
836
880
|
requireScore(this.score, "Score");
|
|
837
881
|
for (const [value, label] of [[this.pointsEarned, "Points earned"], [this.pointsPossible, "Points possible"]]) if (value !== null) requireNonnegativeInteger(value, label);
|
|
838
882
|
if (this.pointsEarned !== null && this.pointsPossible !== null && this.pointsEarned > this.pointsPossible) throw new Error("Points earned cannot exceed points possible");
|
|
883
|
+
if (this.questionRevisionId !== null) {
|
|
884
|
+
const revision = await this.db.get("question_revisions", {
|
|
885
|
+
id: this.questionRevisionId,
|
|
886
|
+
tenant_id: this.tenantId
|
|
887
|
+
});
|
|
888
|
+
if (!revision) throw new Error("Grade requires its tenant question revision");
|
|
889
|
+
validateRubricResults(revision.rubric_json, this.getRubricResults());
|
|
890
|
+
}
|
|
839
891
|
return super.save(options);
|
|
840
892
|
}
|
|
841
893
|
};
|
|
842
|
-
__publicField$
|
|
843
|
-
__decorateClass$
|
|
844
|
-
__decorateClass$
|
|
845
|
-
__decorateClass$
|
|
846
|
-
__decorateClass$
|
|
847
|
-
__decorateClass$
|
|
894
|
+
__publicField$5(Grade, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
895
|
+
__decorateClass$5([tenantId()], Grade.prototype, "tenantId", 2);
|
|
896
|
+
__decorateClass$5([foreignKey("Submission", { required: true })], Grade.prototype, "submissionId", 2);
|
|
897
|
+
__decorateClass$5([field({ required: true })], Grade.prototype, "graderKind", 2);
|
|
898
|
+
__decorateClass$5([crossPackageRef("@happyvertical/smrt-profiles:Profile", { nullable: true })], Grade.prototype, "graderProfileId", 2);
|
|
899
|
+
__decorateClass$5([field({
|
|
848
900
|
type: "decimal",
|
|
849
901
|
nullable: true
|
|
850
902
|
})], Grade.prototype, "score", 2);
|
|
851
|
-
__decorateClass$
|
|
903
|
+
__decorateClass$5([field({
|
|
852
904
|
type: "integer",
|
|
853
905
|
nullable: true
|
|
854
906
|
})], Grade.prototype, "pointsEarned", 2);
|
|
855
|
-
__decorateClass$
|
|
907
|
+
__decorateClass$5([field({
|
|
856
908
|
type: "integer",
|
|
857
909
|
nullable: true
|
|
858
910
|
})], Grade.prototype, "pointsPossible", 2);
|
|
859
|
-
|
|
911
|
+
__decorateClass$5([field({
|
|
912
|
+
type: "text",
|
|
913
|
+
nullable: true
|
|
914
|
+
})], Grade.prototype, "questionRevisionId", 2);
|
|
915
|
+
Grade = __decorateClass$5([TenantScoped({ mode: "required" }), smrt({ ...closed$4 })], Grade);
|
|
860
916
|
//#endregion
|
|
861
917
|
//#region src/collections.ts
|
|
862
918
|
var CourseCollection = class extends SmrtCollection {
|
|
@@ -915,143 +971,1051 @@ var ScannedSubmissionCollection = class extends SmrtCollection {
|
|
|
915
971
|
var ExternalCompletionCollection = class extends SmrtCollection {
|
|
916
972
|
static _itemClass = ExternalCompletion;
|
|
917
973
|
};
|
|
918
|
-
var AttestationCollection = class extends SmrtCollection {
|
|
919
|
-
static _itemClass = Attestation;
|
|
974
|
+
var AttestationCollection = class extends SmrtCollection {
|
|
975
|
+
static _itemClass = Attestation;
|
|
976
|
+
};
|
|
977
|
+
var GradeCollection = class extends SmrtCollection {
|
|
978
|
+
static _itemClass = Grade;
|
|
979
|
+
};
|
|
980
|
+
var SubmissionAssetCollection = class extends SmrtJunction {
|
|
981
|
+
static _itemClass = SubmissionAsset;
|
|
982
|
+
leftField = "submissionId";
|
|
983
|
+
rightField = "assetId";
|
|
984
|
+
};
|
|
985
|
+
//#endregion
|
|
986
|
+
//#region src/catalog-service.ts
|
|
987
|
+
var CatalogAuthorizationError = class extends Error {
|
|
988
|
+
constructor() {
|
|
989
|
+
super("Education catalog access is not authorized.");
|
|
990
|
+
this.name = "CatalogAuthorizationError";
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
function projectLesson(lesson, includeAnswerKeys) {
|
|
994
|
+
if (includeAnswerKeys || !("answerUrl" in lesson)) return lesson;
|
|
995
|
+
return Object.assign(Object.create(Object.getPrototypeOf(lesson)), lesson, { answerUrl: "" });
|
|
996
|
+
}
|
|
997
|
+
var CatalogService = class {
|
|
998
|
+
constructor(courses, sections, lessons, assets, tenants, authorize) {
|
|
999
|
+
this.courses = courses;
|
|
1000
|
+
this.sections = sections;
|
|
1001
|
+
this.lessons = lessons;
|
|
1002
|
+
this.assets = assets;
|
|
1003
|
+
this.tenants = tenants;
|
|
1004
|
+
this.authorize = authorize;
|
|
1005
|
+
}
|
|
1006
|
+
courses;
|
|
1007
|
+
sections;
|
|
1008
|
+
lessons;
|
|
1009
|
+
assets;
|
|
1010
|
+
tenants;
|
|
1011
|
+
authorize;
|
|
1012
|
+
executor() {
|
|
1013
|
+
const executor = this.courses.db;
|
|
1014
|
+
if (!executor || [
|
|
1015
|
+
this.sections,
|
|
1016
|
+
this.lessons,
|
|
1017
|
+
this.assets
|
|
1018
|
+
].some((collection) => collection.db !== executor)) throw new CatalogAuthorizationError();
|
|
1019
|
+
return executor;
|
|
1020
|
+
}
|
|
1021
|
+
authorizeInScope(request) {
|
|
1022
|
+
return this.authorize({
|
|
1023
|
+
...request,
|
|
1024
|
+
executor: this.executor()
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
async findVisible(request) {
|
|
1028
|
+
const { tenantId, actorUserId, includePrivate = false, options = {} } = request;
|
|
1029
|
+
if (!tenantId.trim() || !actorUserId.trim()) throw new CatalogAuthorizationError();
|
|
1030
|
+
const actor = {
|
|
1031
|
+
actorTenantId: tenantId,
|
|
1032
|
+
actorUserId,
|
|
1033
|
+
rootTenantId: tenantId
|
|
1034
|
+
};
|
|
1035
|
+
if (await this.authorizeInScope({
|
|
1036
|
+
...actor,
|
|
1037
|
+
action: "education.catalog.read"
|
|
1038
|
+
}) !== true) throw new CatalogAuthorizationError();
|
|
1039
|
+
if (includePrivate && await this.authorizeInScope({
|
|
1040
|
+
...actor,
|
|
1041
|
+
action: "education.catalog.read-private"
|
|
1042
|
+
}) !== true) throw new CatalogAuthorizationError();
|
|
1043
|
+
return this.tenants.withTenantCatalogReadScope({
|
|
1044
|
+
collections: [
|
|
1045
|
+
"@happyvertical/smrt-education:Course",
|
|
1046
|
+
"@happyvertical/smrt-education:CourseSection",
|
|
1047
|
+
"@happyvertical/smrt-education:Lesson",
|
|
1048
|
+
"@happyvertical/smrt-education:LessonAsset"
|
|
1049
|
+
],
|
|
1050
|
+
authorize: async (scope2) => scope2.mode === "catalog" && scope2.actorTenantId === tenantId && scope2.actorUserId === actorUserId && scope2.rootTenantId === tenantId
|
|
1051
|
+
}, async () => this.courses.list({
|
|
1052
|
+
...options,
|
|
1053
|
+
where: [
|
|
1054
|
+
[{ "licenseKind !=": "personal-use-copy" }],
|
|
1055
|
+
[{ licenseKind: null }],
|
|
1056
|
+
...includePrivate ? [[{
|
|
1057
|
+
licenseKind: "personal-use-copy",
|
|
1058
|
+
tenantId
|
|
1059
|
+
}]] : []
|
|
1060
|
+
]
|
|
1061
|
+
}));
|
|
1062
|
+
}
|
|
1063
|
+
/** Project one already-visible course without exposing answer-key assets by default. */
|
|
1064
|
+
async projectCourse(request) {
|
|
1065
|
+
const course = (await this.findVisible(request)).find((item) => item.id === request.courseId);
|
|
1066
|
+
if (!course?.id) return null;
|
|
1067
|
+
const includeAnswerKeys = request.includeAnswerKeys === true;
|
|
1068
|
+
const actor = {
|
|
1069
|
+
actorTenantId: request.tenantId,
|
|
1070
|
+
actorUserId: request.actorUserId,
|
|
1071
|
+
rootTenantId: request.tenantId
|
|
1072
|
+
};
|
|
1073
|
+
if (includeAnswerKeys && await this.authorizeInScope({
|
|
1074
|
+
...actor,
|
|
1075
|
+
action: "education.catalog.answer-key.read"
|
|
1076
|
+
}) !== true) throw new CatalogAuthorizationError();
|
|
1077
|
+
return this.tenants.withTenantCatalogReadScope({
|
|
1078
|
+
collections: [
|
|
1079
|
+
"@happyvertical/smrt-education:CourseSection",
|
|
1080
|
+
"@happyvertical/smrt-education:Lesson",
|
|
1081
|
+
"@happyvertical/smrt-education:LessonAsset"
|
|
1082
|
+
],
|
|
1083
|
+
authorize: async (scope) => scope.mode === "catalog" && scope.actorTenantId === request.tenantId && scope.actorUserId === request.actorUserId
|
|
1084
|
+
}, async () => {
|
|
1085
|
+
const sections = await this.sections.list({
|
|
1086
|
+
where: { courseId: course.id },
|
|
1087
|
+
orderBy: ["sortOrder ASC", "id ASC"]
|
|
1088
|
+
});
|
|
1089
|
+
const result = [];
|
|
1090
|
+
for (const section of sections) {
|
|
1091
|
+
if (section.tenantId !== course.tenantId) continue;
|
|
1092
|
+
const lessons = await this.lessons.list({
|
|
1093
|
+
where: { sectionId: section.id },
|
|
1094
|
+
orderBy: ["sortOrder ASC", "id ASC"]
|
|
1095
|
+
});
|
|
1096
|
+
result.push({
|
|
1097
|
+
section,
|
|
1098
|
+
lessons: await Promise.all(lessons.filter((lesson) => lesson.tenantId === course.tenantId).map(async (lesson) => ({
|
|
1099
|
+
lesson: projectLesson(lesson, includeAnswerKeys),
|
|
1100
|
+
assets: (await this.assets.byLeft(String(lesson.id))).filter((asset) => asset.tenantId === course.tenantId && (includeAnswerKeys || asset.relationship !== "answer-key"))
|
|
1101
|
+
})))
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
return {
|
|
1105
|
+
course,
|
|
1106
|
+
sections: result
|
|
1107
|
+
};
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
//#endregion
|
|
1112
|
+
//#region src/course-authoring-contracts.ts
|
|
1113
|
+
var COURSE_AUTHORING_ACTIONS = [
|
|
1114
|
+
"education.course.read-draft",
|
|
1115
|
+
"education.course.author",
|
|
1116
|
+
"education.course.generate",
|
|
1117
|
+
"education.course.review",
|
|
1118
|
+
"education.course.release",
|
|
1119
|
+
"education.course.assign",
|
|
1120
|
+
"education.course.archive"
|
|
1121
|
+
];
|
|
1122
|
+
var COURSE_PROVENANCES = [
|
|
1123
|
+
"supplied",
|
|
1124
|
+
"generated",
|
|
1125
|
+
"edited",
|
|
1126
|
+
"extracted"
|
|
1127
|
+
];
|
|
1128
|
+
var MAX_PAGE_SIZE = 100;
|
|
1129
|
+
var DEFAULT_PAGE_SIZE = 50;
|
|
1130
|
+
var CourseAuthoringError = class extends Error {
|
|
1131
|
+
constructor(code, message) {
|
|
1132
|
+
super(message);
|
|
1133
|
+
this.code = code;
|
|
1134
|
+
this.name = new.target.name;
|
|
1135
|
+
}
|
|
1136
|
+
code;
|
|
1137
|
+
};
|
|
1138
|
+
var CourseAuthoringAuthorizationError = class extends CourseAuthoringError {
|
|
1139
|
+
constructor() {
|
|
1140
|
+
super("not-authorized", "Course authoring access is not authorized.");
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
var CourseAuthoringValidationError = class extends CourseAuthoringError {
|
|
1144
|
+
constructor(message) {
|
|
1145
|
+
super("invalid-request", message);
|
|
1146
|
+
}
|
|
1147
|
+
};
|
|
1148
|
+
var CourseNotFoundError = class extends CourseAuthoringError {
|
|
1149
|
+
constructor() {
|
|
1150
|
+
super("course-not-found", "Course was not found.");
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
1153
|
+
var CourseDiscardedError = class extends CourseAuthoringError {
|
|
1154
|
+
constructor() {
|
|
1155
|
+
super("course-discarded", "Course draft was discarded.");
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
var CourseArchivedError = class extends CourseAuthoringError {
|
|
1159
|
+
constructor() {
|
|
1160
|
+
super("course-archived", "Course is archived; unarchive it first.");
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
var CourseStaleHeadError = class extends CourseAuthoringError {
|
|
1164
|
+
constructor(currentHeadRevisionId) {
|
|
1165
|
+
super("stale-head", "The draft head moved; reload and retry.");
|
|
1166
|
+
this.currentHeadRevisionId = currentHeadRevisionId;
|
|
1167
|
+
}
|
|
1168
|
+
currentHeadRevisionId;
|
|
1169
|
+
};
|
|
1170
|
+
var CoursePolicyBasisMismatchError = class extends CourseAuthoringError {
|
|
1171
|
+
constructor() {
|
|
1172
|
+
super("policy-basis-mismatch", "The supplied policy basis differs from the one the authorizer relied on.");
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
var CourseRequestKeyConflictError = class extends CourseAuthoringError {
|
|
1176
|
+
constructor() {
|
|
1177
|
+
super("request-key-conflict", "The request key was already used for a different request.");
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
1180
|
+
var ReviewGateRequiredError = class extends CourseAuthoringError {
|
|
1181
|
+
constructor() {
|
|
1182
|
+
super("review-gate-required", "Release requires a configured review gate; none is configured.");
|
|
1183
|
+
}
|
|
1184
|
+
};
|
|
1185
|
+
var ReviewNotCurrentError = class extends CourseAuthoringError {
|
|
1186
|
+
constructor(reason) {
|
|
1187
|
+
super("review-not-current", "Release requires a current review of this exact revision.");
|
|
1188
|
+
this.reason = reason;
|
|
1189
|
+
}
|
|
1190
|
+
reason;
|
|
1191
|
+
};
|
|
1192
|
+
var CourseRevisionAlreadyReleasedError = class extends CourseAuthoringError {
|
|
1193
|
+
constructor(releaseId) {
|
|
1194
|
+
super("revision-already-released", "This revision is already released.");
|
|
1195
|
+
this.releaseId = releaseId;
|
|
1196
|
+
}
|
|
1197
|
+
releaseId;
|
|
1198
|
+
};
|
|
1199
|
+
var CourseDiscardRefusedError = class extends CourseAuthoringError {
|
|
1200
|
+
constructor(references) {
|
|
1201
|
+
super("discard-refused", "Only drafts with no release, enrollment, attempt, review or variant can be discarded.");
|
|
1202
|
+
this.references = references;
|
|
1203
|
+
}
|
|
1204
|
+
references;
|
|
1205
|
+
};
|
|
1206
|
+
var EnrollmentPinConflictError = class extends CourseAuthoringError {
|
|
1207
|
+
constructor(message = "Enrollment pin request conflicts with current state.") {
|
|
1208
|
+
super("enrollment-pin-conflict", message);
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
var CourseMaterialCopyError = class extends CourseAuthoringError {
|
|
1212
|
+
constructor(message = "Course material could not be copied to an owned asset.") {
|
|
1213
|
+
super("material-copy-failed", message);
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
//#endregion
|
|
1217
|
+
//#region src/course-authoring-models.ts
|
|
1218
|
+
var __defProp$4 = Object.defineProperty;
|
|
1219
|
+
var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
|
|
1220
|
+
var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, {
|
|
1221
|
+
enumerable: true,
|
|
1222
|
+
configurable: true,
|
|
1223
|
+
writable: true,
|
|
1224
|
+
value
|
|
1225
|
+
}) : obj[key] = value;
|
|
1226
|
+
var __decorateClass$4 = (decorators, target, key, kind) => {
|
|
1227
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
|
|
1228
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
1229
|
+
if (kind && result) __defProp$4(target, key, result);
|
|
1230
|
+
return result;
|
|
1231
|
+
};
|
|
1232
|
+
var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
1233
|
+
var closed$3 = {
|
|
1234
|
+
api: false,
|
|
1235
|
+
mcp: false,
|
|
1236
|
+
cli: false
|
|
1237
|
+
};
|
|
1238
|
+
var LICENSES = [
|
|
1239
|
+
"own-work",
|
|
1240
|
+
"open-license",
|
|
1241
|
+
"personal-use-copy",
|
|
1242
|
+
"licensed-for-sharing"
|
|
1243
|
+
];
|
|
1244
|
+
var PROVENANCES = [
|
|
1245
|
+
"supplied",
|
|
1246
|
+
"generated",
|
|
1247
|
+
"edited",
|
|
1248
|
+
"extracted"
|
|
1249
|
+
];
|
|
1250
|
+
function adopt(target, options) {
|
|
1251
|
+
for (const [key, value] of Object.entries(options)) if (Object.hasOwn(target, key)) target[key] = value;
|
|
1252
|
+
}
|
|
1253
|
+
function requireText(value, message, max = 200) {
|
|
1254
|
+
if (typeof value !== "string" || !value.trim() || value.length > max) throw new Error(message);
|
|
1255
|
+
}
|
|
1256
|
+
function requireJson(value, shape, what) {
|
|
1257
|
+
let parsed;
|
|
1258
|
+
try {
|
|
1259
|
+
parsed = JSON.parse(value);
|
|
1260
|
+
} catch {
|
|
1261
|
+
throw new Error(`${what} must be JSON`);
|
|
1262
|
+
}
|
|
1263
|
+
if (shape === "array" ? !Array.isArray(parsed) : !parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${what} must be a JSON ${shape}`);
|
|
1264
|
+
}
|
|
1265
|
+
async function requireInsert(row) {
|
|
1266
|
+
if (!row.tenantId?.trim()) throw new Error("Course authoring requires a tenant");
|
|
1267
|
+
if (row.id && await row.db.get(row.tableName, { id: row.id })) throw new Error("Course authoring history is immutable");
|
|
1268
|
+
}
|
|
1269
|
+
async function requireOwned(row, table, id, extra = {}) {
|
|
1270
|
+
if (!await row.db.get(table, {
|
|
1271
|
+
id,
|
|
1272
|
+
tenant_id: row.tenantId,
|
|
1273
|
+
...extra
|
|
1274
|
+
})) throw new Error("Course authoring parent ownership mismatch");
|
|
1275
|
+
}
|
|
1276
|
+
function noDelete$1() {
|
|
1277
|
+
throw new Error("Authored course history is immutable; erasure requires an owning retention operation");
|
|
1278
|
+
}
|
|
1279
|
+
function validDate(value) {
|
|
1280
|
+
if (!(value instanceof Date) || !Number.isFinite(value.getTime())) throw new Error("Invalid course authoring timestamp");
|
|
1281
|
+
}
|
|
1282
|
+
var CourseRevision = class extends SmrtObject {
|
|
1283
|
+
tenantId = "";
|
|
1284
|
+
courseId = "";
|
|
1285
|
+
parentRevisionId = null;
|
|
1286
|
+
sequence = 1;
|
|
1287
|
+
authorUserId = "";
|
|
1288
|
+
delegatingGuardianUserId = "";
|
|
1289
|
+
delegationId = "";
|
|
1290
|
+
provenance = "supplied";
|
|
1291
|
+
fieldProvenanceJson = "{}";
|
|
1292
|
+
title = "";
|
|
1293
|
+
summary = "";
|
|
1294
|
+
subject = "";
|
|
1295
|
+
theme = "";
|
|
1296
|
+
levelMin = 0;
|
|
1297
|
+
levelMax = 0;
|
|
1298
|
+
objectivesJson = "[]";
|
|
1299
|
+
sectionsJson = "[]";
|
|
1300
|
+
licenseKind = "own-work";
|
|
1301
|
+
requestKey = "";
|
|
1302
|
+
payloadDigest = "";
|
|
1303
|
+
contentDigest = "";
|
|
1304
|
+
recordedAt = /* @__PURE__ */ new Date();
|
|
1305
|
+
constructor(options = {}) {
|
|
1306
|
+
super(options);
|
|
1307
|
+
adopt(this, options);
|
|
1308
|
+
}
|
|
1309
|
+
async delete() {
|
|
1310
|
+
noDelete$1();
|
|
1311
|
+
}
|
|
1312
|
+
async save(options) {
|
|
1313
|
+
await requireInsert(this);
|
|
1314
|
+
await requireOwned(this, "courses", this.courseId);
|
|
1315
|
+
if (this.parentRevisionId) await requireOwned(this, "course_revisions", this.parentRevisionId, { course_id: this.courseId });
|
|
1316
|
+
requireText(this.authorUserId, "Revision requires an author");
|
|
1317
|
+
requireText(this.title, "Revision requires a title");
|
|
1318
|
+
requireText(this.requestKey, "Revision requires a request key");
|
|
1319
|
+
if (!PROVENANCES.includes(this.provenance) || !LICENSES.includes(this.licenseKind) || !Number.isSafeInteger(this.sequence) || this.sequence < 1 || !Number.isSafeInteger(this.levelMin) || !Number.isSafeInteger(this.levelMax) || this.levelMin < 0 || this.levelMax < this.levelMin) throw new Error("Invalid course revision");
|
|
1320
|
+
requireJson(this.fieldProvenanceJson, "object", "Field provenance");
|
|
1321
|
+
requireJson(this.objectivesJson, "array", "Objectives");
|
|
1322
|
+
requireJson(this.sectionsJson, "array", "Sections");
|
|
1323
|
+
validDate(this.recordedAt);
|
|
1324
|
+
this.requireInsertOnSave();
|
|
1325
|
+
return super.save(options);
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
__publicField$4(CourseRevision, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1329
|
+
__decorateClass$4([tenantId()], CourseRevision.prototype, "tenantId", 2);
|
|
1330
|
+
__decorateClass$4([foreignKey("Course", { required: true })], CourseRevision.prototype, "courseId", 2);
|
|
1331
|
+
__decorateClass$4([foreignKey("CourseRevision", { nullable: true })], CourseRevision.prototype, "parentRevisionId", 2);
|
|
1332
|
+
__decorateClass$4([field({ type: "text" })], CourseRevision.prototype, "fieldProvenanceJson", 2);
|
|
1333
|
+
__decorateClass$4([field({ type: "text" })], CourseRevision.prototype, "summary", 2);
|
|
1334
|
+
__decorateClass$4([field({ type: "text" })], CourseRevision.prototype, "objectivesJson", 2);
|
|
1335
|
+
__decorateClass$4([field({ type: "text" })], CourseRevision.prototype, "sectionsJson", 2);
|
|
1336
|
+
CourseRevision = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1337
|
+
tableName: "course_revisions",
|
|
1338
|
+
conflictColumns: [
|
|
1339
|
+
"tenant_id",
|
|
1340
|
+
"course_id",
|
|
1341
|
+
"request_key"
|
|
1342
|
+
],
|
|
1343
|
+
...closed$3
|
|
1344
|
+
})], CourseRevision);
|
|
1345
|
+
var CourseRevisionCollection = class extends SmrtCollection {
|
|
1346
|
+
static _itemClass = CourseRevision;
|
|
1347
|
+
};
|
|
1348
|
+
var LessonRevision = class extends SmrtObject {
|
|
1349
|
+
tenantId = "";
|
|
1350
|
+
courseId = "";
|
|
1351
|
+
lessonKey = "";
|
|
1352
|
+
parentRevisionId = null;
|
|
1353
|
+
title = "";
|
|
1354
|
+
body = "";
|
|
1355
|
+
lessonType = "reading";
|
|
1356
|
+
completionRule = "attest";
|
|
1357
|
+
estimatedMinutes = 0;
|
|
1358
|
+
difficulty = 0;
|
|
1359
|
+
modality = "online";
|
|
1360
|
+
externalRef = "";
|
|
1361
|
+
externalUrl = "";
|
|
1362
|
+
licenseKind = "own-work";
|
|
1363
|
+
/** The lesson's own licence before materials; sticky across revisions of a lessonKey. */
|
|
1364
|
+
ownLicenseKind = "own-work";
|
|
1365
|
+
materialsJson = "[]";
|
|
1366
|
+
questionsJson = "[]";
|
|
1367
|
+
layoutParamsJson = "{}";
|
|
1368
|
+
provenance = "supplied";
|
|
1369
|
+
fieldProvenanceJson = "{}";
|
|
1370
|
+
authorUserId = "";
|
|
1371
|
+
delegatingGuardianUserId = "";
|
|
1372
|
+
delegationId = "";
|
|
1373
|
+
contentDigest = "";
|
|
1374
|
+
recordedAt = /* @__PURE__ */ new Date();
|
|
1375
|
+
constructor(options = {}) {
|
|
1376
|
+
super(options);
|
|
1377
|
+
adopt(this, options);
|
|
1378
|
+
}
|
|
1379
|
+
async delete() {
|
|
1380
|
+
noDelete$1();
|
|
1381
|
+
}
|
|
1382
|
+
async save(options) {
|
|
1383
|
+
await requireInsert(this);
|
|
1384
|
+
await requireOwned(this, "courses", this.courseId);
|
|
1385
|
+
if (this.parentRevisionId) await requireOwned(this, "lesson_revisions", this.parentRevisionId, {
|
|
1386
|
+
course_id: this.courseId,
|
|
1387
|
+
lesson_key: this.lessonKey
|
|
1388
|
+
});
|
|
1389
|
+
requireText(this.lessonKey, "Lesson revision requires a lesson key");
|
|
1390
|
+
requireText(this.title, "Lesson revision requires a title");
|
|
1391
|
+
requireText(this.authorUserId, "Lesson revision requires an author");
|
|
1392
|
+
if (![
|
|
1393
|
+
"reading",
|
|
1394
|
+
"video",
|
|
1395
|
+
"worksheet",
|
|
1396
|
+
"exercise"
|
|
1397
|
+
].includes(this.lessonType) || ![
|
|
1398
|
+
"attest",
|
|
1399
|
+
"submit",
|
|
1400
|
+
"score",
|
|
1401
|
+
"external"
|
|
1402
|
+
].includes(this.completionRule) || !PROVENANCES.includes(this.provenance) || !LICENSES.includes(this.licenseKind) || !Number.isSafeInteger(this.estimatedMinutes) || this.estimatedMinutes < 0 || !Number.isFinite(this.difficulty)) throw new Error("Invalid lesson revision");
|
|
1403
|
+
requireJson(this.materialsJson, "array", "Materials");
|
|
1404
|
+
requireJson(this.questionsJson, "array", "Lesson questions");
|
|
1405
|
+
requireJson(this.layoutParamsJson, "object", "Layout params");
|
|
1406
|
+
for (const ref of JSON.parse(this.questionsJson)) {
|
|
1407
|
+
if (typeof ref?.questionRevisionId !== "string") throw new Error("Lesson question reference is invalid");
|
|
1408
|
+
await requireOwned(this, "question_revisions", ref.questionRevisionId);
|
|
1409
|
+
}
|
|
1410
|
+
requireJson(this.fieldProvenanceJson, "object", "Field provenance");
|
|
1411
|
+
validDate(this.recordedAt);
|
|
1412
|
+
this.requireInsertOnSave();
|
|
1413
|
+
return super.save(options);
|
|
1414
|
+
}
|
|
1415
|
+
};
|
|
1416
|
+
__publicField$4(LessonRevision, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1417
|
+
__decorateClass$4([tenantId()], LessonRevision.prototype, "tenantId", 2);
|
|
1418
|
+
__decorateClass$4([foreignKey("Course", { required: true })], LessonRevision.prototype, "courseId", 2);
|
|
1419
|
+
__decorateClass$4([foreignKey("LessonRevision", { nullable: true })], LessonRevision.prototype, "parentRevisionId", 2);
|
|
1420
|
+
__decorateClass$4([field({ type: "text" })], LessonRevision.prototype, "body", 2);
|
|
1421
|
+
__decorateClass$4([field({ type: "text" })], LessonRevision.prototype, "materialsJson", 2);
|
|
1422
|
+
__decorateClass$4([field({ type: "text" })], LessonRevision.prototype, "questionsJson", 2);
|
|
1423
|
+
__decorateClass$4([field({ type: "text" })], LessonRevision.prototype, "layoutParamsJson", 2);
|
|
1424
|
+
__decorateClass$4([field({ type: "text" })], LessonRevision.prototype, "fieldProvenanceJson", 2);
|
|
1425
|
+
LessonRevision = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1426
|
+
tableName: "lesson_revisions",
|
|
1427
|
+
conflictColumns: [
|
|
1428
|
+
"tenant_id",
|
|
1429
|
+
"course_id",
|
|
1430
|
+
"lesson_key",
|
|
1431
|
+
"content_digest"
|
|
1432
|
+
],
|
|
1433
|
+
...closed$3
|
|
1434
|
+
})], LessonRevision);
|
|
1435
|
+
var LessonRevisionCollection = class extends SmrtCollection {
|
|
1436
|
+
static _itemClass = LessonRevision;
|
|
1437
|
+
};
|
|
1438
|
+
var CourseDraftHead = class extends SmrtObject {
|
|
1439
|
+
tenantId = "";
|
|
1440
|
+
courseId = "";
|
|
1441
|
+
headRevisionId = "";
|
|
1442
|
+
version = 1;
|
|
1443
|
+
updatedByUserId = "";
|
|
1444
|
+
updatedAt = /* @__PURE__ */ new Date();
|
|
1445
|
+
constructor(options = {}) {
|
|
1446
|
+
super(options);
|
|
1447
|
+
adopt(this, options);
|
|
1448
|
+
}
|
|
1449
|
+
async delete() {
|
|
1450
|
+
noDelete$1();
|
|
1451
|
+
}
|
|
1452
|
+
async save(options) {
|
|
1453
|
+
if (!this.tenantId?.trim()) throw new Error("Course head requires a tenant");
|
|
1454
|
+
await requireOwned(this, "courses", this.courseId);
|
|
1455
|
+
await requireOwned(this, "course_revisions", this.headRevisionId, { course_id: this.courseId });
|
|
1456
|
+
if (this.id && await this.db.get(this.tableName, { id: this.id }) && !await this.db.get(this.tableName, {
|
|
1457
|
+
id: this.id,
|
|
1458
|
+
tenant_id: this.tenantId,
|
|
1459
|
+
course_id: this.courseId
|
|
1460
|
+
})) throw new Error("Course head identity cannot change");
|
|
1461
|
+
if (!Number.isSafeInteger(this.version) || this.version < 1) throw new Error("Invalid course head version");
|
|
1462
|
+
requireText(this.updatedByUserId, "Course head requires an actor");
|
|
1463
|
+
return super.save(options);
|
|
1464
|
+
}
|
|
1465
|
+
};
|
|
1466
|
+
__publicField$4(CourseDraftHead, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1467
|
+
__decorateClass$4([tenantId()], CourseDraftHead.prototype, "tenantId", 2);
|
|
1468
|
+
__decorateClass$4([foreignKey("Course", { required: true })], CourseDraftHead.prototype, "courseId", 2);
|
|
1469
|
+
__decorateClass$4([foreignKey("CourseRevision", { required: true })], CourseDraftHead.prototype, "headRevisionId", 2);
|
|
1470
|
+
CourseDraftHead = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1471
|
+
tableName: "course_draft_heads",
|
|
1472
|
+
conflictColumns: ["tenant_id", "course_id"],
|
|
1473
|
+
...closed$3
|
|
1474
|
+
})], CourseDraftHead);
|
|
1475
|
+
var CourseDraftHeadCollection = class extends SmrtCollection {
|
|
1476
|
+
static _itemClass = CourseDraftHead;
|
|
1477
|
+
};
|
|
1478
|
+
var CourseRelease = class extends SmrtObject {
|
|
1479
|
+
tenantId = "";
|
|
1480
|
+
courseId = "";
|
|
1481
|
+
revisionId = "";
|
|
1482
|
+
releaseCourseId = "";
|
|
1483
|
+
releaseNumber = 1;
|
|
1484
|
+
reviewId = "";
|
|
1485
|
+
actorUserId = "";
|
|
1486
|
+
delegatingGuardianUserId = "";
|
|
1487
|
+
delegationId = "";
|
|
1488
|
+
requestKey = "";
|
|
1489
|
+
recordedAt = /* @__PURE__ */ new Date();
|
|
1490
|
+
constructor(options = {}) {
|
|
1491
|
+
super(options);
|
|
1492
|
+
adopt(this, options);
|
|
1493
|
+
}
|
|
1494
|
+
async delete() {
|
|
1495
|
+
noDelete$1();
|
|
1496
|
+
}
|
|
1497
|
+
async save(options) {
|
|
1498
|
+
await requireInsert(this);
|
|
1499
|
+
await requireOwned(this, "courses", this.courseId);
|
|
1500
|
+
await requireOwned(this, "courses", this.releaseCourseId);
|
|
1501
|
+
await requireOwned(this, "course_revisions", this.revisionId, { course_id: this.courseId });
|
|
1502
|
+
requireText(this.actorUserId, "Release requires an actor");
|
|
1503
|
+
requireText(this.requestKey, "Release requires a request key");
|
|
1504
|
+
if (!Number.isSafeInteger(this.releaseNumber) || this.releaseNumber < 1) throw new Error("Invalid release number");
|
|
1505
|
+
validDate(this.recordedAt);
|
|
1506
|
+
this.requireInsertOnSave();
|
|
1507
|
+
return super.save(options);
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
__publicField$4(CourseRelease, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1511
|
+
__decorateClass$4([tenantId()], CourseRelease.prototype, "tenantId", 2);
|
|
1512
|
+
__decorateClass$4([foreignKey("Course", { required: true })], CourseRelease.prototype, "courseId", 2);
|
|
1513
|
+
__decorateClass$4([foreignKey("CourseRevision", { required: true })], CourseRelease.prototype, "revisionId", 2);
|
|
1514
|
+
__decorateClass$4([foreignKey("Course", { required: true })], CourseRelease.prototype, "releaseCourseId", 2);
|
|
1515
|
+
CourseRelease = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1516
|
+
tableName: "course_releases",
|
|
1517
|
+
conflictColumns: [
|
|
1518
|
+
"tenant_id",
|
|
1519
|
+
"course_id",
|
|
1520
|
+
"request_key"
|
|
1521
|
+
],
|
|
1522
|
+
...closed$3
|
|
1523
|
+
})], CourseRelease);
|
|
1524
|
+
var CourseReleaseCollection = class extends SmrtCollection {
|
|
1525
|
+
static _itemClass = CourseRelease;
|
|
1526
|
+
};
|
|
1527
|
+
var ReleasedLesson = class extends SmrtObject {
|
|
1528
|
+
tenantId = "";
|
|
1529
|
+
courseId = "";
|
|
1530
|
+
releaseId = "";
|
|
1531
|
+
lessonKey = "";
|
|
1532
|
+
lessonRevisionId = "";
|
|
1533
|
+
lessonId = "";
|
|
1534
|
+
sectionId = "";
|
|
1535
|
+
constructor(options = {}) {
|
|
1536
|
+
super(options);
|
|
1537
|
+
adopt(this, options);
|
|
1538
|
+
}
|
|
1539
|
+
async delete() {
|
|
1540
|
+
noDelete$1();
|
|
1541
|
+
}
|
|
1542
|
+
async save(options) {
|
|
1543
|
+
await requireInsert(this);
|
|
1544
|
+
await requireOwned(this, "course_releases", this.releaseId, { course_id: this.courseId });
|
|
1545
|
+
await requireOwned(this, "lesson_revisions", this.lessonRevisionId, {
|
|
1546
|
+
course_id: this.courseId,
|
|
1547
|
+
lesson_key: this.lessonKey
|
|
1548
|
+
});
|
|
1549
|
+
await requireOwned(this, "lessons", this.lessonId);
|
|
1550
|
+
requireText(this.lessonKey, "Released lesson requires a lesson key");
|
|
1551
|
+
this.requireInsertOnSave();
|
|
1552
|
+
return super.save(options);
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
__publicField$4(ReleasedLesson, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1556
|
+
__decorateClass$4([tenantId()], ReleasedLesson.prototype, "tenantId", 2);
|
|
1557
|
+
__decorateClass$4([foreignKey("Course", { required: true })], ReleasedLesson.prototype, "courseId", 2);
|
|
1558
|
+
__decorateClass$4([foreignKey("CourseRelease", { required: true })], ReleasedLesson.prototype, "releaseId", 2);
|
|
1559
|
+
__decorateClass$4([foreignKey("LessonRevision", { required: true })], ReleasedLesson.prototype, "lessonRevisionId", 2);
|
|
1560
|
+
__decorateClass$4([foreignKey("Lesson", { required: true })], ReleasedLesson.prototype, "lessonId", 2);
|
|
1561
|
+
__decorateClass$4([foreignKey("CourseSection", { required: true })], ReleasedLesson.prototype, "sectionId", 2);
|
|
1562
|
+
ReleasedLesson = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1563
|
+
tableName: "released_lessons",
|
|
1564
|
+
conflictColumns: [
|
|
1565
|
+
"tenant_id",
|
|
1566
|
+
"release_id",
|
|
1567
|
+
"lesson_key"
|
|
1568
|
+
],
|
|
1569
|
+
...closed$3
|
|
1570
|
+
})], ReleasedLesson);
|
|
1571
|
+
var ReleasedLessonCollection = class extends SmrtCollection {
|
|
1572
|
+
static _itemClass = ReleasedLesson;
|
|
1573
|
+
};
|
|
1574
|
+
var CourseArchiveDecision = class extends SmrtObject {
|
|
1575
|
+
tenantId = "";
|
|
1576
|
+
courseId = "";
|
|
1577
|
+
action = "archive";
|
|
1578
|
+
sequence = 1;
|
|
1579
|
+
actorUserId = "";
|
|
1580
|
+
delegatingGuardianUserId = "";
|
|
1581
|
+
delegationId = "";
|
|
1582
|
+
requestKey = "";
|
|
1583
|
+
recordedAt = /* @__PURE__ */ new Date();
|
|
1584
|
+
constructor(options = {}) {
|
|
1585
|
+
super(options);
|
|
1586
|
+
adopt(this, options);
|
|
1587
|
+
}
|
|
1588
|
+
async delete() {
|
|
1589
|
+
noDelete$1();
|
|
1590
|
+
}
|
|
1591
|
+
async save(options) {
|
|
1592
|
+
await requireInsert(this);
|
|
1593
|
+
await requireOwned(this, "courses", this.courseId);
|
|
1594
|
+
requireText(this.actorUserId, "Archive decision requires an actor");
|
|
1595
|
+
requireText(this.requestKey, "Archive decision requires a request key");
|
|
1596
|
+
if (!["archive", "unarchive"].includes(this.action) || !Number.isSafeInteger(this.sequence) || this.sequence < 1) throw new Error("Invalid archive decision");
|
|
1597
|
+
validDate(this.recordedAt);
|
|
1598
|
+
this.requireInsertOnSave();
|
|
1599
|
+
return super.save(options);
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
__publicField$4(CourseArchiveDecision, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1603
|
+
__decorateClass$4([tenantId()], CourseArchiveDecision.prototype, "tenantId", 2);
|
|
1604
|
+
__decorateClass$4([foreignKey("Course", { required: true })], CourseArchiveDecision.prototype, "courseId", 2);
|
|
1605
|
+
CourseArchiveDecision = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1606
|
+
tableName: "course_archive_decisions",
|
|
1607
|
+
conflictColumns: [
|
|
1608
|
+
"tenant_id",
|
|
1609
|
+
"course_id",
|
|
1610
|
+
"request_key"
|
|
1611
|
+
],
|
|
1612
|
+
...closed$3
|
|
1613
|
+
})], CourseArchiveDecision);
|
|
1614
|
+
var CourseArchiveDecisionCollection = class extends SmrtCollection {
|
|
1615
|
+
static _itemClass = CourseArchiveDecision;
|
|
1616
|
+
};
|
|
1617
|
+
var CourseDiscardTombstone = class extends SmrtObject {
|
|
1618
|
+
tenantId = "";
|
|
1619
|
+
courseId = "";
|
|
1620
|
+
title = "";
|
|
1621
|
+
headRevisionId = "";
|
|
1622
|
+
revisionCount = 0;
|
|
1623
|
+
reason = "";
|
|
1624
|
+
actorUserId = "";
|
|
1625
|
+
delegatingGuardianUserId = "";
|
|
1626
|
+
delegationId = "";
|
|
1627
|
+
requestKey = "";
|
|
1628
|
+
recordedAt = /* @__PURE__ */ new Date();
|
|
1629
|
+
constructor(options = {}) {
|
|
1630
|
+
super(options);
|
|
1631
|
+
adopt(this, options);
|
|
1632
|
+
}
|
|
1633
|
+
async delete() {
|
|
1634
|
+
noDelete$1();
|
|
1635
|
+
}
|
|
1636
|
+
async save(options) {
|
|
1637
|
+
await requireInsert(this);
|
|
1638
|
+
await requireOwned(this, "courses", this.courseId);
|
|
1639
|
+
requireText(this.actorUserId, "Discard requires an actor");
|
|
1640
|
+
requireText(this.requestKey, "Discard requires a request key");
|
|
1641
|
+
validDate(this.recordedAt);
|
|
1642
|
+
this.requireInsertOnSave();
|
|
1643
|
+
return super.save(options);
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
__publicField$4(CourseDiscardTombstone, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1647
|
+
__decorateClass$4([tenantId()], CourseDiscardTombstone.prototype, "tenantId", 2);
|
|
1648
|
+
__decorateClass$4([foreignKey("Course", { required: true })], CourseDiscardTombstone.prototype, "courseId", 2);
|
|
1649
|
+
CourseDiscardTombstone = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1650
|
+
tableName: "course_discard_tombstones",
|
|
1651
|
+
conflictColumns: ["tenant_id", "course_id"],
|
|
1652
|
+
...closed$3
|
|
1653
|
+
})], CourseDiscardTombstone);
|
|
1654
|
+
var CourseDiscardTombstoneCollection = class extends SmrtCollection {
|
|
1655
|
+
static _itemClass = CourseDiscardTombstone;
|
|
1656
|
+
};
|
|
1657
|
+
var EnrollmentReleasePin = class extends SmrtObject {
|
|
1658
|
+
tenantId = "";
|
|
1659
|
+
enrollmentId = "";
|
|
1660
|
+
learnerId = "";
|
|
1661
|
+
courseId = "";
|
|
1662
|
+
releaseId = "";
|
|
1663
|
+
previousReleaseId = null;
|
|
1664
|
+
kind = "initial";
|
|
1665
|
+
sequence = 1;
|
|
1666
|
+
/** Empty for automatic moves, which have no actor. */
|
|
1667
|
+
actorUserId = "";
|
|
1668
|
+
delegatingGuardianUserId = "";
|
|
1669
|
+
delegationId = "";
|
|
1670
|
+
/** Automatic moves: the delegation id recorded on the opt-in followed; empty otherwise. */
|
|
1671
|
+
optInDelegationId = "";
|
|
1672
|
+
/** Host assignment-setting value relied on; automatic moves copy the opt-in's. */
|
|
1673
|
+
policyBasis = "";
|
|
1674
|
+
/** Scope of the opt-in an automatic move followed ('course' | 'enrollment'); empty otherwise. */
|
|
1675
|
+
optInScope = "";
|
|
1676
|
+
requestKey = "";
|
|
1677
|
+
recordedAt = /* @__PURE__ */ new Date();
|
|
1678
|
+
constructor(options = {}) {
|
|
1679
|
+
super(options);
|
|
1680
|
+
adopt(this, options);
|
|
1681
|
+
}
|
|
1682
|
+
async delete() {
|
|
1683
|
+
noDelete$1();
|
|
1684
|
+
}
|
|
1685
|
+
async save(options) {
|
|
1686
|
+
await requireInsert(this);
|
|
1687
|
+
await requireOwned(this, "enrollments", this.enrollmentId, { learner_id: this.learnerId });
|
|
1688
|
+
await requireOwned(this, "course_releases", this.releaseId, { course_id: this.courseId });
|
|
1689
|
+
if (this.previousReleaseId) await requireOwned(this, "course_releases", this.previousReleaseId, { course_id: this.courseId });
|
|
1690
|
+
requireText(this.requestKey, "Enrollment pin requires a request key", 300);
|
|
1691
|
+
requireText(this.policyBasis, "Enrollment pin requires a policy basis");
|
|
1692
|
+
if (![
|
|
1693
|
+
"initial",
|
|
1694
|
+
"explicit",
|
|
1695
|
+
"auto"
|
|
1696
|
+
].includes(this.kind) || this.kind !== "auto" && !this.actorUserId || !Number.isSafeInteger(this.sequence) || this.sequence < 1) throw new Error("Invalid enrollment pin");
|
|
1697
|
+
if (this.kind === "auto" ? !["course", "enrollment"].includes(this.optInScope) : this.optInScope !== "") throw new Error("Invalid enrollment pin opt-in scope");
|
|
1698
|
+
validDate(this.recordedAt);
|
|
1699
|
+
this.requireInsertOnSave();
|
|
1700
|
+
return super.save(options);
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
1703
|
+
__publicField$4(EnrollmentReleasePin, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1704
|
+
__decorateClass$4([tenantId()], EnrollmentReleasePin.prototype, "tenantId", 2);
|
|
1705
|
+
__decorateClass$4([foreignKey("Enrollment", { required: true })], EnrollmentReleasePin.prototype, "enrollmentId", 2);
|
|
1706
|
+
__decorateClass$4([foreignKey("Learner", { required: true })], EnrollmentReleasePin.prototype, "learnerId", 2);
|
|
1707
|
+
__decorateClass$4([foreignKey("Course", { required: true })], EnrollmentReleasePin.prototype, "courseId", 2);
|
|
1708
|
+
__decorateClass$4([foreignKey("CourseRelease", { required: true })], EnrollmentReleasePin.prototype, "releaseId", 2);
|
|
1709
|
+
__decorateClass$4([foreignKey("CourseRelease", { nullable: true })], EnrollmentReleasePin.prototype, "previousReleaseId", 2);
|
|
1710
|
+
EnrollmentReleasePin = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1711
|
+
tableName: "enrollment_release_pins",
|
|
1712
|
+
conflictColumns: [
|
|
1713
|
+
"tenant_id",
|
|
1714
|
+
"enrollment_id",
|
|
1715
|
+
"request_key"
|
|
1716
|
+
],
|
|
1717
|
+
...closed$3
|
|
1718
|
+
})], EnrollmentReleasePin);
|
|
1719
|
+
var EnrollmentReleasePinCollection = class extends SmrtCollection {
|
|
1720
|
+
static _itemClass = EnrollmentReleasePin;
|
|
1721
|
+
};
|
|
1722
|
+
var CourseUpgradeChoice = class extends SmrtObject {
|
|
1723
|
+
tenantId = "";
|
|
1724
|
+
courseId = "";
|
|
1725
|
+
/** Empty for a course-level choice. */
|
|
1726
|
+
enrollmentId = "";
|
|
1727
|
+
enabled = false;
|
|
1728
|
+
sequence = 1;
|
|
1729
|
+
actorUserId = "";
|
|
1730
|
+
delegatingGuardianUserId = "";
|
|
1731
|
+
delegationId = "";
|
|
1732
|
+
/** Host assignment-setting value relied on for this choice. */
|
|
1733
|
+
policyBasis = "";
|
|
1734
|
+
requestKey = "";
|
|
1735
|
+
recordedAt = /* @__PURE__ */ new Date();
|
|
1736
|
+
constructor(options = {}) {
|
|
1737
|
+
super(options);
|
|
1738
|
+
adopt(this, options);
|
|
1739
|
+
}
|
|
1740
|
+
async delete() {
|
|
1741
|
+
noDelete$1();
|
|
1742
|
+
}
|
|
1743
|
+
async save(options) {
|
|
1744
|
+
await requireInsert(this);
|
|
1745
|
+
await requireOwned(this, "courses", this.courseId);
|
|
1746
|
+
if (this.enrollmentId) await requireOwned(this, "enrollments", this.enrollmentId);
|
|
1747
|
+
requireText(this.actorUserId, "Upgrade choice requires an actor");
|
|
1748
|
+
requireText(this.requestKey, "Upgrade choice requires a request key");
|
|
1749
|
+
requireText(this.policyBasis, "Upgrade choice requires a policy basis");
|
|
1750
|
+
if (typeof this.enabled !== "boolean" || !Number.isSafeInteger(this.sequence) || this.sequence < 1) throw new Error("Invalid upgrade choice");
|
|
1751
|
+
validDate(this.recordedAt);
|
|
1752
|
+
this.requireInsertOnSave();
|
|
1753
|
+
return super.save(options);
|
|
1754
|
+
}
|
|
1755
|
+
};
|
|
1756
|
+
__publicField$4(CourseUpgradeChoice, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1757
|
+
__decorateClass$4([tenantId()], CourseUpgradeChoice.prototype, "tenantId", 2);
|
|
1758
|
+
__decorateClass$4([foreignKey("Course", { required: true })], CourseUpgradeChoice.prototype, "courseId", 2);
|
|
1759
|
+
CourseUpgradeChoice = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1760
|
+
tableName: "course_upgrade_choices",
|
|
1761
|
+
conflictColumns: [
|
|
1762
|
+
"tenant_id",
|
|
1763
|
+
"course_id",
|
|
1764
|
+
"request_key"
|
|
1765
|
+
],
|
|
1766
|
+
...closed$3
|
|
1767
|
+
})], CourseUpgradeChoice);
|
|
1768
|
+
var CourseUpgradeChoiceCollection = class extends SmrtCollection {
|
|
1769
|
+
static _itemClass = CourseUpgradeChoice;
|
|
1770
|
+
};
|
|
1771
|
+
var EnrollmentAutoUpgradeSkip = class extends SmrtObject {
|
|
1772
|
+
tenantId = "";
|
|
1773
|
+
enrollmentId = "";
|
|
1774
|
+
learnerId = "";
|
|
1775
|
+
courseId = "";
|
|
1776
|
+
releaseId = "";
|
|
1777
|
+
/** 'course' or 'enrollment': the scope of the opt-in that was followed. */
|
|
1778
|
+
optInScope = "";
|
|
1779
|
+
optInActorUserId = "";
|
|
1780
|
+
optInDelegatingGuardianUserId = "";
|
|
1781
|
+
optInDelegationId = "";
|
|
1782
|
+
policyBasis = "";
|
|
1783
|
+
/** The authorizer's bounded reason, else 'not-authorized'. Never a thrown message. */
|
|
1784
|
+
reason = "not-authorized";
|
|
1785
|
+
/** The authorizer's short code (`[a-z0-9._-]`, up to 64); empty when none. */
|
|
1786
|
+
code = "";
|
|
1787
|
+
recordedAt = /* @__PURE__ */ new Date();
|
|
1788
|
+
constructor(options = {}) {
|
|
1789
|
+
super(options);
|
|
1790
|
+
adopt(this, options);
|
|
1791
|
+
}
|
|
1792
|
+
async delete() {
|
|
1793
|
+
noDelete$1();
|
|
1794
|
+
}
|
|
1795
|
+
async save(options) {
|
|
1796
|
+
await requireInsert(this);
|
|
1797
|
+
await requireOwned(this, "enrollments", this.enrollmentId, { learner_id: this.learnerId });
|
|
1798
|
+
await requireOwned(this, "course_releases", this.releaseId, { course_id: this.courseId });
|
|
1799
|
+
requireText(this.optInActorUserId, "Auto-upgrade skip requires the opt-in actor");
|
|
1800
|
+
requireText(this.policyBasis, "Auto-upgrade skip requires a policy basis");
|
|
1801
|
+
requireText(this.reason, "Auto-upgrade skip requires a reason", 200);
|
|
1802
|
+
if (!["course", "enrollment"].includes(this.optInScope) || /[\u0000-\u001f\u007f]/.test(this.reason) || !/^[a-z0-9._-]{0,64}$/.test(this.code)) throw new Error("Invalid auto-upgrade skip");
|
|
1803
|
+
validDate(this.recordedAt);
|
|
1804
|
+
this.requireInsertOnSave();
|
|
1805
|
+
return super.save(options);
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
__publicField$4(EnrollmentAutoUpgradeSkip, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1809
|
+
__decorateClass$4([tenantId()], EnrollmentAutoUpgradeSkip.prototype, "tenantId", 2);
|
|
1810
|
+
__decorateClass$4([foreignKey("Enrollment", { required: true })], EnrollmentAutoUpgradeSkip.prototype, "enrollmentId", 2);
|
|
1811
|
+
__decorateClass$4([foreignKey("Learner", { required: true })], EnrollmentAutoUpgradeSkip.prototype, "learnerId", 2);
|
|
1812
|
+
__decorateClass$4([foreignKey("Course", { required: true })], EnrollmentAutoUpgradeSkip.prototype, "courseId", 2);
|
|
1813
|
+
__decorateClass$4([foreignKey("CourseRelease", { required: true })], EnrollmentAutoUpgradeSkip.prototype, "releaseId", 2);
|
|
1814
|
+
EnrollmentAutoUpgradeSkip = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1815
|
+
tableName: "enrollment_auto_upgrade_skips",
|
|
1816
|
+
conflictColumns: [
|
|
1817
|
+
"tenant_id",
|
|
1818
|
+
"enrollment_id",
|
|
1819
|
+
"release_id"
|
|
1820
|
+
],
|
|
1821
|
+
...closed$3
|
|
1822
|
+
})], EnrollmentAutoUpgradeSkip);
|
|
1823
|
+
var EnrollmentAutoUpgradeSkipCollection = class extends SmrtCollection {
|
|
1824
|
+
static _itemClass = EnrollmentAutoUpgradeSkip;
|
|
920
1825
|
};
|
|
921
|
-
var
|
|
922
|
-
|
|
1826
|
+
var EnrollmentAssignmentRecord = class extends SmrtObject {
|
|
1827
|
+
tenantId = "";
|
|
1828
|
+
enrollmentId = "";
|
|
1829
|
+
learnerId = "";
|
|
1830
|
+
actorUserId = "";
|
|
1831
|
+
delegatingGuardianUserId = "";
|
|
1832
|
+
delegationId = "";
|
|
1833
|
+
recordedAt = /* @__PURE__ */ new Date();
|
|
1834
|
+
constructor(options = {}) {
|
|
1835
|
+
super(options);
|
|
1836
|
+
adopt(this, options);
|
|
1837
|
+
}
|
|
1838
|
+
async delete() {
|
|
1839
|
+
noDelete$1();
|
|
1840
|
+
}
|
|
1841
|
+
async save(options) {
|
|
1842
|
+
await requireInsert(this);
|
|
1843
|
+
await requireOwned(this, "enrollments", this.enrollmentId, { learner_id: this.learnerId });
|
|
1844
|
+
requireText(this.actorUserId, "Assignment record requires the agent");
|
|
1845
|
+
requireText(this.delegatingGuardianUserId, "Assignment record requires the delegating guardian", 200);
|
|
1846
|
+
requireText(this.delegationId, "Assignment record requires a delegation id", 200);
|
|
1847
|
+
validDate(this.recordedAt);
|
|
1848
|
+
this.requireInsertOnSave();
|
|
1849
|
+
return super.save(options);
|
|
1850
|
+
}
|
|
923
1851
|
};
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1852
|
+
__publicField$4(EnrollmentAssignmentRecord, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1853
|
+
__decorateClass$4([tenantId()], EnrollmentAssignmentRecord.prototype, "tenantId", 2);
|
|
1854
|
+
__decorateClass$4([foreignKey("Enrollment", { required: true })], EnrollmentAssignmentRecord.prototype, "enrollmentId", 2);
|
|
1855
|
+
__decorateClass$4([foreignKey("Learner", { required: true })], EnrollmentAssignmentRecord.prototype, "learnerId", 2);
|
|
1856
|
+
EnrollmentAssignmentRecord = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1857
|
+
tableName: "enrollment_assignment_records",
|
|
1858
|
+
conflictColumns: ["tenant_id", "enrollment_id"],
|
|
1859
|
+
...closed$3
|
|
1860
|
+
})], EnrollmentAssignmentRecord);
|
|
1861
|
+
var EnrollmentAssignmentRecordCollection = class extends SmrtCollection {
|
|
1862
|
+
static _itemClass = EnrollmentAssignmentRecord;
|
|
928
1863
|
};
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1864
|
+
var CurriculumCreationRecord = class extends SmrtObject {
|
|
1865
|
+
tenantId = "";
|
|
1866
|
+
curriculumId = "";
|
|
1867
|
+
learnerId = "";
|
|
1868
|
+
actorUserId = "";
|
|
1869
|
+
delegatingGuardianUserId = "";
|
|
1870
|
+
delegationId = "";
|
|
1871
|
+
recordedAt = /* @__PURE__ */ new Date();
|
|
1872
|
+
constructor(options = {}) {
|
|
1873
|
+
super(options);
|
|
1874
|
+
adopt(this, options);
|
|
935
1875
|
}
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
if (includeAnswerKeys || !("answerUrl" in lesson)) return lesson;
|
|
939
|
-
return Object.assign(Object.create(Object.getPrototypeOf(lesson)), lesson, { answerUrl: "" });
|
|
940
|
-
}
|
|
941
|
-
var CatalogService = class {
|
|
942
|
-
constructor(courses, sections, lessons, assets, tenants, authorize) {
|
|
943
|
-
this.courses = courses;
|
|
944
|
-
this.sections = sections;
|
|
945
|
-
this.lessons = lessons;
|
|
946
|
-
this.assets = assets;
|
|
947
|
-
this.tenants = tenants;
|
|
948
|
-
this.authorize = authorize;
|
|
1876
|
+
async delete() {
|
|
1877
|
+
noDelete$1();
|
|
949
1878
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
this.sections,
|
|
960
|
-
this.lessons,
|
|
961
|
-
this.assets
|
|
962
|
-
].some((collection) => collection.db !== executor)) throw new CatalogAuthorizationError();
|
|
963
|
-
return executor;
|
|
1879
|
+
async save(options) {
|
|
1880
|
+
await requireInsert(this);
|
|
1881
|
+
await requireOwned(this, "curricula", this.curriculumId, { learner_id: this.learnerId });
|
|
1882
|
+
requireText(this.actorUserId, "Creation record requires the agent");
|
|
1883
|
+
requireText(this.delegatingGuardianUserId, "Creation record requires the delegating guardian", 200);
|
|
1884
|
+
requireText(this.delegationId, "Creation record requires a delegation id", 200);
|
|
1885
|
+
validDate(this.recordedAt);
|
|
1886
|
+
this.requireInsertOnSave();
|
|
1887
|
+
return super.save(options);
|
|
964
1888
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1889
|
+
};
|
|
1890
|
+
__publicField$4(CurriculumCreationRecord, "__smrtPackage__", "@happyvertical/smrt-education");
|
|
1891
|
+
__decorateClass$4([tenantId()], CurriculumCreationRecord.prototype, "tenantId", 2);
|
|
1892
|
+
__decorateClass$4([foreignKey("Curriculum", { required: true })], CurriculumCreationRecord.prototype, "curriculumId", 2);
|
|
1893
|
+
__decorateClass$4([foreignKey("Learner", { required: true })], CurriculumCreationRecord.prototype, "learnerId", 2);
|
|
1894
|
+
CurriculumCreationRecord = __decorateClass$4([TenantScoped({ mode: "required" }), smrt({
|
|
1895
|
+
tableName: "curriculum_creation_records",
|
|
1896
|
+
conflictColumns: ["tenant_id", "curriculum_id"],
|
|
1897
|
+
...closed$3
|
|
1898
|
+
})], CurriculumCreationRecord);
|
|
1899
|
+
var CurriculumCreationRecordCollection = class extends SmrtCollection {
|
|
1900
|
+
static _itemClass = CurriculumCreationRecord;
|
|
1901
|
+
};
|
|
1902
|
+
var present = (value) => value !== void 0 && value !== null && value !== "";
|
|
1903
|
+
function bounded(value, label) {
|
|
1904
|
+
if (typeof value !== "string" || value.length < 1 || value.length > 200 || /[\u0000-\u001f\u007f]/.test(value)) throw new CourseAuthoringValidationError(`Delegation ${label} must be 1-200 printable characters`);
|
|
1905
|
+
return value;
|
|
1906
|
+
}
|
|
1907
|
+
function readDelegation(claim) {
|
|
1908
|
+
const hasGuardian = present(claim.delegatingGuardianUserId);
|
|
1909
|
+
const hasId = present(claim.delegationId);
|
|
1910
|
+
if (!hasGuardian && !hasId) return null;
|
|
1911
|
+
if (hasGuardian !== hasId) throw new CourseAuthoringValidationError("A delegation id requires a delegating guardian, and the reverse");
|
|
1912
|
+
const delegatingGuardianUserId = bounded(claim.delegatingGuardianUserId, "guardian");
|
|
1913
|
+
const delegationId = bounded(claim.delegationId, "id");
|
|
1914
|
+
if (delegatingGuardianUserId === claim.actorUserId) throw new CourseAuthoringAuthorizationError();
|
|
1915
|
+
return {
|
|
1916
|
+
delegatingGuardianUserId,
|
|
1917
|
+
delegationId
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
function delegationActorFields(claim) {
|
|
1921
|
+
return readDelegation(claim) ?? {};
|
|
1922
|
+
}
|
|
1923
|
+
function delegationRecordFields(claim) {
|
|
1924
|
+
return readDelegation(claim) ?? {
|
|
1925
|
+
delegatingGuardianUserId: "",
|
|
1926
|
+
delegationId: ""
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
//#endregion
|
|
1930
|
+
//#region src/course-release-scope.ts
|
|
1931
|
+
async function resolveEnrollmentLessons(db, tenantId, enrollmentId) {
|
|
1932
|
+
const pins = (await (await EnrollmentReleasePinCollection.create({ db })).list({
|
|
1933
|
+
where: {
|
|
1934
|
+
tenantId,
|
|
1935
|
+
enrollmentId
|
|
1936
|
+
},
|
|
1937
|
+
limit: 1e5
|
|
1938
|
+
})).sort((a, b) => a.sequence - b.sequence);
|
|
1939
|
+
const current = pins.at(-1);
|
|
1940
|
+
if (!current) return null;
|
|
1941
|
+
const currentReleaseId = current.releaseId;
|
|
1942
|
+
const lineage = [...new Set(pins.map((p) => p.releaseId))];
|
|
1943
|
+
const releasedLessons = await ReleasedLessonCollection.create({ db });
|
|
1944
|
+
const keysByRelease = /* @__PURE__ */ new Map();
|
|
1945
|
+
for (const releaseId of lineage) {
|
|
1946
|
+
const rows = await releasedLessons.list({
|
|
1947
|
+
where: {
|
|
1948
|
+
tenantId,
|
|
1949
|
+
releaseId
|
|
1950
|
+
},
|
|
1951
|
+
limit: 1e5
|
|
969
1952
|
});
|
|
1953
|
+
keysByRelease.set(releaseId, new Map(rows.map((row) => [row.lessonKey, row.lessonId])));
|
|
970
1954
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
[{ "licenseKind !=": "personal-use-copy" }],
|
|
999
|
-
[{ licenseKind: null }],
|
|
1000
|
-
...includePrivate ? [[{
|
|
1001
|
-
licenseKind: "personal-use-copy",
|
|
1002
|
-
tenantId
|
|
1003
|
-
}]] : []
|
|
1004
|
-
]
|
|
1005
|
-
}));
|
|
1006
|
-
}
|
|
1007
|
-
/** Project one already-visible course without exposing answer-key assets by default. */
|
|
1008
|
-
async projectCourse(request) {
|
|
1009
|
-
const course = (await this.findVisible(request)).find((item) => item.id === request.courseId);
|
|
1010
|
-
if (!course?.id) return null;
|
|
1011
|
-
const includeAnswerKeys = request.includeAnswerKeys === true;
|
|
1012
|
-
const actor = {
|
|
1013
|
-
actorTenantId: request.tenantId,
|
|
1014
|
-
actorUserId: request.actorUserId,
|
|
1015
|
-
rootTenantId: request.tenantId
|
|
1016
|
-
};
|
|
1017
|
-
if (includeAnswerKeys && await this.authorizeInScope({
|
|
1018
|
-
...actor,
|
|
1019
|
-
action: "education.catalog.answer-key.read"
|
|
1020
|
-
}) !== true) throw new CatalogAuthorizationError();
|
|
1021
|
-
return this.tenants.withTenantCatalogReadScope({
|
|
1022
|
-
collections: [
|
|
1023
|
-
"@happyvertical/smrt-education:CourseSection",
|
|
1024
|
-
"@happyvertical/smrt-education:Lesson",
|
|
1025
|
-
"@happyvertical/smrt-education:LessonAsset"
|
|
1026
|
-
],
|
|
1027
|
-
authorize: async (scope) => scope.mode === "catalog" && scope.actorTenantId === request.tenantId && scope.actorUserId === request.actorUserId
|
|
1028
|
-
}, async () => {
|
|
1029
|
-
const sections = await this.sections.list({
|
|
1030
|
-
where: { courseId: course.id },
|
|
1031
|
-
orderBy: ["sortOrder ASC", "id ASC"]
|
|
1032
|
-
});
|
|
1033
|
-
const result = [];
|
|
1034
|
-
for (const section of sections) {
|
|
1035
|
-
if (section.tenantId !== course.tenantId) continue;
|
|
1036
|
-
const lessons = await this.lessons.list({
|
|
1037
|
-
where: { sectionId: section.id },
|
|
1038
|
-
orderBy: ["sortOrder ASC", "id ASC"]
|
|
1039
|
-
});
|
|
1040
|
-
result.push({
|
|
1041
|
-
section,
|
|
1042
|
-
lessons: await Promise.all(lessons.filter((lesson) => lesson.tenantId === course.tenantId).map(async (lesson) => ({
|
|
1043
|
-
lesson: projectLesson(lesson, includeAnswerKeys),
|
|
1044
|
-
assets: (await this.assets.byLeft(String(lesson.id))).filter((asset) => asset.tenantId === course.tenantId && (includeAnswerKeys || asset.relationship !== "answer-key"))
|
|
1045
|
-
})))
|
|
1046
|
-
});
|
|
1047
|
-
}
|
|
1048
|
-
return {
|
|
1049
|
-
course,
|
|
1050
|
-
sections: result
|
|
1051
|
-
};
|
|
1955
|
+
const progress = await (await LessonProgressCollection.create({ db })).list({
|
|
1956
|
+
where: {
|
|
1957
|
+
tenantId,
|
|
1958
|
+
enrollmentId
|
|
1959
|
+
},
|
|
1960
|
+
limit: 1e6
|
|
1961
|
+
});
|
|
1962
|
+
const startedIds = new Set(progress.filter((row) => row.status !== "not_started" || Number(row.attempts) > 0).map((row) => row.lessonId));
|
|
1963
|
+
const result = /* @__PURE__ */ new Map();
|
|
1964
|
+
for (const [lessonKey, lessonId] of keysByRelease.get(currentReleaseId) ?? []) result.set(lessonKey, {
|
|
1965
|
+
lessonKey,
|
|
1966
|
+
lessonId,
|
|
1967
|
+
releaseId: currentReleaseId,
|
|
1968
|
+
pinnedToOlderRelease: false
|
|
1969
|
+
});
|
|
1970
|
+
for (const releaseId of lineage.filter((id) => id !== currentReleaseId)) for (const [lessonKey, lessonId] of keysByRelease.get(releaseId) ?? []) if (startedIds.has(lessonId)) result.set(lessonKey, {
|
|
1971
|
+
lessonKey,
|
|
1972
|
+
lessonId,
|
|
1973
|
+
releaseId,
|
|
1974
|
+
pinnedToOlderRelease: true
|
|
1975
|
+
});
|
|
1976
|
+
const lessons = [...result.values()].sort((a, b) => a.lessonKey.localeCompare(b.lessonKey));
|
|
1977
|
+
const releaseCourseIds = /* @__PURE__ */ new Map();
|
|
1978
|
+
for (const releaseId of /* @__PURE__ */ new Set([currentReleaseId, ...lessons.map((l) => l.releaseId)])) {
|
|
1979
|
+
const release = await db.get("course_releases", {
|
|
1980
|
+
id: releaseId,
|
|
1981
|
+
tenant_id: tenantId
|
|
1052
1982
|
});
|
|
1983
|
+
if (release) releaseCourseIds.set(releaseId, String(release.release_course_id));
|
|
1053
1984
|
}
|
|
1054
|
-
|
|
1985
|
+
return {
|
|
1986
|
+
currentReleaseId,
|
|
1987
|
+
lineage,
|
|
1988
|
+
lessons,
|
|
1989
|
+
releaseCourseIds,
|
|
1990
|
+
keysByRelease,
|
|
1991
|
+
pinLearnerIds: new Set(pins.map((p) => p.learnerId))
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
function orderEffectiveLessons(selection, orderOf) {
|
|
1995
|
+
const currentKeys = selection.keysByRelease.get(selection.currentReleaseId);
|
|
1996
|
+
const rank = (lesson) => {
|
|
1997
|
+
const position = (releaseId, lessonId) => {
|
|
1998
|
+
const at = orderOf(releaseId).indexOf(lessonId);
|
|
1999
|
+
return at < 0 ? Number.MAX_SAFE_INTEGER : at;
|
|
2000
|
+
};
|
|
2001
|
+
const currentId = currentKeys?.get(lesson.lessonKey);
|
|
2002
|
+
if (currentId) return [
|
|
2003
|
+
0,
|
|
2004
|
+
position(selection.currentReleaseId, currentId),
|
|
2005
|
+
0
|
|
2006
|
+
];
|
|
2007
|
+
return [
|
|
2008
|
+
1,
|
|
2009
|
+
selection.lineage.indexOf(lesson.releaseId),
|
|
2010
|
+
position(lesson.releaseId, lesson.lessonId)
|
|
2011
|
+
];
|
|
2012
|
+
};
|
|
2013
|
+
return [...selection.lessons].sort((a, b) => {
|
|
2014
|
+
const [a0, a1, a2] = rank(a);
|
|
2015
|
+
const [b0, b1, b2] = rank(b);
|
|
2016
|
+
return a0 - b0 || a1 - b1 || a2 - b2 || a.lessonKey.localeCompare(b.lessonKey);
|
|
2017
|
+
});
|
|
2018
|
+
}
|
|
1055
2019
|
//#endregion
|
|
1056
2020
|
//#region src/family-intake-contracts.ts
|
|
1057
2021
|
function familyIntakeScopeId(tenantId) {
|
|
@@ -1292,6 +2256,7 @@ function evaluateGroupSharingPolicy(policy, facts) {
|
|
|
1292
2256
|
if (!Number.isFinite(captured) || !Number.isFinite(Date.parse(policy.historyFrom)) || captured < Date.parse(policy.historyFrom)) reasons.push("history");
|
|
1293
2257
|
if (policy.courseIds.mode === "include" && (!facts.courseId || !policy.courseIds.values.includes(facts.courseId))) reasons.push("course");
|
|
1294
2258
|
if (policy.subjects.mode === "include" && (!facts.subject || !policy.subjects.values.includes(facts.subject))) reasons.push("subject");
|
|
2259
|
+
if (facts.courseShareable === false) reasons.push("course-private");
|
|
1295
2260
|
if (policy.parentReviewedOnly && !facts.parentReviewed) reasons.push("parent-review");
|
|
1296
2261
|
const eligible = reasons.length === 0;
|
|
1297
2262
|
return {
|
|
@@ -1461,6 +2426,7 @@ function validateWorkbookAssessment(value) {
|
|
|
1461
2426
|
"unknown"
|
|
1462
2427
|
].includes(q.mappingProvenance)) throw new WorkbookAssessmentValidationError("Invalid mapping provenance");
|
|
1463
2428
|
if (q.evidenceLimitations !== void 0 && (!Array.isArray(q.evidenceLimitations) || q.evidenceLimitations.length > 20 || q.evidenceLimitations.some((v) => typeof v !== "string" || v.length > 500))) throw new WorkbookAssessmentValidationError("Invalid evidence limitations");
|
|
2429
|
+
for (const [value2, label] of [[q.questionRevisionId, "question revision id"], [q.lessonRevisionId, "lesson revision id"]]) if (value2 !== void 0 && (typeof value2 !== "string" || !value2.trim() || value2.length > 200)) throw new WorkbookAssessmentValidationError(`Invalid ${label}`);
|
|
1464
2430
|
text(q.id, "question id", 200);
|
|
1465
2431
|
text(q.prompt, "prompt");
|
|
1466
2432
|
text(q.answer, "answer");
|
|
@@ -1859,6 +2825,19 @@ var WorkbookAssessmentRevision = class extends SmrtObject {
|
|
|
1859
2825
|
learner_id: this.learnerId,
|
|
1860
2826
|
attempt_id: this.attemptId
|
|
1861
2827
|
})) throw new Error("Workbook reviewed source mismatch");
|
|
2828
|
+
const assessment = JSON.parse(this.assessmentJson);
|
|
2829
|
+
for (const [column, table, ids] of [[
|
|
2830
|
+
"question revision",
|
|
2831
|
+
"question_revisions",
|
|
2832
|
+
assessment.questions.map((q) => q.questionRevisionId)
|
|
2833
|
+
], [
|
|
2834
|
+
"lesson revision",
|
|
2835
|
+
"lesson_revisions",
|
|
2836
|
+
assessment.questions.map((q) => q.lessonRevisionId)
|
|
2837
|
+
]]) for (const id of new Set(ids.filter((v) => !!v))) if (!await this.db.get(table, {
|
|
2838
|
+
id,
|
|
2839
|
+
tenant_id: this.tenantId
|
|
2840
|
+
})) throw new Error(`Workbook ${column} reference mismatch`);
|
|
1862
2841
|
if (!["ai", "guardian"].includes(this.kind)) throw new Error("Invalid revision kind");
|
|
1863
2842
|
return super.save(options);
|
|
1864
2843
|
}
|
|
@@ -2192,15 +3171,17 @@ function progressView(row) {
|
|
|
2192
3171
|
};
|
|
2193
3172
|
}
|
|
2194
3173
|
var LearnerService = class LearnerService {
|
|
2195
|
-
constructor(db, authorize, authorizeCatalog) {
|
|
3174
|
+
constructor(db, authorize, authorizeCatalog, learnerOptions = {}) {
|
|
2196
3175
|
this.db = db;
|
|
2197
3176
|
this.authorize = authorize;
|
|
2198
3177
|
this.authorizeCatalog = authorizeCatalog;
|
|
3178
|
+
this.learnerOptions = learnerOptions;
|
|
2199
3179
|
this.postgres = isPostgresDatabase(db);
|
|
2200
3180
|
}
|
|
2201
3181
|
db;
|
|
2202
3182
|
authorize;
|
|
2203
3183
|
authorizeCatalog;
|
|
3184
|
+
learnerOptions;
|
|
2204
3185
|
transactionState;
|
|
2205
3186
|
writeFailure;
|
|
2206
3187
|
writeTail = Promise.resolve();
|
|
@@ -2264,7 +3245,7 @@ var LearnerService = class LearnerService {
|
|
|
2264
3245
|
}
|
|
2265
3246
|
/** Internal subclass hook: preserve the concrete owning service in a transaction. */
|
|
2266
3247
|
createTransactionService(db) {
|
|
2267
|
-
return new LearnerService(db, this.authorize, this.authorizeCatalog);
|
|
3248
|
+
return new LearnerService(db, this.authorize, this.authorizeCatalog, this.learnerOptions);
|
|
2268
3249
|
}
|
|
2269
3250
|
/** Internal compensation hook, invoked after in-flight work drains on abort. */
|
|
2270
3251
|
async rollbackResources(_cause, _rootDb) {}
|
|
@@ -2312,7 +3293,7 @@ var LearnerService = class LearnerService {
|
|
|
2312
3293
|
assertServiceOpen() {
|
|
2313
3294
|
if (this.transactionState === "aborting" || this.transactionState === "closed") throw new Error("Learner transaction scope is closed");
|
|
2314
3295
|
}
|
|
2315
|
-
async requireActor(request, action) {
|
|
3296
|
+
async requireActor(request, action, delegation = null) {
|
|
2316
3297
|
this.assertServiceOpen();
|
|
2317
3298
|
const context = getCurrentTenant();
|
|
2318
3299
|
if (!context || !request.tenantId || !request.actorUserId || !request.learnerId || context.tenantId !== request.tenantId || context.userId !== request.actorUserId || context.superAdminBypass) throw new LearnerAuthorizationError();
|
|
@@ -2335,10 +3316,41 @@ var LearnerService = class LearnerService {
|
|
|
2335
3316
|
id: learner.profileId,
|
|
2336
3317
|
_meta_type: "@happyvertical/smrt-profiles:Person"
|
|
2337
3318
|
})) throw new LearnerAuthorizationError();
|
|
2338
|
-
const
|
|
3319
|
+
const holder = delegation?.delegatingGuardianUserId ?? request.actorUserId;
|
|
3320
|
+
if (delegation && (learner.userId === request.actorUserId || !await this.db.get("users", {
|
|
3321
|
+
id: request.actorUserId,
|
|
3322
|
+
status: "active"
|
|
3323
|
+
}) || !await this.db.get("memberships", {
|
|
3324
|
+
tenant_id: request.tenantId,
|
|
3325
|
+
user_id: holder,
|
|
3326
|
+
status: "active"
|
|
3327
|
+
}))) throw new LearnerAuthorizationError();
|
|
3328
|
+
if (delegation) {
|
|
3329
|
+
const verify = this.learnerOptions.verifyDelegation;
|
|
3330
|
+
let verified = false;
|
|
3331
|
+
if (typeof verify === "function") try {
|
|
3332
|
+
const decision = await verify(this.db, {
|
|
3333
|
+
tenantId: request.tenantId,
|
|
3334
|
+
actorUserId: request.actorUserId,
|
|
3335
|
+
delegatingGuardianUserId: delegation.delegatingGuardianUserId,
|
|
3336
|
+
delegationId: delegation.delegationId,
|
|
3337
|
+
learnerId: request.learnerId,
|
|
3338
|
+
...delegation.courseId ? { courseId: delegation.courseId } : {},
|
|
3339
|
+
...delegation.operation ? { operation: delegation.operation } : {}
|
|
3340
|
+
});
|
|
3341
|
+
verified = typeof decision === "object" && decision !== null ? decision.allowed === true : decision === true;
|
|
3342
|
+
} catch {
|
|
3343
|
+
verified = false;
|
|
3344
|
+
}
|
|
3345
|
+
if (!verified) throw new LearnerAuthorizationError();
|
|
3346
|
+
}
|
|
3347
|
+
const own = learner.userId === holder;
|
|
2339
3348
|
let relationshipTypeId;
|
|
2340
3349
|
if (!own) {
|
|
2341
|
-
const user = await this.db.get("users", {
|
|
3350
|
+
const user = await this.db.get("users", {
|
|
3351
|
+
id: holder,
|
|
3352
|
+
...delegation ? { status: "active" } : {}
|
|
3353
|
+
});
|
|
2342
3354
|
const relationship = user && request.guardianRelationshipId ? await this.db.get("profile_relationships", {
|
|
2343
3355
|
id: request.guardianRelationshipId,
|
|
2344
3356
|
tenant_id: request.tenantId,
|
|
@@ -2354,14 +3366,29 @@ var LearnerService = class LearnerService {
|
|
|
2354
3366
|
"education.learner.packet",
|
|
2355
3367
|
"education.learner.notes.read",
|
|
2356
3368
|
"education.learner.notes.add",
|
|
2357
|
-
"education.learner.complete"
|
|
3369
|
+
"education.learner.complete",
|
|
3370
|
+
"education.learner.review-response"
|
|
2358
3371
|
].includes(action)) throw new LearnerAuthorizationError();
|
|
3372
|
+
const { delegatingGuardianUserId: _guardian, delegationId: _id, relationshipTypeId: _type, delegated: _delegated, delegatingRelationshipTypeId: _delegatingType, own: _own, ...base } = request;
|
|
3373
|
+
const authority = delegation ? {
|
|
3374
|
+
delegated: true,
|
|
3375
|
+
relationshipTypeId: void 0,
|
|
3376
|
+
delegatingGuardianUserId: delegation.delegatingGuardianUserId,
|
|
3377
|
+
delegationId: delegation.delegationId,
|
|
3378
|
+
delegatingRelationshipTypeId: relationshipTypeId
|
|
3379
|
+
} : {
|
|
3380
|
+
delegated: void 0,
|
|
3381
|
+
relationshipTypeId,
|
|
3382
|
+
delegatingGuardianUserId: void 0,
|
|
3383
|
+
delegationId: void 0,
|
|
3384
|
+
delegatingRelationshipTypeId: void 0
|
|
3385
|
+
};
|
|
2359
3386
|
if (await this.authorize({
|
|
2360
|
-
...
|
|
3387
|
+
...base,
|
|
3388
|
+
...authority,
|
|
2361
3389
|
executor: this.db,
|
|
2362
3390
|
action,
|
|
2363
|
-
own
|
|
2364
|
-
relationshipTypeId
|
|
3391
|
+
own
|
|
2365
3392
|
}) !== true) throw new LearnerAuthorizationError();
|
|
2366
3393
|
return learner;
|
|
2367
3394
|
}
|
|
@@ -2509,15 +3536,40 @@ var LearnerService = class LearnerService {
|
|
|
2509
3536
|
};
|
|
2510
3537
|
});
|
|
2511
3538
|
}
|
|
3539
|
+
/**
|
|
3540
|
+
* Creates a planned curriculum. A delegated agent passes
|
|
3541
|
+
* `delegatingGuardianUserId` + `delegationId` (as for `enroll`); the new
|
|
3542
|
+
* curriculum then also appends one `CurriculumCreationRecord`.
|
|
3543
|
+
*/
|
|
2512
3544
|
async createCurriculum(request) {
|
|
3545
|
+
let delegation;
|
|
3546
|
+
try {
|
|
3547
|
+
delegation = readDelegation(request);
|
|
3548
|
+
} catch (error) {
|
|
3549
|
+
if (error instanceof CourseAuthoringAuthorizationError) throw new LearnerAuthorizationError();
|
|
3550
|
+
throw error;
|
|
3551
|
+
}
|
|
2513
3552
|
return this.mutate(async (service) => {
|
|
2514
|
-
const learner = await service.requireActor(request, "education.learner.plan"
|
|
3553
|
+
const learner = await service.requireActor(request, "education.learner.plan", delegation ? {
|
|
3554
|
+
...delegation,
|
|
3555
|
+
operation: "create-curriculum"
|
|
3556
|
+
} : null);
|
|
2515
3557
|
if (typeof request.title !== "string" || !request.title.trim()) throw new Error("Curriculum title is required");
|
|
2516
|
-
|
|
3558
|
+
const created = await (await CurriculumCollection.create({ db: service.db })).create({
|
|
2517
3559
|
tenantId: request.tenantId,
|
|
2518
3560
|
learnerId: String(learner.id),
|
|
2519
3561
|
title: request.title
|
|
2520
|
-
})
|
|
3562
|
+
});
|
|
3563
|
+
if (delegation) await (await CurriculumCreationRecordCollection.create({ db: service.db })).create({
|
|
3564
|
+
_insertOnly: true,
|
|
3565
|
+
tenantId: request.tenantId,
|
|
3566
|
+
curriculumId: String(created.id),
|
|
3567
|
+
learnerId: String(learner.id),
|
|
3568
|
+
actorUserId: request.actorUserId,
|
|
3569
|
+
...delegation,
|
|
3570
|
+
recordedAt: /* @__PURE__ */ new Date()
|
|
3571
|
+
});
|
|
3572
|
+
return curriculumView(created);
|
|
2521
3573
|
});
|
|
2522
3574
|
}
|
|
2523
3575
|
async catalog(request, courseId) {
|
|
@@ -2531,12 +3583,81 @@ var LearnerService = class LearnerService {
|
|
|
2531
3583
|
if (!projection) throw new LearnerAuthorizationError();
|
|
2532
3584
|
return projection;
|
|
2533
3585
|
}
|
|
3586
|
+
/**
|
|
3587
|
+
* The lessons that apply to an authorized enrollment. An unpinned enrollment
|
|
3588
|
+
* reads its own course, exactly as before. A pinned one reads the effective
|
|
3589
|
+
* set shared with `CourseAuthoringService.getEnrollmentRelease`: started
|
|
3590
|
+
* lessons stay on their original immutable rows, the rest follow the current
|
|
3591
|
+
* release. Every course row is still authorized through the catalog.
|
|
3592
|
+
*/
|
|
3593
|
+
async lessonScope(request, enrollment) {
|
|
3594
|
+
const selection = await this.db.tableExists("enrollment_release_pins") ? await resolveEnrollmentLessons(this.db, request.tenantId, String(enrollment.id)) : null;
|
|
3595
|
+
if (!selection) {
|
|
3596
|
+
const catalog = await this.catalog(request, enrollment.courseId);
|
|
3597
|
+
return {
|
|
3598
|
+
course: catalog.course,
|
|
3599
|
+
entries: catalog.sections.flatMap((section) => section.lessons.map(({ lesson }) => ({
|
|
3600
|
+
lesson,
|
|
3601
|
+
course: catalog.course
|
|
3602
|
+
})))
|
|
3603
|
+
};
|
|
3604
|
+
}
|
|
3605
|
+
if ([...selection.pinLearnerIds].some((id) => id !== enrollment.learnerId)) throw new LearnerAuthorizationError();
|
|
3606
|
+
const releases = /* @__PURE__ */ new Set([selection.currentReleaseId, ...selection.lessons.map((lesson) => lesson.releaseId)]);
|
|
3607
|
+
const catalogs = /* @__PURE__ */ new Map();
|
|
3608
|
+
for (const releaseId of releases) {
|
|
3609
|
+
const courseId = selection.releaseCourseIds.get(releaseId);
|
|
3610
|
+
if (!courseId) throw new LearnerAuthorizationError();
|
|
3611
|
+
catalogs.set(releaseId, await this.catalog(request, courseId));
|
|
3612
|
+
}
|
|
3613
|
+
const rows = /* @__PURE__ */ new Map();
|
|
3614
|
+
const order = /* @__PURE__ */ new Map();
|
|
3615
|
+
for (const [releaseId, catalog] of catalogs) {
|
|
3616
|
+
const ids = [];
|
|
3617
|
+
for (const section of catalog.sections) for (const { lesson } of section.lessons) {
|
|
3618
|
+
ids.push(String(lesson.id));
|
|
3619
|
+
rows.set(String(lesson.id), {
|
|
3620
|
+
lesson,
|
|
3621
|
+
course: catalog.course
|
|
3622
|
+
});
|
|
3623
|
+
}
|
|
3624
|
+
order.set(releaseId, ids);
|
|
3625
|
+
}
|
|
3626
|
+
const entries = [];
|
|
3627
|
+
for (const effective of orderEffectiveLessons(selection, (releaseId) => order.get(releaseId) ?? [])) {
|
|
3628
|
+
const entry = rows.get(effective.lessonId);
|
|
3629
|
+
if (entry && order.get(effective.releaseId)?.includes(effective.lessonId)) entries.push(entry);
|
|
3630
|
+
}
|
|
3631
|
+
return {
|
|
3632
|
+
course: catalogs.get(selection.currentReleaseId).course,
|
|
3633
|
+
entries
|
|
3634
|
+
};
|
|
3635
|
+
}
|
|
3636
|
+
async lessonInScope(request, enrollment, lessonId) {
|
|
3637
|
+
return (await this.lessonScope(request, enrollment)).entries.some(({ lesson }) => String(lesson.id) === lessonId);
|
|
3638
|
+
}
|
|
2534
3639
|
async lock(table, id, tenant) {
|
|
2535
3640
|
if (this.postgres) await this.db.query(`SELECT id FROM ${table} WHERE id = ? AND tenant_id = ? FOR UPDATE`, [id, tenant]);
|
|
2536
3641
|
}
|
|
3642
|
+
/**
|
|
3643
|
+
* Creates (or returns) the enrollment that assigns a course to a learner. A
|
|
3644
|
+
* delegated agent passes `delegatingGuardianUserId` + `delegationId`; a new
|
|
3645
|
+
* delegated enrollment also appends an `EnrollmentAssignmentRecord`.
|
|
3646
|
+
*/
|
|
2537
3647
|
async enroll(request) {
|
|
3648
|
+
let delegation;
|
|
3649
|
+
try {
|
|
3650
|
+
delegation = readDelegation(request);
|
|
3651
|
+
} catch (error) {
|
|
3652
|
+
if (error instanceof CourseAuthoringAuthorizationError) throw new LearnerAuthorizationError();
|
|
3653
|
+
throw error;
|
|
3654
|
+
}
|
|
2538
3655
|
return this.mutate(async (service) => {
|
|
2539
|
-
const learner = await service.requireActor(request, "education.learner.plan"
|
|
3656
|
+
const learner = await service.requireActor(request, "education.learner.plan", delegation ? {
|
|
3657
|
+
...delegation,
|
|
3658
|
+
courseId: request.courseId,
|
|
3659
|
+
operation: "enroll"
|
|
3660
|
+
} : null);
|
|
2540
3661
|
await service.lock("curricula", request.curriculumId, request.tenantId);
|
|
2541
3662
|
const curriculum = await (await CurriculumCollection.create({ db: service.db })).get({
|
|
2542
3663
|
id: request.curriculumId,
|
|
@@ -2555,13 +3676,23 @@ var LearnerService = class LearnerService {
|
|
|
2555
3676
|
if (existing.learnerId !== learner.id) throw new LearnerAuthorizationError();
|
|
2556
3677
|
return enrollmentView(existing);
|
|
2557
3678
|
}
|
|
2558
|
-
|
|
3679
|
+
const created = await enrollments.create({
|
|
2559
3680
|
tenantId: request.tenantId,
|
|
2560
3681
|
curriculumId: String(curriculum.id),
|
|
2561
3682
|
learnerId: String(learner.id),
|
|
2562
3683
|
courseId: request.courseId,
|
|
2563
3684
|
_insertOnly: true
|
|
2564
|
-
})
|
|
3685
|
+
});
|
|
3686
|
+
if (delegation) await (await EnrollmentAssignmentRecordCollection.create({ db: service.db })).create({
|
|
3687
|
+
_insertOnly: true,
|
|
3688
|
+
tenantId: request.tenantId,
|
|
3689
|
+
enrollmentId: String(created.id),
|
|
3690
|
+
learnerId: String(learner.id),
|
|
3691
|
+
actorUserId: request.actorUserId,
|
|
3692
|
+
...delegation,
|
|
3693
|
+
recordedAt: /* @__PURE__ */ new Date()
|
|
3694
|
+
});
|
|
3695
|
+
return enrollmentView(created);
|
|
2565
3696
|
});
|
|
2566
3697
|
}
|
|
2567
3698
|
async requireEnrollment(request, action) {
|
|
@@ -2613,8 +3744,8 @@ var LearnerService = class LearnerService {
|
|
|
2613
3744
|
}
|
|
2614
3745
|
async listProgress(request) {
|
|
2615
3746
|
const enrollment = await this.requireEnrollment(request, "education.learner.read");
|
|
2616
|
-
const
|
|
2617
|
-
const lessons = new Set(
|
|
3747
|
+
const scope = await this.lessonScope(request, enrollment);
|
|
3748
|
+
const lessons = new Set(scope.entries.map(({ lesson }) => String(lesson.id)));
|
|
2618
3749
|
const rows = await (await LessonProgressCollection.create({ db: this.db })).list({ where: {
|
|
2619
3750
|
enrollmentId: String(enrollment.id),
|
|
2620
3751
|
learnerId: request.learnerId,
|
|
@@ -2648,18 +3779,19 @@ var LearnerService = class LearnerService {
|
|
|
2648
3779
|
await this.requireActor(request, "education.learner.read");
|
|
2649
3780
|
const result = [];
|
|
2650
3781
|
for (const enrollment of enrollments) {
|
|
2651
|
-
const
|
|
3782
|
+
const scope = await this.lessonScope(request, enrollment);
|
|
2652
3783
|
const progresses = await (await LessonProgressCollection.create({ db: this.db })).list({ where: {
|
|
2653
3784
|
tenantId: request.tenantId,
|
|
2654
3785
|
enrollmentId: String(enrollment.id),
|
|
2655
3786
|
learnerId: request.learnerId
|
|
2656
3787
|
} });
|
|
2657
3788
|
const byLesson = new Map(await Promise.all(progresses.map(async (row) => [row.lessonId, await this.projectProgress(request, row)])));
|
|
2658
|
-
const
|
|
3789
|
+
const next = scope.entries.find(({ lesson: candidate }) => {
|
|
2659
3790
|
const progress2 = byLesson.get(String(candidate.id));
|
|
2660
3791
|
return !progress2 || !["complete", "skipped"].includes(progress2.status);
|
|
2661
3792
|
});
|
|
2662
|
-
if (!
|
|
3793
|
+
if (!next) continue;
|
|
3794
|
+
const { lesson, course } = next;
|
|
2663
3795
|
const progress = byLesson.get(String(lesson.id));
|
|
2664
3796
|
result.push({
|
|
2665
3797
|
enrollment: enrollmentView(enrollment),
|
|
@@ -2668,7 +3800,7 @@ var LearnerService = class LearnerService {
|
|
|
2668
3800
|
id: String(lesson.id),
|
|
2669
3801
|
title: lesson.title,
|
|
2670
3802
|
kind: lesson.constructor.name,
|
|
2671
|
-
requiresOnlineResource:
|
|
3803
|
+
requiresOnlineResource: course.modality === "online" || lesson.constructor.name === "Video"
|
|
2672
3804
|
}
|
|
2673
3805
|
});
|
|
2674
3806
|
}
|
|
@@ -2677,30 +3809,30 @@ var LearnerService = class LearnerService {
|
|
|
2677
3809
|
/** All lesson summaries for one authorized assignment/enrollment. */
|
|
2678
3810
|
async assignmentLessons(request) {
|
|
2679
3811
|
const enrollment = await this.requireEnrollment(request, "education.learner.read");
|
|
2680
|
-
const
|
|
3812
|
+
const scope = await this.lessonScope(request, enrollment);
|
|
2681
3813
|
const progresses = await (await LessonProgressCollection.create({ db: this.db })).list({ where: {
|
|
2682
3814
|
tenantId: request.tenantId,
|
|
2683
3815
|
enrollmentId: String(enrollment.id),
|
|
2684
3816
|
learnerId: request.learnerId
|
|
2685
3817
|
} });
|
|
2686
3818
|
const byLesson = new Map(progresses.map((row) => [row.lessonId, row]));
|
|
2687
|
-
return Promise.all(
|
|
3819
|
+
return Promise.all(scope.entries.map(async ({ lesson, course }) => ({
|
|
2688
3820
|
enrollment: enrollmentView(enrollment),
|
|
2689
3821
|
progress: byLesson.get(String(lesson.id)) ? await this.projectProgress(request, byLesson.get(String(lesson.id))) : null,
|
|
2690
3822
|
lesson: {
|
|
2691
3823
|
id: String(lesson.id),
|
|
2692
3824
|
title: lesson.title,
|
|
2693
3825
|
kind: lesson.constructor.name,
|
|
2694
|
-
requiresOnlineResource:
|
|
3826
|
+
requiresOnlineResource: course.modality === "online" || lesson.constructor.name === "Video"
|
|
2695
3827
|
}
|
|
2696
|
-
})))
|
|
3828
|
+
})));
|
|
2697
3829
|
}
|
|
2698
3830
|
/** Start once. Caller-supplied learner/status/score/counter patches are never applied. */
|
|
2699
3831
|
async startLesson(request) {
|
|
2700
3832
|
return this.mutate(async (service) => {
|
|
2701
3833
|
const enrollment = await service.requireEnrollment(request, "education.learner.start");
|
|
2702
3834
|
await service.lock("enrollments", request.enrollmentId, request.tenantId);
|
|
2703
|
-
if (!
|
|
3835
|
+
if (!await service.lessonInScope(request, enrollment, request.lessonId)) throw new LearnerAuthorizationError();
|
|
2704
3836
|
const progress = await LessonProgressCollection.create({ db: service.db });
|
|
2705
3837
|
const existing = await progress.get({
|
|
2706
3838
|
enrollmentId: String(enrollment.id),
|
|
@@ -25459,7 +26591,7 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
25459
26591
|
async progress(request, action) {
|
|
25460
26592
|
const enrollment = await this.requireEnrollment(request, action);
|
|
25461
26593
|
await this.lockProgressOwner(request);
|
|
25462
|
-
if (!
|
|
26594
|
+
if (!await this.lessonInScope(request, enrollment, request.lessonId)) throw new LearnerAuthorizationError();
|
|
25463
26595
|
const collection = await LessonProgressCollection.create({ db: this.db });
|
|
25464
26596
|
let progress = await collection.get({
|
|
25465
26597
|
enrollmentId: request.enrollmentId,
|
|
@@ -25493,8 +26625,7 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
25493
26625
|
lessonId: row.lessonId,
|
|
25494
26626
|
tenantId: request.tenantId
|
|
25495
26627
|
}, { cache: false });
|
|
25496
|
-
|
|
25497
|
-
if (!progress || !catalog.sections.some((section) => section.lessons.some(({ lesson }) => lesson.id === row.lessonId))) throw new LearnerAuthorizationError();
|
|
26628
|
+
if (!progress || !await this.lessonInScope(request, enrollment, row.lessonId)) throw new LearnerAuthorizationError();
|
|
25498
26629
|
return {
|
|
25499
26630
|
row,
|
|
25500
26631
|
progress
|
|
@@ -25622,7 +26753,7 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
25622
26753
|
*/
|
|
25623
26754
|
async assertLessonAssignment(request) {
|
|
25624
26755
|
const enrollment = await this.requireEnrollment(request, "education.learner.submit");
|
|
25625
|
-
if (!
|
|
26756
|
+
if (!await this.lessonInScope(request, enrollment, request.lessonId)) throw new LearnerAuthorizationError();
|
|
25626
26757
|
}
|
|
25627
26758
|
/** Reject legacy upload/review entry points using authoritative owned scope. */
|
|
25628
26759
|
async assertNonWorkbookSubmissionScope(request) {
|
|
@@ -25798,7 +26929,7 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
25798
26929
|
/** Latest retained completed-work evidence; lesson completion is independent. */
|
|
25799
26930
|
async getLatestLessonSubmission(request) {
|
|
25800
26931
|
const enrollment = await this.requireEnrollment(request, "education.learner.read");
|
|
25801
|
-
if (!
|
|
26932
|
+
if (!await this.lessonInScope(request, enrollment, request.lessonId)) throw new LearnerAuthorizationError();
|
|
25802
26933
|
const progress = await (await LessonProgressCollection.create({ db: this.db })).get({
|
|
25803
26934
|
enrollmentId: request.enrollmentId,
|
|
25804
26935
|
learnerId: request.learnerId,
|
|
@@ -25962,7 +27093,7 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
25962
27093
|
/** Sign only an exact authorized printable join; answer keys never qualify. */
|
|
25963
27094
|
async signLessonPrintable(request) {
|
|
25964
27095
|
const enrollment = await this.requireEnrollment(request, "education.learner.read");
|
|
25965
|
-
if (!
|
|
27096
|
+
if (!await this.lessonInScope(request, enrollment, request.lessonId)) throw new LearnerAuthorizationError();
|
|
25966
27097
|
if (!await (await LessonAssetCollection.create({ db: this.db })).get({
|
|
25967
27098
|
tenantId: request.tenantId,
|
|
25968
27099
|
lessonId: request.lessonId,
|
|
@@ -28041,6 +29172,6 @@ var WorkbookPhotoService = class WorkbookPhotoService extends WorkbookService {
|
|
|
28041
29172
|
}
|
|
28042
29173
|
};
|
|
28043
29174
|
//#endregion
|
|
28044
|
-
export { FamilyIntakeDecision as $, WorkbookFeedbackDelivery as A,
|
|
29175
|
+
export { FamilyIntakeDecision as $, CourseCollection as $t, WorkbookFeedbackDelivery as A, Learner as An, COURSE_AUTHORING_ACTIONS as At, validateWorkbookAssessment as B, Worksheet as Bn, CoursePolicyBasisMismatchError as Bt, WorkbookActivity as C, SubmissionAsset as Cn, EnrollmentAutoUpgradeSkipCollection as Ct, WorkbookAttempt as D, LessonAsset as Dn, LessonRevisionCollection as Dt, WorkbookAssessmentRevisionCollection as E, validateRubricResults as En, LessonRevision as Et, WorkbookWorkFilingCollection as F, Exercise as Fn, CourseAuthoringValidationError as Ft, GroupSharingConflictError as G, EnrollmentPinConflictError as Gt, evaluateGroupSharingPolicy as H, CourseRevisionAlreadyReleasedError as Ht, WORKBOOK_MATH_VOCABULARY as I, ExternalCourse as In, CourseDiscardRefusedError as It, validateGroupSharingPolicy as J, ReviewNotCurrentError as Jt, GroupSharingPolicyValidationError as K, MAX_PAGE_SIZE as Kt, WorkbookAssessmentValidationError as L, Lesson as Ln, CourseDiscardedError as Lt, WorkbookFeedbackRelease as M, AuthoredCourse as Mn, CourseArchivedError as Mt, WorkbookFeedbackReleaseCollection as N, Course as Nn, CourseAuthoringAuthorizationError as Nt, WorkbookAttemptCollection as O, Curriculum as On, ReleasedLesson as Ot, WorkbookWorkFiling as P, CourseSection as Pn, CourseAuthoringError as Pt, FamilyIntakeCollection as Q, AuthoredCourseCollection as Qt, WorkbookCompletionConflictError as R, Reading as Rn, CourseMaterialCopyError as Rt, Workbook as S, Submission as Sn, EnrollmentAutoUpgradeSkip as St, WorkbookAssessmentRevision as T, parseQuestionRubric as Tn, EnrollmentReleasePinCollection as Tt, summarizeWorkbookEvidence as U, CourseStaleHeadError as Ut, workbookAggregateFeedback as V, CourseRequestKeyConflictError as Vt, GroupSharingAuthorizationError as W, DEFAULT_PAGE_SIZE as Wt, FamilyIntakeAsset as X, CatalogService as Xt, FamilyIntake as Y, CatalogAuthorizationError as Yt, FamilyIntakeAssetCollection as Z, AttestationCollection as Zt, WorkbookWorkflowRequiredError as _, WorksheetCollection as _n, CourseUpgradeChoiceCollection as _t, WorkbookContextRevisionCollection as a, ExternalCourseCollection as an, readDelegation as at, WorkbookCompletionDecision as b, Grade as bn, EnrollmentAssignmentRecord as bt, WorkbookPhotoDecision as c, LessonAssetCollection as cn, CourseDiscardTombstone as ct, WorkbookPhotoIntakeCollection as d, ReadingCollection as dn, CourseDraftHeadCollection as dt, CourseSectionCollection as en, FamilyIntakeDecisionCollection as et, validateWorkbookPhotoIdentification as f, ScannedSubmissionCollection as fn, CourseRelease as ft, SubmissionStorageCleanupError as g, VideoCollection as gn, CourseUpgradeChoice as gt, SubmissionService as h, SubmissionIntakeBindingCollection as hn, CourseRevisionCollection as ht, WorkbookContextRevision as i, ExternalCompletionCollection as in, delegationRecordFields as it, WorkbookFeedbackDeliveryCollection as j, LessonProgress as jn, COURSE_PROVENANCES as jt, WorkbookCollection as k, Enrollment as kn, ReleasedLessonCollection as kt, WorkbookPhotoDecisionCollection as l, LessonCollection as ln, CourseDiscardTombstoneCollection as lt, normalizeWorkbookAnswerRegions as m, SubmissionCollection as mn, CourseRevision as mt, preservePhotoProposal as n, EnrollmentCollection as nn, resolveEnrollmentLessons as nt, WorkbookPhotoAsset as o, GradeCollection as on, CourseArchiveDecision as ot, normalizeWorkbookObservedLayout as p, SubmissionAssetCollection as pn, CourseReleaseCollection as pt, canonicalGroupSharingPolicy as q, ReviewGateRequiredError as qt, WorkbookService as r, ExerciseCollection as rn, delegationActorFields as rt, WorkbookPhotoAssetCollection as s, LearnerCollection as sn, CourseArchiveDecisionCollection as st, WorkbookPhotoService as t, CurriculumCollection as tn, familyIntakeScopeId as tt, WorkbookPhotoIntake as u, LessonProgressCollection as un, CourseDraftHead as ut, LearnerAuthorizationError as v, Attestation as vn, CurriculumCreationRecord as vt, WorkbookActivityCollection as w, SubmissionIntakeBinding as wn, EnrollmentReleasePin as wt, WorkbookCompletionDecisionCollection as x, ScannedSubmission as xn, EnrollmentAssignmentRecordCollection as xt, LearnerService as y, ExternalCompletion as yn, CurriculumCreationRecordCollection as yt, aggregateWorkbookQuestions as z, Video as zn, CourseNotFoundError as zt };
|
|
28045
29176
|
|
|
28046
|
-
//# sourceMappingURL=workbook-photo-service-
|
|
29177
|
+
//# sourceMappingURL=workbook-photo-service-BxvZDqjM.js.map
|