@happyvertical/smrt-education 0.60.4 → 0.60.6
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/class-crunchlabs.md +9 -0
- package/agents/course-bundle-import.md +18 -3
- package/agents/lesson-materials.md +24 -1
- package/dist/chunks/{workbook-photo-service-wqZZWGLb.js → workbook-photo-service-DEb2LSC0.js} +144 -28
- package/dist/chunks/workbook-photo-service-DEb2LSC0.js.map +1 -0
- package/dist/course-bundle-manifest-v2.d.ts +13 -1
- package/dist/course-bundle-manifest-v2.d.ts.map +1 -1
- package/dist/course-bundle-service.d.ts.map +1 -1
- package/dist/course-bundle.d.ts +7 -1
- package/dist/course-bundle.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +128 -22
- package/dist/index.js.map +1 -1
- package/dist/ingestion.js +1 -1
- package/dist/lesson-assets.d.ts +29 -0
- package/dist/lesson-assets.d.ts.map +1 -1
- package/dist/manifest.json +111 -9
- package/dist/smrt-knowledge.json +45 -9
- package/dist/sources/class-crunchlabs.d.ts +5 -0
- package/dist/sources/class-crunchlabs.d.ts.map +1 -1
- package/dist/submission-service.d.ts +37 -0
- package/dist/submission-service.d.ts.map +1 -1
- package/package.json +8 -8
- package/dist/chunks/workbook-photo-service-wqZZWGLb.js.map +0 -1
|
@@ -125,6 +125,15 @@ skipped) or `memoryClassCrunchLabsSource(files)`. It never reads video files.
|
|
|
125
125
|
number), so `attachCourse` merges the parts in lesson order whatever order they
|
|
126
126
|
arrive in.
|
|
127
127
|
|
|
128
|
+
- Lesson steps (#4656): the slides are step `launch` (order 0, "Launch"); each
|
|
129
|
+
matched video is a step keyed by its real step code (`6`, `1b`) with the
|
|
130
|
+
video's title as label, ordered numerically (`classCrunchLabsStepOrder`: `1` <
|
|
131
|
+
`1a` < `1b` < `2`; videos sharing a code share the step, offset by 0.001 each);
|
|
132
|
+
the status check assessment (printable PDF and the editable master) is step
|
|
133
|
+
`check` (order 9999, "Status check"). The mission log, handouts, printables, the
|
|
134
|
+
teacher manual and the assessment guidance carry no step: the first three are the
|
|
135
|
+
learner's setup, the others stay guardian material.
|
|
136
|
+
|
|
128
137
|
The `report` lists lessons, members, parts, pages, matched and unmatched videos,
|
|
129
138
|
duplicates, ignored files and warnings. Fixtures in tests are synthetic files
|
|
130
139
|
with the same naming; no real download is ever copied into the repository.
|
|
@@ -221,10 +221,17 @@ the same way: an unknown key anywhere is `manifest_unknown_key` (with its path i
|
|
|
221
221
|
once), `role` (required) one of `teacher-guide`, `slides`, `learner-pages`,
|
|
222
222
|
`assessment`, `assessment-guidance`, `handout`, `printable`, `answer-key`,
|
|
223
223
|
`attachment` (`manifest_role` otherwise; a video is a reference, never a file),
|
|
224
|
-
optional `audience` and `
|
|
224
|
+
optional `audience`, `title` and (lesson members only) `step`. The audience is the role default
|
|
225
225
|
(`LESSON_ASSET_DEFAULT_AUDIENCE`) unless stated; `answer-key` and
|
|
226
226
|
`assessment-guidance` are always guardian (a learner audience is
|
|
227
227
|
`manifest_audience`). Members not listed stay recorded evidence with no role.
|
|
228
|
+
- **Lesson steps** (SMRT #4656): a lesson member or video may declare
|
|
229
|
+
`step: { key, order, label? }` (a closed object; `manifest_invalid` for a key over
|
|
230
|
+
32 characters or outside `A-Za-z0-9._:-`, a non-finite or non-number `order`
|
|
231
|
+
(|order| up to 1,000,000), or an empty label; `manifest_unknown_key` for anything
|
|
232
|
+
else, and a course member cannot carry one). Materials that share a `key` are
|
|
233
|
+
one step; fractions order `1b` after `1`. A video's `step` is still accepted as a
|
|
234
|
+
whole number (its rank); the object form also ranks it by `floor(order)`.
|
|
228
235
|
- **Pages**: `{ member, from, to, label? }` ranges over a member of the same lesson
|
|
229
236
|
(PDF, Word or PowerPoint; the rendered PDF for Office). Each page becomes a
|
|
230
237
|
`LessonPage` keyed `p<n>`, labelled `<label> <n>` (default `Page <n>`), ordered by
|
|
@@ -232,7 +239,7 @@ the same way: an unknown key anywhere is `manifest_unknown_key` (with its path i
|
|
|
232
239
|
500 pages per lesson.
|
|
233
240
|
- **Videos**: `{ reference, title, step, audience? }` where `reference` is a bare id
|
|
234
241
|
or an allowlisted https YouTube URL (`parseYouTubeReference`; anything else is
|
|
235
|
-
`manifest_video`), `step` an integer 0-9999. They become `LessonExternalVideo`
|
|
242
|
+
`manifest_video`), `step` an integer 0-9999 or a lesson step object (see above). They become `LessonExternalVideo`
|
|
236
243
|
rows; nothing is downloaded.
|
|
237
244
|
- **Course members** become `CourseAsset` rows, not tied to any lesson.
|
|
238
245
|
- **Unit and parts** (SMRT #4632): `course.key` (a manifest key) names the unit
|
|
@@ -260,7 +267,15 @@ manifest gave none or the member backs several materials with different titles.
|
|
|
260
267
|
row), the same for the original and rendered halves of a pair. Lesson and course
|
|
261
268
|
material views show the row title and fall back to the stored asset's name when
|
|
262
269
|
there is none. A re-attach fills a missing title; a different one is a
|
|
263
|
-
`conflict` unless `supersede` restates it. **
|
|
270
|
+
`conflict` unless `supersede` restates it. **Lesson steps** (#4656): the declared
|
|
271
|
+
step is recorded on the `lesson_assets` row (`step_key`, `step_order`, `step_label`;
|
|
272
|
+
both halves of an original/rendered pair share it) and on `lesson_external_videos`
|
|
273
|
+
(`step_key`, `step_position`, `step_label`; its existing integer `step_order` stays
|
|
274
|
+
the rank, `floor(order)` for an object step). All are additive nullable columns.
|
|
275
|
+
A re-attach fills a row's missing step without a conflict; a different step is a
|
|
276
|
+
`conflict` unless `supersede` restates it (replays are unchanged). Hosts read it
|
|
277
|
+
with `SubmissionService.listLessonSteps` ([lesson materials](lesson-materials.md)).
|
|
278
|
+
**Slides** keep the original
|
|
264
279
|
deck (hand `bytes` to the host as a private asset: "Open in PowerPoint") and its
|
|
265
280
|
rendered pages (`rendered`: "Present").
|
|
266
281
|
|
|
@@ -44,7 +44,7 @@ catalog projection without answer keys (every learner-facing catalog read) appli
|
|
|
44
44
|
the same rule. `signLessonMaterial`/`signCourseMaterial` sign only a file in the
|
|
45
45
|
actor's own list (claim scope `lesson:<id>` / `course:<id>`); a guessed id of
|
|
46
46
|
guardian material is denied; `thumbnail: true` signs the thumbnail of a visible
|
|
47
|
-
material instead. `signLessonPrintable`
|
|
47
|
+
material instead. `signLessonPrintable` signs a lesson file for printing: a `printable`, `handout` or `learner-pages` row (handout and learner pages only when the stored asset is `application/pdf`), learner-audience for a learner and any audience for a guardian. A retired row, an `original` rendition (the editable file) and any other role, answer keys included, are refused (SMRT #4657). Storage URIs never appear.
|
|
48
48
|
|
|
49
49
|
`setMaterialAudience({enrollmentId, scope, kind, materialId, audience})` lets a
|
|
50
50
|
guardian with plan authority change a family-owned row within the role's bounds
|
|
@@ -54,6 +54,29 @@ family-owned, so this is the after-attach path; before attach use
|
|
|
54
54
|
Authored courses change audience through course authoring (`CourseMaterialInput.audience`,
|
|
55
55
|
carried to the released join); shared or other families' rows are denied.
|
|
56
56
|
|
|
57
|
+
## Lesson steps (SMRT #4656)
|
|
58
|
+
|
|
59
|
+
`listLessonSteps({enrollmentId, lessonId})` walks a multi-step lesson in order,
|
|
60
|
+
filtered like `listLessonMaterials` (same actor and enrollment checks, learner
|
|
61
|
+
audience only for a learner, retired rows left out, same `LessonMaterialView`s):
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
{ setup: LessonMaterialView[]; // stepless printable / handout / learner-pages files
|
|
65
|
+
steps: Array<{ key: string; label: string; // label falls back to the key
|
|
66
|
+
kind: 'present' | 'watch' | 'do' | 'check';
|
|
67
|
+
order: number; // 1b sorts after 1
|
|
68
|
+
materials: LessonMaterialView[] }> }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Steps come from the `step_key`/`step_order`/`step_label` recorded on lesson files and
|
|
72
|
+
(`step_position`) external videos by a course bundle ([manifest steps](course-bundle-import.md)).
|
|
73
|
+
Materials sharing a key are one step (the original and rendered halves of a deck stay
|
|
74
|
+
together); steps sort by order then key; `kind` is `present` if any material is
|
|
75
|
+
`slides`, else `watch` if any is an external video, else `check` if any is an
|
|
76
|
+
`assessment`, else `do`. A lesson with no declared steps returns `steps: []`.
|
|
77
|
+
Course-level materials (supply lists) are not part of a lesson listing and stay with
|
|
78
|
+
`listCourseMaterials`.
|
|
79
|
+
|
|
57
80
|
## External videos
|
|
58
81
|
|
|
59
82
|
`LessonExternalVideo` references a video at an allowlisted provider (YouTube only):
|
package/dist/chunks/{workbook-photo-service-wqZZWGLb.js → workbook-photo-service-DEb2LSC0.js}
RENAMED
|
@@ -637,6 +637,14 @@ function materialAudience(relationship, audience) {
|
|
|
637
637
|
return effective;
|
|
638
638
|
}
|
|
639
639
|
var MATERIAL_RENDITIONS = ["original", "rendered"];
|
|
640
|
+
var LESSON_STEP_KEY_MAX = 32;
|
|
641
|
+
var LESSON_STEP_LABEL_MAX = 200;
|
|
642
|
+
var LESSON_STEP_ORDER_MAX = 1e6;
|
|
643
|
+
function checkStep(key, rawOrder, label) {
|
|
644
|
+
if (key === null && rawOrder === null && label === null) return;
|
|
645
|
+
const order = rawOrder === null ? null : Number(rawOrder);
|
|
646
|
+
if (typeof key !== "string" || !key.trim() || key.length > 32 || typeof order !== "number" || !Number.isFinite(order) || Math.abs(order) > 1e6 || label !== null && (typeof label !== "string" || label.length > 200)) throw new Error("Invalid lesson step");
|
|
647
|
+
}
|
|
640
648
|
async function requirePrivateAsset(row, assetId = row.assetId) {
|
|
641
649
|
const asset = await (await AssetCollection.create({ db: row.db })).get(assetId, { cache: false });
|
|
642
650
|
if (!row.tenantId || !asset || asset.tenantId !== row.tenantId || asset.accessMode !== "private" || !asset.ownerProfileId) throw new Error("Private lesson material requires an authorized private asset");
|
|
@@ -681,6 +689,9 @@ var LessonAsset = class extends SmrtObject {
|
|
|
681
689
|
title = null;
|
|
682
690
|
retiredAt = null;
|
|
683
691
|
sourceImportId = null;
|
|
692
|
+
stepKey = null;
|
|
693
|
+
stepOrder = null;
|
|
694
|
+
stepLabel = null;
|
|
684
695
|
constructor(options = {}) {
|
|
685
696
|
super(options);
|
|
686
697
|
for (const [key, value] of Object.entries(options)) if (Object.hasOwn(this, key)) this[key] = value;
|
|
@@ -688,6 +699,7 @@ var LessonAsset = class extends SmrtObject {
|
|
|
688
699
|
async save(options) {
|
|
689
700
|
materialAudience(this.relationship, this.audience);
|
|
690
701
|
if (this.title !== null && (typeof this.title !== "string" || this.title.length > 200)) throw new Error("Invalid lesson material title");
|
|
702
|
+
checkStep(this.stepKey, this.stepOrder, this.stepLabel);
|
|
691
703
|
const lesson = await this.db.get("lessons", { id: this.lessonId });
|
|
692
704
|
if (!lesson) throw new Error(`education lesson ${this.lessonId} does not exist`);
|
|
693
705
|
if (!await this.db.get("lessons", {
|
|
@@ -725,6 +737,18 @@ __decorateClass$10([field({
|
|
|
725
737
|
type: "text",
|
|
726
738
|
nullable: true
|
|
727
739
|
})], LessonAsset.prototype, "sourceImportId", 2);
|
|
740
|
+
__decorateClass$10([field({
|
|
741
|
+
type: "text",
|
|
742
|
+
nullable: true
|
|
743
|
+
})], LessonAsset.prototype, "stepKey", 2);
|
|
744
|
+
__decorateClass$10([field({
|
|
745
|
+
type: "decimal",
|
|
746
|
+
nullable: true
|
|
747
|
+
})], LessonAsset.prototype, "stepOrder", 2);
|
|
748
|
+
__decorateClass$10([field({
|
|
749
|
+
type: "text",
|
|
750
|
+
nullable: true
|
|
751
|
+
})], LessonAsset.prototype, "stepLabel", 2);
|
|
728
752
|
LessonAsset = __decorateClass$10([TenantScoped({ mode: "optional" }), smrt({
|
|
729
753
|
tableName: "lesson_assets",
|
|
730
754
|
conflictColumns: [
|
|
@@ -840,6 +864,9 @@ var LessonExternalVideo = class extends SmrtObject {
|
|
|
840
864
|
audience = "";
|
|
841
865
|
retiredAt = null;
|
|
842
866
|
sourceImportId = null;
|
|
867
|
+
stepKey = null;
|
|
868
|
+
stepPosition = null;
|
|
869
|
+
stepLabel = null;
|
|
843
870
|
constructor(options = {}) {
|
|
844
871
|
super(options);
|
|
845
872
|
for (const [key, value] of Object.entries(options)) if (Object.hasOwn(this, key)) this[key] = value;
|
|
@@ -847,6 +874,7 @@ var LessonExternalVideo = class extends SmrtObject {
|
|
|
847
874
|
async save(options) {
|
|
848
875
|
materialAudience("video", this.audience);
|
|
849
876
|
if (!EXTERNAL_VIDEO_PROVIDERS.includes(this.provider) || !YOUTUBE_ID.test(this.videoId) || typeof this.title !== "string" || !this.title.trim() || this.title.length > 200 || !Number.isSafeInteger(this.stepOrder) || this.stepOrder < 0) throw new Error("Invalid external video reference");
|
|
877
|
+
checkStep(this.stepKey, this.stepPosition, this.stepLabel);
|
|
850
878
|
if (!await this.db.get("lessons", {
|
|
851
879
|
id: this.lessonId,
|
|
852
880
|
tenant_id: this.tenantId
|
|
@@ -865,6 +893,18 @@ __decorateClass$10([field({
|
|
|
865
893
|
type: "text",
|
|
866
894
|
nullable: true
|
|
867
895
|
})], LessonExternalVideo.prototype, "sourceImportId", 2);
|
|
896
|
+
__decorateClass$10([field({
|
|
897
|
+
type: "text",
|
|
898
|
+
nullable: true
|
|
899
|
+
})], LessonExternalVideo.prototype, "stepKey", 2);
|
|
900
|
+
__decorateClass$10([field({
|
|
901
|
+
type: "decimal",
|
|
902
|
+
nullable: true
|
|
903
|
+
})], LessonExternalVideo.prototype, "stepPosition", 2);
|
|
904
|
+
__decorateClass$10([field({
|
|
905
|
+
type: "text",
|
|
906
|
+
nullable: true
|
|
907
|
+
})], LessonExternalVideo.prototype, "stepLabel", 2);
|
|
868
908
|
LessonExternalVideo = __decorateClass$10([TenantScoped({ mode: "optional" }), smrt({
|
|
869
909
|
tableName: "lesson_external_videos",
|
|
870
910
|
conflictColumns: [
|
|
@@ -32467,6 +32507,21 @@ var ExternalVideoReferenceError = class extends LearnerValidationError {
|
|
|
32467
32507
|
this.name = "ExternalVideoReferenceError";
|
|
32468
32508
|
}
|
|
32469
32509
|
};
|
|
32510
|
+
var PRINTABLE_RELATIONSHIPS = [
|
|
32511
|
+
"printable",
|
|
32512
|
+
"handout",
|
|
32513
|
+
"learner-pages"
|
|
32514
|
+
];
|
|
32515
|
+
var stepOfRow = (row, orderField) => row.step_key ? {
|
|
32516
|
+
key: String(row.step_key),
|
|
32517
|
+
order: Number(row[orderField]) || 0,
|
|
32518
|
+
label: row.step_label ? String(row.step_label) : ""
|
|
32519
|
+
} : null;
|
|
32520
|
+
var SETUP_RELATIONSHIPS = /* @__PURE__ */ new Set([
|
|
32521
|
+
"printable",
|
|
32522
|
+
"handout",
|
|
32523
|
+
"learner-pages"
|
|
32524
|
+
]);
|
|
32470
32525
|
function pairRenditions(rows, views) {
|
|
32471
32526
|
const out = views.map((v) => ({ ...v }));
|
|
32472
32527
|
rows.forEach((row, i) => {
|
|
@@ -33333,6 +33388,9 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
33333
33388
|
* learner-audience material; keys and guidance never reach them.
|
|
33334
33389
|
*/
|
|
33335
33390
|
async listLessonMaterials(request) {
|
|
33391
|
+
return (await this.collectLessonMaterials(request)).map((m) => m.view);
|
|
33392
|
+
}
|
|
33393
|
+
async collectLessonMaterials(request) {
|
|
33336
33394
|
const enrollment = await this.requireEnrollment(request, "education.learner.read");
|
|
33337
33395
|
if (!await this.lessonInScope(request, enrollment, request.lessonId)) throw new LearnerAuthorizationError();
|
|
33338
33396
|
const lesson = await this.db.get("lessons", { id: request.lessonId });
|
|
@@ -33340,28 +33398,72 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
33340
33398
|
const tenant = lesson.tenant_id ?? null;
|
|
33341
33399
|
const learnerOnly = await this.learnerAudienceOnly(request);
|
|
33342
33400
|
const files = (await this.db.list("lesson_assets", { lesson_id: request.lessonId })).filter((row) => (row.tenant_id ?? null) === tenant && !row.retired_at && this.visibleTo(learnerOnly, String(row.relationship), String(row.audience ?? ""))).sort((a, b) => Number(a.sort_order) - Number(b.sort_order) || String(a.id).localeCompare(String(b.id)));
|
|
33343
|
-
const videos = await this.db.tableExists("lesson_external_videos") ? (await this.db.list("lesson_external_videos", { lesson_id: request.lessonId })).filter((row) => (row.tenant_id ?? null) === tenant && !row.retired_at && this.visibleTo(learnerOnly, "video", String(row.audience ?? ""))).sort((a, b) => Number(a.step_order) - Number(b.step_order) || String(a.id).localeCompare(String(b.id))) : [];
|
|
33401
|
+
const videos = await this.db.tableExists("lesson_external_videos") ? (await this.db.list("lesson_external_videos", { lesson_id: request.lessonId })).filter((row) => (row.tenant_id ?? null) === tenant && !row.retired_at && this.visibleTo(learnerOnly, "video", String(row.audience ?? ""))).sort((a, b) => Number(a.step_order) - Number(b.step_order) || (Number(a.step_position ?? 0) || 0) - (Number(b.step_position ?? 0) || 0) || String(a.id).localeCompare(String(b.id))) : [];
|
|
33344
33402
|
const fileViews = [];
|
|
33345
33403
|
for (const row of files) fileViews.push(await this.fileView(row, "lesson"));
|
|
33346
33404
|
const result = pairRenditions(files, fileViews);
|
|
33347
|
-
|
|
33348
|
-
|
|
33349
|
-
|
|
33350
|
-
|
|
33351
|
-
|
|
33352
|
-
|
|
33353
|
-
|
|
33354
|
-
|
|
33355
|
-
|
|
33356
|
-
|
|
33357
|
-
|
|
33358
|
-
|
|
33359
|
-
|
|
33360
|
-
|
|
33361
|
-
|
|
33362
|
-
|
|
33363
|
-
|
|
33364
|
-
|
|
33405
|
+
const steps = files.map((row) => stepOfRow(row, "step_order"));
|
|
33406
|
+
for (const row of videos) {
|
|
33407
|
+
steps.push(stepOfRow(row, "step_position"));
|
|
33408
|
+
result.push({
|
|
33409
|
+
id: String(row.id),
|
|
33410
|
+
kind: "external-video",
|
|
33411
|
+
scope: "lesson",
|
|
33412
|
+
relationship: "video",
|
|
33413
|
+
audience: materialAudience("video", String(row.audience ?? "")),
|
|
33414
|
+
title: String(row.title),
|
|
33415
|
+
order: Number(row.step_order) || 0,
|
|
33416
|
+
assetId: null,
|
|
33417
|
+
provider: row.provider,
|
|
33418
|
+
videoId: String(row.video_id),
|
|
33419
|
+
embedUrl: externalVideoEmbedUrl(row.provider, String(row.video_id)),
|
|
33420
|
+
mimeType: null,
|
|
33421
|
+
rendition: null,
|
|
33422
|
+
pairedMaterialId: null,
|
|
33423
|
+
thumbnailAssetId: null
|
|
33424
|
+
});
|
|
33425
|
+
}
|
|
33426
|
+
return result.map((view, i) => ({
|
|
33427
|
+
view,
|
|
33428
|
+
step: steps[i] ?? null
|
|
33429
|
+
}));
|
|
33430
|
+
}
|
|
33431
|
+
/**
|
|
33432
|
+
* A lesson walked in order (SMRT #4656), audience-filtered exactly like
|
|
33433
|
+
* `listLessonMaterials`: stepless handouts, learner pages and printables as
|
|
33434
|
+
* `setup`, then the declared steps. A step groups every material sharing its
|
|
33435
|
+
* key (the original and rendered halves of a deck stay together). A lesson
|
|
33436
|
+
* with no declared steps returns `steps: []`. Course-level supply lists are
|
|
33437
|
+
* not part of a lesson's listing and stay with `listCourseMaterials`.
|
|
33438
|
+
*/
|
|
33439
|
+
async listLessonSteps(request) {
|
|
33440
|
+
const collected = await this.collectLessonMaterials(request);
|
|
33441
|
+
const setup = collected.filter((m) => !m.step && m.view.kind === "file" && SETUP_RELATIONSHIPS.has(m.view.relationship)).map((m) => m.view);
|
|
33442
|
+
const groups = /* @__PURE__ */ new Map();
|
|
33443
|
+
for (const { view, step } of collected) {
|
|
33444
|
+
if (!step) continue;
|
|
33445
|
+
const group = groups.get(step.key) ?? {
|
|
33446
|
+
key: step.key,
|
|
33447
|
+
order: step.order,
|
|
33448
|
+
label: "",
|
|
33449
|
+
materials: []
|
|
33450
|
+
};
|
|
33451
|
+
group.order = Math.min(group.order, step.order);
|
|
33452
|
+
group.label = group.label || step.label;
|
|
33453
|
+
group.materials.push(view);
|
|
33454
|
+
groups.set(step.key, group);
|
|
33455
|
+
}
|
|
33456
|
+
const kindOf = (materials) => materials.some((m) => m.relationship === "slides") ? "present" : materials.some((m) => m.kind === "external-video") ? "watch" : materials.some((m) => m.relationship === "assessment") ? "check" : "do";
|
|
33457
|
+
return {
|
|
33458
|
+
setup,
|
|
33459
|
+
steps: [...groups.values()].sort((a, b) => a.order - b.order || (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)).map((g) => ({
|
|
33460
|
+
key: g.key,
|
|
33461
|
+
label: g.label || g.key,
|
|
33462
|
+
kind: kindOf(g.materials),
|
|
33463
|
+
order: g.order,
|
|
33464
|
+
materials: g.materials
|
|
33465
|
+
}))
|
|
33466
|
+
};
|
|
33365
33467
|
}
|
|
33366
33468
|
/** Course-level materials (supply lists, posters) under the same audience rule. */
|
|
33367
33469
|
async listCourseMaterials(request) {
|
|
@@ -33531,16 +33633,30 @@ var SubmissionService = class SubmissionService extends LearnerService {
|
|
|
33531
33633
|
return view;
|
|
33532
33634
|
});
|
|
33533
33635
|
}
|
|
33636
|
+
/**
|
|
33637
|
+
* Sign a lesson file for printing: a `printable`, `handout` or `learner-pages`
|
|
33638
|
+
* PDF the actor may see (learner-audience, or any for a guardian). Never an
|
|
33639
|
+
* editable `original` rendition and never an answer key.
|
|
33640
|
+
*/
|
|
33534
33641
|
async signLessonPrintable(request) {
|
|
33535
33642
|
const enrollment = await this.requireEnrollment(request, "education.learner.read");
|
|
33536
33643
|
if (!await this.lessonInScope(request, enrollment, request.lessonId)) throw new LearnerAuthorizationError();
|
|
33537
|
-
|
|
33538
|
-
|
|
33539
|
-
|
|
33540
|
-
|
|
33541
|
-
|
|
33542
|
-
|
|
33543
|
-
|
|
33644
|
+
if (!this.options.signAsset) throw new LearnerAuthorizationError();
|
|
33645
|
+
const links = await LessonAssetCollection.create({ db: this.db });
|
|
33646
|
+
let link = null;
|
|
33647
|
+
for (const relationship of PRINTABLE_RELATIONSHIPS) {
|
|
33648
|
+
link = await links.get({
|
|
33649
|
+
tenantId: request.tenantId,
|
|
33650
|
+
lessonId: request.lessonId,
|
|
33651
|
+
assetId: request.assetId,
|
|
33652
|
+
relationship
|
|
33653
|
+
}, { cache: false });
|
|
33654
|
+
if (link?.retiredAt) link = null;
|
|
33655
|
+
if (link) break;
|
|
33656
|
+
}
|
|
33657
|
+
if (!link) throw new LearnerAuthorizationError();
|
|
33658
|
+
if (link.rendition === "original") throw new LearnerAuthorizationError();
|
|
33659
|
+
if (link.relationship !== "printable" && (await this.materialAsset(request.assetId)).mimeType !== "application/pdf") throw new LearnerAuthorizationError();
|
|
33544
33660
|
if (await this.learnerAudienceOnly(request) && materialAudience(link.relationship, link.audience) !== "learner") throw new LearnerAuthorizationError();
|
|
33545
33661
|
const expiresAt = new Date(Date.now() + 300 * 1e3);
|
|
33546
33662
|
return {
|
|
@@ -36974,6 +37090,6 @@ var WorkbookPhotoService = class WorkbookPhotoService extends LessonWorkService
|
|
|
36974
37090
|
}
|
|
36975
37091
|
};
|
|
36976
37092
|
//#endregion
|
|
36977
|
-
export { LessonAlreadyCompleteError as $,
|
|
37093
|
+
export { LessonAlreadyCompleteError as $, externalVideoEmbedUrl as $i, QUESTION_COMPLETION_MODES as $n, ReleasedLesson as $r, WorkFeedbackRelease as $t, QUESTION_EXTRACTION_VERSION as A, Attestation as Ai, QuestionRevision as An, CourseCoverChangeCollection as Ar, WorkbookPhotoDecision as At, SELF_ATTESTABLE_LESSON_KINDS as B, CourseAsset as Bi, normalizeQuestionContent as Bn, CourseUpgradeChoiceCollection as Br, WorkCompletionDecision as Bt, MAX_EXTRACTED_OMISSIONS as C, ReadingCollection as Ci, LearningObjectiveCollection as Cn, MAX_REFUSAL_EXPLANATION_LENGTH as Cr, canonicalGroupSharingPolicy as Ct, MAX_EXTRACTED_STUDENT_WORK as D, SubmissionIntakeBindingCollection as Di, QuestionCollection as Dn, CourseArchiveDecision as Dr, BookContextRevisionCollection as Dt, MAX_EXTRACTED_SECTIONS as E, SubmissionCollection as Ei, Question as En, readDelegation as Er, BookContextRevision as Et, normalizeWorkObservedLayout as F, Submission as Fi, resolveEnrollmentLessons as Fn, CourseRelease as Fr, FamilyIntakeAsset as Ft, EnrollmentNotActiveError as G, LESSON_ASSET_RELATIONSHIPS as Gi, MAX_PRINTABLE_ITEMS as Gn, EnrollmentAssignmentRecord as Gr, LessonWorkFiling as Gt, SubmissionStorageCleanupError as H, EXTERNAL_VIDEO_PROVIDERS as Hi, GENERATED_PRINTABLE_RELATIONSHIPS as Hn, CourseVisibilityChangeCollection as Hr, readWorkCompletion as Ht, normalizeWorkAnswerRegions as I, SubmissionAsset as Ii, applyProposalFields as In, CourseReleaseCollection as Ir, FamilyIntakeAssetCollection as It, InterestThemeError as J, LESSON_STEP_ORDER_MAX as Ji, MAX_PRINTABLE_TOTAL_TEXT as Jn, EnrollmentAutoUpgradeSkipCollection as Jr, WorkAssessmentRevisionCollection as Jt, EnrollmentPaceError as K, LESSON_STEP_KEY_MAX as Ki, MAX_PRINTABLE_PAGES as Kn, EnrollmentAssignmentRecordCollection as Kr, LessonWorkFilingCollection as Kt, LessonWorkWorkflowRequiredError as L, SubmissionIntakeBinding as Li, draftFromRevision as Ln, CourseRevision as Lr, FamilyIntakeCollection as Lt, QuestionExtractionPendingError as M, Grade as Mi, CourseAuthoringService as Mn, CourseDiscardTombstoneCollection as Mr, WorkbookPhotoIntake as Mt, QuestionExtractionValidationError as N, LessonSelfAttestation as Ni, authoredVisibilityPermitsGroups as Nn, CourseDraftHead as Nr, WorkbookPhotoIntakeCollection as Nt, MAX_EXTRACTED_TEXT as O, VideoCollection as Oi, QuestionResponse as On, CourseArchiveDecisionCollection as Or, WorkbookPhotoAsset as Ot, LessonWorkService as P, ScannedSubmission as Pi, effectiveCourseVisibility as Pn, CourseDraftHeadCollection as Pr, FamilyIntake as Pt, LearnerValidationError as Q, MATERIAL_RENDITIONS as Qi, PrintableStorageNotConfiguredError as Qn, LessonRevisionCollection as Qr, WorkFeedbackDeliveryCollection as Qt, OwnAttestationConflictError as R, parseQuestionRubric as Ri, planProposal as Rn, CourseRevisionCollection as Rr, FamilyIntakeDecision as Rt, MAX_EXTRACTED_ANCHORS as S, LessonSelfAttestationCollection as Si, LearningObjective as Sn, MAX_REFUSAL_CATEGORY_LENGTH as Sr, GroupSharingPolicyValidationError as St, MAX_EXTRACTED_QUESTIONS as T, SubmissionAssetCollection as Ti, LearningObjectiveRevisionCollection as Tn, delegationRecordFields as Tr, familyIntakeScopeId as Tt, submissionPageKey as U, GUARDIAN_ONLY_RELATIONSHIPS as Ui, LearningObjectiveKeyConflictError as Un, CurriculumCreationRecord as Ur, LessonActivity as Ut, SubmissionService as V, CourseAssetCollection as Vi, GENERATED_PRINTABLE_AUDIENCES as Vn, CourseVisibilityChange as Vr, WorkCompletionDecisionCollection as Vt, ENROLLMENT_STATUS_TRANSITIONS as W, LESSON_ASSET_DEFAULT_AUDIENCE as Wi, LearningObjectiveNotFoundError as Wn, CurriculumCreationRecordCollection as Wr, LessonActivityCollection as Wt, LearnerHistoryUnavailableError as X, LessonExternalVideo as Xi, PrintableOwnerError as Xn, EnrollmentReleasePinCollection as Xr, WorkAttemptCollection as Xt, LearnerAuthorizationError as Y, LessonAsset as Yi, MAX_RESOLVE_QUESTION_REVISIONS as Yn, EnrollmentReleasePin as Yr, WorkAttempt as Yt, LearnerService as Z, LessonExternalVideoCollection as Zi, PrintableStorageCleanupError as Zn, LessonRevision as Zr, WorkFeedbackDelivery as Zt, EXTRACTED_OMISSION_REASONS as _, Reading as _a, GradeCollection as _i, renderGuardianWorksheetPdf as _n, CourseProposalFieldError as _r, LessonProgressTransitionCollection as _t, questionExtractionProposalDigest as a, LessonProgress as aa, mostRestrictiveLicense as ai, aggregateWorkQuestions as an, QuestionReferenceError as ar, MIN_LESSONS_PER_WEEK as at, EXTRACTED_QUESTION_STATUSES as b, LessonCollection as bi, GeneratedPrintable as bn, MAX_GENERATION_GRADE as br, GroupSharingAuthorizationError as bt, QuestionExtractionRecord as c, interestThemeFromColumns as ca, AttestationCollection as ci, LessonPage as cn, COURSE_GENERATION_STATUSES as cr, EnrollmentPaceChange as ct, extractedQuestionContent as d, COURSE_VISIBILITIES as da, CourseSectionCollection as di, validLessonPageKey as dn, CourseGenerationBriefError as dr, EnrollmentStatusChangeCollection as dt, materialAudience as ea, ReleasedLessonCollection as ei, WorkFeedbackReleaseCollection as en, QUESTION_FIELDS as er, LessonPutBackError as et, matchExtractedQuestions as f, Course as fa, CurriculumCollection as fi, QuestionAuthoringService as fn, CourseGenerationInProgressError as fr, LearnerInterestThemeChange as ft, EXTRACTED_MEDIA_KINDS as g, Lesson as ga, ExternalCourseCollection as gi, worksheetVersionId as gn, CourseGeneratorNotConfiguredError as gr, LessonProgressTransition as gt, EXTRACTED_LEGIBILITY as h, ExternalCourse as ha, ExternalCompletionCollection as hi, canonical$2 as hn, CourseGenerationRefusal as hr, LessonCompletionRecordCollection as ht, QuestionExtractionService as i, Learner as ia, isGroupShareableLicense as ii, WorkCompletionConflictError as in, QuestionNotFoundError as ir, MAX_NEXT_LESSONS_COUNT as it, QuestionExtractionNotFoundError as j, ExternalCompletion as ji, QuestionRevisionCollection as jn, CourseDiscardTombstone as jr, WorkbookPhotoDecisionCollection as jt, QUESTION_EXTRACTION_SOURCE_KINDS as k, WorksheetCollection as ki, QuestionResponseCollection as kn, CourseCoverChange as kr, WorkbookPhotoAssetCollection as kt, QuestionExtractionRecordCollection as l, parseInterestTheme as la, AuthoredCourseCollection as li, LessonPageCollection as ln, COURSE_GENERATION_TARGETS as lr, EnrollmentPaceChangeCollection as lt, questionExtractionSourceIdentity as m, Exercise as ma, ExerciseCollection as mi, buildLearnerWorksheet as mn, CourseGenerationOutputError as mr, LessonCompletionRecord as mt, preservePhotoProposal as n, Curriculum as na, AUTHORED_RELEASE_SOURCE_KEY as ni, WORK_MATH_VOCABULARY as nn, QUESTION_PROVENANCES as nr, LessonWorkOwnedError as nt, ExtractedQuestionLink as o, MAX_INTEREST_THEME_KEY_CHARS as oa, CatalogAuthorizationError as oi, validateWorkAssessment as on, WorksheetVersionMismatchError as or, enrollmentView as ot, normalizeQuestionExtraction as p, CourseSection as pa, EnrollmentCollection as pi, buildGuardianWorksheet as pn, CourseGenerationNotFoundError as pr, LearnerInterestThemeChangeCollection as pt, EnrollmentStatusTransitionError as q, LESSON_STEP_LABEL_MAX as qi, MAX_PRINTABLE_TEXT_LENGTH as qn, EnrollmentAutoUpgradeSkip as qr, WorkAssessmentRevision as qt, validateWorkbookPhotoIdentification as r, Enrollment as ra, isAuthoredReleaseGroupShareable as ri, WorkAssessmentValidationError as rn, QUESTION_RESPONSE_TYPES as rr, MAX_LESSONS_PER_WEEK as rt, ExtractedQuestionLinkCollection as s, MAX_INTEREST_THEME_LABEL_CHARS as sa, CatalogService as si, workAggregateFeedback as sn, groupSharingDigest as sr, requirePolicyBasis as st, WorkbookPhotoService as t, parseYouTubeReference as ta, AUTHORED_IDENTITY_SOURCE_KEY as ti, LessonWorkPlacementError as tn, QUESTION_PROMPT_SUMMARY_LENGTH as tr, LessonSkippedError as tt, QUESTION_EXTRACTION_ARGS_SCHEMA as u, AuthoredCourse as ua, CourseCollection as ui, lessonPageView as un, COURSE_GENERATION_TERMINAL as ur, EnrollmentStatusChange as ut, EXTRACTED_ORIGINS as v, Video as va, LearnerCollection as vi, renderLearnerWorksheetPdf as vn, CourseProposalNotFoundError as vr, evaluateGroupSharingPolicy as vt, MAX_EXTRACTED_PAGES as w, ScannedSubmissionCollection as wi, LearningObjectiveRevision as wn, delegationActorFields as wr, validateGroupSharingPolicy as wt, EXTRACTED_READABILITY as x, LessonProgressCollection as xi, GeneratedPrintableCollection as xn, MAX_GENERATION_OBJECTIVE_CHARS as xr, GroupSharingConflictError as xt, EXTRACTED_PAGE_ROLES as y, Worksheet as ya, LessonAssetCollection as yi, es_exports as yn, MAX_GENERATION_BRIEF_FIELD_CHARS as yr, summarizeWorkEvidence as yt, OwnAttestationLessonKindError as z, validateRubricResults as zi, validateCourseGenerationOutput as zn, CourseUpgradeChoice as zr, FamilyIntakeDecisionCollection as zt };
|
|
36978
37094
|
|
|
36979
|
-
//# sourceMappingURL=workbook-photo-service-
|
|
37095
|
+
//# sourceMappingURL=workbook-photo-service-DEb2LSC0.js.map
|