@things-factory/learning 10.1.260 → 10.1.261
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/dist-client/components/learning-material-form.d.ts +41 -0
- package/dist-client/components/learning-material-form.js +202 -0
- package/dist-client/components/learning-material-form.js.map +1 -0
- package/dist-client/components/learning-unit-form.d.ts +3 -0
- package/dist-client/components/learning-unit-form.js +10 -2
- package/dist-client/components/learning-unit-form.js.map +1 -1
- package/dist-client/pages/learning-course-editor.d.ts +1 -0
- package/dist-client/pages/learning-course-editor.js +19 -6
- package/dist-client/pages/learning-course-editor.js.map +1 -1
- package/dist-client/pages/learning-course-list.d.ts +1 -1
- package/dist-client/pages/learning-course-list.js +6 -2
- package/dist-client/pages/learning-course-list.js.map +1 -1
- package/dist-client/pages/learning-enrollment-status.d.ts +28 -16
- package/dist-client/pages/learning-enrollment-status.js +95 -112
- package/dist-client/pages/learning-enrollment-status.js.map +1 -1
- package/dist-client/pages/learning-view.d.ts +11 -0
- package/dist-client/pages/learning-view.js +94 -19
- package/dist-client/pages/learning-view.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/utils/learning-api.d.ts +48 -3
- package/dist-client/utils/learning-api.js +51 -2
- package/dist-client/utils/learning-api.js.map +1 -1
- package/dist-client/utils/material-source.d.ts +12 -0
- package/dist-client/utils/material-source.js +77 -0
- package/dist-client/utils/material-source.js.map +1 -0
- package/dist-server/ai-reads.js +1 -1
- package/dist-server/ai-reads.js.map +1 -1
- package/dist-server/service/course/course-mutation.d.ts +1 -1
- package/dist-server/service/course/course-mutation.js +7 -6
- package/dist-server/service/course/course-mutation.js.map +1 -1
- package/dist-server/service/course/course-query.d.ts +1 -0
- package/dist-server/service/course/course-query.js +14 -3
- package/dist-server/service/course/course-query.js.map +1 -1
- package/dist-server/service/course/course-rules.d.ts +3 -1
- package/dist-server/service/course/course-rules.js +5 -1
- package/dist-server/service/course/course-rules.js.map +1 -1
- package/dist-server/service/course/course-service.d.ts +5 -4
- package/dist-server/service/course/course-service.js +10 -14
- package/dist-server/service/course/course-service.js.map +1 -1
- package/dist-server/service/course/course-type.d.ts +1 -0
- package/dist-server/service/course/course-type.js +4 -0
- package/dist-server/service/course/course-type.js.map +1 -1
- package/dist-server/service/course/course-unit.d.ts +3 -2
- package/dist-server/service/course/course-unit.js +7 -2
- package/dist-server/service/course/course-unit.js.map +1 -1
- package/dist-server/service/course/index.d.ts +2 -1
- package/dist-server/service/course/index.js +2 -1
- package/dist-server/service/course/index.js.map +1 -1
- package/dist-server/service/course/material-audio.d.ts +1 -0
- package/dist-server/service/course/material-audio.js +43 -0
- package/dist-server/service/course/material-audio.js.map +1 -0
- package/dist-server/service/course/material-draft-mutation.d.ts +13 -0
- package/dist-server/service/course/material-draft-mutation.js +55 -0
- package/dist-server/service/course/material-draft-mutation.js.map +1 -0
- package/dist-server/service/course/material-draft-rules.d.ts +38 -0
- package/dist-server/service/course/material-draft-rules.js +83 -0
- package/dist-server/service/course/material-draft-rules.js.map +1 -0
- package/dist-server/service/course/material-draft-service.d.ts +15 -0
- package/dist-server/service/course/material-draft-service.js +162 -0
- package/dist-server/service/course/material-draft-service.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-query.d.ts +5 -4
- package/dist-server/service/enrollment/enrollment-query.js +26 -35
- package/dist-server/service/enrollment/enrollment-query.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-service.js +13 -3
- package/dist-server/service/enrollment/enrollment-service.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-status.d.ts +31 -18
- package/dist-server/service/enrollment/enrollment-status.js +101 -42
- package/dist-server/service/enrollment/enrollment-status.js.map +1 -1
- package/dist-server/service/enrollment/enrollment-type.d.ts +1 -14
- package/dist-server/service/enrollment/enrollment-type.js +6 -54
- package/dist-server/service/enrollment/enrollment-type.js.map +1 -1
- package/dist-server/service/enrollment/enrollment.d.ts +1 -0
- package/dist-server/service/enrollment/enrollment.js +5 -0
- package/dist-server/service/enrollment/enrollment.js.map +1 -1
- package/dist-server/service/enrollment/learning-ai.d.ts +9 -2
- package/dist-server/service/enrollment/learning-ai.js +25 -28
- package/dist-server/service/enrollment/learning-ai.js.map +1 -1
- package/dist-server/service/enrollment/learning-documents.js +3 -4
- package/dist-server/service/enrollment/learning-documents.js.map +1 -1
- package/dist-server/service/enrollment/progress-rules.d.ts +36 -11
- package/dist-server/service/enrollment/progress-rules.js +47 -12
- package/dist-server/service/enrollment/progress-rules.js.map +1 -1
- package/dist-server/service/enrollment/unit-progress.d.ts +4 -0
- package/dist-server/service/enrollment/unit-progress.js +20 -0
- package/dist-server/service/enrollment/unit-progress.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -13
- package/translations/en.json +30 -7
- package/translations/es.json +30 -7
- package/translations/ja.json +30 -7
- package/translations/ko.json +30 -7
- package/translations/ms.json +30 -7
- package/translations/zh.json +30 -7
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
export declare const UNIT_FIELDS = "id seq kind name body url durationSeconds minSeconds file { id name fullpath mimetype }";
|
|
2
|
-
|
|
1
|
+
export declare const UNIT_FIELDS = "id seq kind name body url durationSeconds pageCount secondsPerPage minSeconds file { id name fullpath mimetype }";
|
|
2
|
+
/** One unit's progress as the server keeps it - where it stands and the time it acknowledged. */
|
|
3
|
+
export declare const PROGRESS_FIELDS = "learnedSeconds watchedSeconds positionSeconds pagesSeen pagesRead pageSeconds pageAt doneAt";
|
|
4
|
+
export declare const LEARNING_FIELDS = "\n id status state dueOn startedAt learnedSeconds score attempts attemptsLeft quizSize missing acknowledgedAt completedAt certificateNo\n course { id name description category }\n version { id number minSeconds acknowledgement passScore questionCount attemptLimit }\n units { counts unit { id seq kind name body url durationSeconds pageCount secondsPerPage minSeconds file { id name fullpath mimetype } } progress { learnedSeconds watchedSeconds positionSeconds pagesSeen pagesRead pageSeconds pageAt doneAt } }\n lastUnitId\n";
|
|
3
5
|
export declare const MY_LIST_FIELDS = "\n id status state dueOn completedAt certificateNo\n course { id name category }\n units { counts progress { doneAt } }\n";
|
|
4
|
-
export declare const VERSION_FIELDS = "\n id number status minSeconds acknowledgement passScore questionCount attemptLimit sourceKind sourceNote publishedAt closedAt\n units { id seq kind name body url durationSeconds minSeconds file { id name fullpath mimetype } }\n questions { id seq kind text explanation drafted choices { id text correct } }\n";
|
|
6
|
+
export declare const VERSION_FIELDS = "\n id number status minSeconds acknowledgement passScore questionCount attemptLimit sourceKind sourceNote publishedAt closedAt\n units { id seq kind name body url durationSeconds pageCount secondsPerPage minSeconds file { id name fullpath mimetype } }\n questions { id seq kind text explanation drafted choices { id text correct } }\n";
|
|
5
7
|
export declare const COURSE_FIELDS = "\n id name description category updatedAt\n manager { id name }\n publishedVersion { id number }\n versions { id number status publishedAt closedAt }\n";
|
|
6
8
|
export declare const STATUS_ROW_FIELDS = "\n id state departmentName units unitsDone versionNumber\n user { id name email }\n course { id name }\n enrollment { id status dueOn sourceKind startedAt completedAt learnedSeconds score attempts certificateNo cancelReason }\n";
|
|
7
9
|
/** Units done of those that count, from an enrolment's units. */
|
|
@@ -14,3 +16,46 @@ export declare function unitsDoneOf(units?: {
|
|
|
14
16
|
done: number;
|
|
15
17
|
total: number;
|
|
16
18
|
};
|
|
19
|
+
type UnitRow = {
|
|
20
|
+
counts: boolean;
|
|
21
|
+
unit: {
|
|
22
|
+
id: string;
|
|
23
|
+
};
|
|
24
|
+
progress?: {
|
|
25
|
+
doneAt?: string | null;
|
|
26
|
+
} | null;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The unit the course opens on: the one learned last, on any device; else the first that counts and is not done; else
|
|
30
|
+
* the first. Opening on the first not done sent a learner back from where they were (2026-11-08).
|
|
31
|
+
*/
|
|
32
|
+
export declare function resumeUnitId(enrollment: {
|
|
33
|
+
lastUnitId?: string | null;
|
|
34
|
+
units?: UnitRow[];
|
|
35
|
+
} | null | undefined): string;
|
|
36
|
+
/**
|
|
37
|
+
* The seconds the page in reading was looked at already, as the server acknowledged them - a learner coming back to the
|
|
38
|
+
* page carries on from there. They belong to the furthest page reached, and only while it is the page left on.
|
|
39
|
+
*/
|
|
40
|
+
export declare function pageLookedOf(progress: {
|
|
41
|
+
pagesSeen?: number | null;
|
|
42
|
+
pageAt?: number | null;
|
|
43
|
+
pageSeconds?: number | null;
|
|
44
|
+
} | null | undefined): number;
|
|
45
|
+
/**
|
|
46
|
+
* The progress a course screen starts each lesson from (2026-11-08). The server's answer to each tick is kept by unit;
|
|
47
|
+
* a lesson opened - the same unit coming back too - starts from the newest of that and what the page read. Taken once
|
|
48
|
+
* per lesson shown: the lesson counts its own time on from there, and a newer value given while it plays would count
|
|
49
|
+
* that time twice. `reopen` lets the next lesson shown take the newest again.
|
|
50
|
+
*/
|
|
51
|
+
export declare class LessonStarts {
|
|
52
|
+
private saved;
|
|
53
|
+
private starts;
|
|
54
|
+
/** The server's answer for a unit of an enrolment. */
|
|
55
|
+
keep(enrollmentId: string, unitId: string, progress: any): void;
|
|
56
|
+
/** Where the lesson of a unit starts - fixed until `reopen`. */
|
|
57
|
+
startOf(enrollmentId: string, unitId: string, read: any): any;
|
|
58
|
+
/** The lesson shown has left - the next one starts from the newest answer. */
|
|
59
|
+
reopen(): void;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/* The fields the screens of this module read - one place, so a page and a popup ask for the same shape. */
|
|
2
|
-
export const UNIT_FIELDS = `id seq kind name body url durationSeconds minSeconds file { id name fullpath mimetype }`;
|
|
2
|
+
export const UNIT_FIELDS = `id seq kind name body url durationSeconds pageCount secondsPerPage minSeconds file { id name fullpath mimetype }`;
|
|
3
|
+
/** One unit's progress as the server keeps it - where it stands and the time it acknowledged. */
|
|
4
|
+
export const PROGRESS_FIELDS = `learnedSeconds watchedSeconds positionSeconds pagesSeen pagesRead pageSeconds pageAt doneAt`;
|
|
3
5
|
export const LEARNING_FIELDS = `
|
|
4
6
|
id status state dueOn startedAt learnedSeconds score attempts attemptsLeft quizSize missing acknowledgedAt completedAt certificateNo
|
|
5
7
|
course { id name description category }
|
|
6
8
|
version { id number minSeconds acknowledgement passScore questionCount attemptLimit }
|
|
7
|
-
units { counts unit { ${UNIT_FIELDS} } progress {
|
|
9
|
+
units { counts unit { ${UNIT_FIELDS} } progress { ${PROGRESS_FIELDS} } }
|
|
10
|
+
lastUnitId
|
|
8
11
|
`;
|
|
9
12
|
export const MY_LIST_FIELDS = `
|
|
10
13
|
id status state dueOn completedAt certificateNo
|
|
@@ -33,4 +36,50 @@ export function unitsDoneOf(units = []) {
|
|
|
33
36
|
const counted = units.filter(one => one.counts);
|
|
34
37
|
return { done: counted.filter(one => one.progress?.doneAt).length, total: counted.length };
|
|
35
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* The unit the course opens on: the one learned last, on any device; else the first that counts and is not done; else
|
|
41
|
+
* the first. Opening on the first not done sent a learner back from where they were (2026-11-08).
|
|
42
|
+
*/
|
|
43
|
+
export function resumeUnitId(enrollment) {
|
|
44
|
+
const units = enrollment?.units ?? [];
|
|
45
|
+
const last = units.find(row => row.unit.id === enrollment?.lastUnitId);
|
|
46
|
+
return (last ?? units.find(row => row.counts && !row.progress?.doneAt) ?? units[0])?.unit.id ?? '';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The seconds the page in reading was looked at already, as the server acknowledged them - a learner coming back to the
|
|
50
|
+
* page carries on from there. They belong to the furthest page reached, and only while it is the page left on.
|
|
51
|
+
*/
|
|
52
|
+
export function pageLookedOf(progress) {
|
|
53
|
+
if (!progress?.pageAt || progress.pageAt !== progress.pagesSeen)
|
|
54
|
+
return 0;
|
|
55
|
+
return progress.pageSeconds ?? 0;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The progress a course screen starts each lesson from (2026-11-08). The server's answer to each tick is kept by unit;
|
|
59
|
+
* a lesson opened - the same unit coming back too - starts from the newest of that and what the page read. Taken once
|
|
60
|
+
* per lesson shown: the lesson counts its own time on from there, and a newer value given while it plays would count
|
|
61
|
+
* that time twice. `reopen` lets the next lesson shown take the newest again.
|
|
62
|
+
*/
|
|
63
|
+
export class LessonStarts {
|
|
64
|
+
constructor() {
|
|
65
|
+
this.saved = new Map();
|
|
66
|
+
this.starts = new Map();
|
|
67
|
+
}
|
|
68
|
+
/** The server's answer for a unit of an enrolment. */
|
|
69
|
+
keep(enrollmentId, unitId, progress) {
|
|
70
|
+
if (progress)
|
|
71
|
+
this.saved.set(`${enrollmentId}:${unitId}`, progress);
|
|
72
|
+
}
|
|
73
|
+
/** Where the lesson of a unit starts - fixed until `reopen`. */
|
|
74
|
+
startOf(enrollmentId, unitId, read) {
|
|
75
|
+
const key = `${enrollmentId}:${unitId}`;
|
|
76
|
+
if (!this.starts.has(key))
|
|
77
|
+
this.starts.set(key, this.saved.get(key) ?? read ?? {});
|
|
78
|
+
return this.starts.get(key);
|
|
79
|
+
}
|
|
80
|
+
/** The lesson shown has left - the next one starts from the newest answer. */
|
|
81
|
+
reopen() {
|
|
82
|
+
this.starts.clear();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
36
85
|
//# sourceMappingURL=learning-api.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"learning-api.js","sourceRoot":"","sources":["../../client/utils/learning-api.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAE3G,MAAM,CAAC,MAAM,WAAW,GAAG,
|
|
1
|
+
{"version":3,"file":"learning-api.js","sourceRoot":"","sources":["../../client/utils/learning-api.ts"],"names":[],"mappings":"AAAA,2GAA2G;AAE3G,MAAM,CAAC,MAAM,WAAW,GAAG,kHAAkH,CAAA;AAE7I,iGAAiG;AACjG,MAAM,CAAC,MAAM,eAAe,GAAG,6FAA6F,CAAA;AAE5H,MAAM,CAAC,MAAM,eAAe,GAAG;;;;0BAIL,WAAW,iBAAiB,eAAe;;CAEpE,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG;;;;CAI7B,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG;;YAElB,WAAW;;CAEtB,CAAA;AAED,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;CAK5B,CAAA;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;CAKhC,CAAA;AAED,iEAAiE;AACjE,MAAM,UAAU,WAAW,CAAC,QAA6E,EAAE;IACzG,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC/C,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;AAC5F,CAAC;AAID;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,UAAgF;IAC3G,MAAM,KAAK,GAAG,UAAU,EAAE,KAAK,IAAI,EAAE,CAAA;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,UAAU,EAAE,UAAU,CAAC,CAAA;IACtE,OAAO,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,CAAA;AACpG,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,QAA+G;IAC1I,IAAI,CAAC,QAAQ,EAAE,MAAM,IAAI,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,SAAS;QAAE,OAAO,CAAC,CAAA;IACzE,OAAO,QAAQ,CAAC,WAAW,IAAI,CAAC,CAAA;AAClC,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,YAAY;IAAzB;QACU,UAAK,GAAG,IAAI,GAAG,EAAe,CAAA;QAC9B,WAAM,GAAG,IAAI,GAAG,EAAe,CAAA;IAkBzC,CAAC;IAhBC,sDAAsD;IACtD,IAAI,CAAC,YAAoB,EAAE,MAAc,EAAE,QAAa;QACtD,IAAI,QAAQ;YAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,YAAY,IAAI,MAAM,EAAE,EAAE,QAAQ,CAAC,CAAA;IACrE,CAAC;IAED,gEAAgE;IAChE,OAAO,CAAC,YAAoB,EAAE,MAAc,EAAE,IAAS;QACrD,MAAM,GAAG,GAAG,GAAG,YAAY,IAAI,MAAM,EAAE,CAAA;QACvC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC,CAAA;QAClF,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC7B,CAAC;IAED,8EAA8E;IAC9E,MAAM;QACJ,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAA;IACrB,CAAC;CACF","sourcesContent":["/* The fields the screens of this module read - one place, so a page and a popup ask for the same shape. */\n\nexport const UNIT_FIELDS = `id seq kind name body url durationSeconds pageCount secondsPerPage minSeconds file { id name fullpath mimetype }`\n\n/** One unit's progress as the server keeps it - where it stands and the time it acknowledged. */\nexport const PROGRESS_FIELDS = `learnedSeconds watchedSeconds positionSeconds pagesSeen pagesRead pageSeconds pageAt doneAt`\n\nexport const LEARNING_FIELDS = `\n id status state dueOn startedAt learnedSeconds score attempts attemptsLeft quizSize missing acknowledgedAt completedAt certificateNo\n course { id name description category }\n version { id number minSeconds acknowledgement passScore questionCount attemptLimit }\n units { counts unit { ${UNIT_FIELDS} } progress { ${PROGRESS_FIELDS} } }\n lastUnitId\n`\n\nexport const MY_LIST_FIELDS = `\n id status state dueOn completedAt certificateNo\n course { id name category }\n units { counts progress { doneAt } }\n`\n\nexport const VERSION_FIELDS = `\n id number status minSeconds acknowledgement passScore questionCount attemptLimit sourceKind sourceNote publishedAt closedAt\n units { ${UNIT_FIELDS} }\n questions { id seq kind text explanation drafted choices { id text correct } }\n`\n\nexport const COURSE_FIELDS = `\n id name description category updatedAt\n manager { id name }\n publishedVersion { id number }\n versions { id number status publishedAt closedAt }\n`\n\nexport const STATUS_ROW_FIELDS = `\n id state departmentName units unitsDone versionNumber\n user { id name email }\n course { id name }\n enrollment { id status dueOn sourceKind startedAt completedAt learnedSeconds score attempts certificateNo cancelReason }\n`\n\n/** Units done of those that count, from an enrolment's units. */\nexport function unitsDoneOf(units: { counts: boolean; progress?: { doneAt?: string | null } | null }[] = []) {\n const counted = units.filter(one => one.counts)\n return { done: counted.filter(one => one.progress?.doneAt).length, total: counted.length }\n}\n\ntype UnitRow = { counts: boolean; unit: { id: string }; progress?: { doneAt?: string | null } | null }\n\n/**\n * The unit the course opens on: the one learned last, on any device; else the first that counts and is not done; else\n * the first. Opening on the first not done sent a learner back from where they were (2026-11-08).\n */\nexport function resumeUnitId(enrollment: { lastUnitId?: string | null; units?: UnitRow[] } | null | undefined): string {\n const units = enrollment?.units ?? []\n const last = units.find(row => row.unit.id === enrollment?.lastUnitId)\n return (last ?? units.find(row => row.counts && !row.progress?.doneAt) ?? units[0])?.unit.id ?? ''\n}\n\n/**\n * The seconds the page in reading was looked at already, as the server acknowledged them - a learner coming back to the\n * page carries on from there. They belong to the furthest page reached, and only while it is the page left on.\n */\nexport function pageLookedOf(progress: { pagesSeen?: number | null; pageAt?: number | null; pageSeconds?: number | null } | null | undefined): number {\n if (!progress?.pageAt || progress.pageAt !== progress.pagesSeen) return 0\n return progress.pageSeconds ?? 0\n}\n\n/**\n * The progress a course screen starts each lesson from (2026-11-08). The server's answer to each tick is kept by unit;\n * a lesson opened - the same unit coming back too - starts from the newest of that and what the page read. Taken once\n * per lesson shown: the lesson counts its own time on from there, and a newer value given while it plays would count\n * that time twice. `reopen` lets the next lesson shown take the newest again.\n */\nexport class LessonStarts {\n private saved = new Map<string, any>()\n private starts = new Map<string, any>()\n\n /** The server's answer for a unit of an enrolment. */\n keep(enrollmentId: string, unitId: string, progress: any) {\n if (progress) this.saved.set(`${enrollmentId}:${unitId}`, progress)\n }\n\n /** Where the lesson of a unit starts - fixed until `reopen`. */\n startOf(enrollmentId: string, unitId: string, read: any): any {\n const key = `${enrollmentId}:${unitId}`\n if (!this.starts.has(key)) this.starts.set(key, this.saved.get(key) ?? read ?? {})\n return this.starts.get(key)\n }\n\n /** The lesson shown has left - the next one starts from the newest answer. */\n reopen() {\n this.starts.clear()\n }\n}\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type MaterialPart = {
|
|
2
|
+
id: string;
|
|
3
|
+
text?: string;
|
|
4
|
+
image?: string;
|
|
5
|
+
};
|
|
6
|
+
export type MaterialSource = {
|
|
7
|
+
fileId: string;
|
|
8
|
+
parts: MaterialPart[];
|
|
9
|
+
};
|
|
10
|
+
/** Complete captions, never a guessed transcript from the video's name or a few sampled frames. */
|
|
11
|
+
export declare function captionParts(text: string): MaterialPart[];
|
|
12
|
+
export declare function pdfMaterial(fileId: string, url: string, signal: AbortSignal): Promise<MaterialSource>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { drawPdfPage, loadPdfjs, isScannedPage, pdfPageText } from '@things-factory/ai-assistant/dist-client/index.js';
|
|
2
|
+
/** Complete captions, never a guessed transcript from the video's name or a few sampled frames. */
|
|
3
|
+
export function captionParts(text) {
|
|
4
|
+
const clean = text.replace(/^\uFEFF/, '').trim();
|
|
5
|
+
if (!clean || clean.length > 120_000)
|
|
6
|
+
throw new Error('learning-ai-source-limit');
|
|
7
|
+
// Keep timestamps and cue order as evidence. Plain transcripts are also accepted, explicitly supplied by the manager.
|
|
8
|
+
const blocks = clean.split(/\r?\n\s*\r?\n/).filter(s => s.trim() && s.trim() !== 'WEBVTT');
|
|
9
|
+
const parts = [];
|
|
10
|
+
let current = '';
|
|
11
|
+
for (const block of blocks) {
|
|
12
|
+
if (current && current.length + block.length > 8000) {
|
|
13
|
+
parts.push({ id: `caption-${parts.length + 1}`, text: current });
|
|
14
|
+
current = '';
|
|
15
|
+
}
|
|
16
|
+
current += (current ? '\n\n' : '') + block;
|
|
17
|
+
}
|
|
18
|
+
if (current)
|
|
19
|
+
parts.push({ id: `caption-${parts.length + 1}`, text: current });
|
|
20
|
+
if (!parts.length || parts.length > 80)
|
|
21
|
+
throw new Error('learning-ai-source-limit');
|
|
22
|
+
return parts;
|
|
23
|
+
}
|
|
24
|
+
const blobData = (blob) => new Promise((resolve, reject) => {
|
|
25
|
+
const reader = new FileReader();
|
|
26
|
+
reader.onload = () => resolve(String(reader.result));
|
|
27
|
+
reader.onerror = () => reject(reader.error);
|
|
28
|
+
reader.readAsDataURL(blob);
|
|
29
|
+
});
|
|
30
|
+
export async function pdfMaterial(fileId, url, signal) {
|
|
31
|
+
const response = await fetch(url, { credentials: 'include', signal });
|
|
32
|
+
if (!response.ok)
|
|
33
|
+
throw new Error('learning-ai-source-read');
|
|
34
|
+
const blob = await response.blob();
|
|
35
|
+
if (blob.size > 50 * 1024 * 1024)
|
|
36
|
+
throw new Error('learning-ai-source-limit');
|
|
37
|
+
const pdfjs = await loadPdfjs();
|
|
38
|
+
const loading = pdfjs.getDocument({ data: new Uint8Array(await blob.arrayBuffer()) });
|
|
39
|
+
const abort = () => { void loading.destroy(); };
|
|
40
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
41
|
+
try {
|
|
42
|
+
if (signal.aborted)
|
|
43
|
+
throw new Error('learning-ai-source-read');
|
|
44
|
+
const pdf = await loading.promise;
|
|
45
|
+
if (!pdf.numPages || pdf.numPages > 80)
|
|
46
|
+
throw new Error('learning-ai-source-limit');
|
|
47
|
+
const parts = [];
|
|
48
|
+
let chars = 0, scans = 0, imageChars = 0;
|
|
49
|
+
for (let n = 1; n <= pdf.numPages; n++) {
|
|
50
|
+
if (signal.aborted)
|
|
51
|
+
throw new Error('learning-ai-source-read');
|
|
52
|
+
const page = await pdf.getPage(n);
|
|
53
|
+
const text = pdfPageText((await page.getTextContent()).items);
|
|
54
|
+
if (isScannedPage(text)) {
|
|
55
|
+
if (++scans > 12)
|
|
56
|
+
throw new Error('learning-ai-source-limit');
|
|
57
|
+
const image = await blobData(await drawPdfPage(pdf, n));
|
|
58
|
+
imageChars += image.length;
|
|
59
|
+
if (imageChars > 8_000_000)
|
|
60
|
+
throw new Error('learning-ai-source-limit');
|
|
61
|
+
parts.push({ id: `page-${n}`, image });
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
chars += text.length;
|
|
65
|
+
if (chars > 120_000)
|
|
66
|
+
throw new Error('learning-ai-source-limit');
|
|
67
|
+
parts.push({ id: `page-${n}`, text });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { fileId, parts };
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
signal.removeEventListener('abort', abort);
|
|
74
|
+
await loading.destroy();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=material-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"material-source.js","sourceRoot":"","sources":["../../client/utils/material-source.ts"],"names":[],"mappings":"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","sourcesContent":["import { drawPdfPage, loadPdfjs, isScannedPage, pdfPageText } from '@things-factory/ai-assistant/dist-client/index.js'\n\nexport type MaterialPart = { id: string; text?: string; image?: string }\nexport type MaterialSource = { fileId: string; parts: MaterialPart[] }\n\n/** Complete captions, never a guessed transcript from the video's name or a few sampled frames. */\nexport function captionParts(text: string): MaterialPart[] {\n const clean = text.replace(/^\\uFEFF/, '').trim()\n if (!clean || clean.length > 120_000) throw new Error('learning-ai-source-limit')\n // Keep timestamps and cue order as evidence. Plain transcripts are also accepted, explicitly supplied by the manager.\n const blocks = clean.split(/\\r?\\n\\s*\\r?\\n/).filter(s => s.trim() && s.trim() !== 'WEBVTT')\n const parts: MaterialPart[] = []\n let current = ''\n for (const block of blocks) {\n if (current && current.length + block.length > 8000) { parts.push({ id: `caption-${parts.length + 1}`, text: current }); current = '' }\n current += (current ? '\\n\\n' : '') + block\n }\n if (current) parts.push({ id: `caption-${parts.length + 1}`, text: current })\n if (!parts.length || parts.length > 80) throw new Error('learning-ai-source-limit')\n return parts\n}\n\nconst blobData = (blob: Blob): Promise<string> => new Promise((resolve, reject) => {\n const reader = new FileReader()\n reader.onload = () => resolve(String(reader.result))\n reader.onerror = () => reject(reader.error)\n reader.readAsDataURL(blob)\n})\n\nexport async function pdfMaterial(fileId: string, url: string, signal: AbortSignal): Promise<MaterialSource> {\n const response = await fetch(url, { credentials: 'include', signal })\n if (!response.ok) throw new Error('learning-ai-source-read')\n const blob = await response.blob()\n if (blob.size > 50 * 1024 * 1024) throw new Error('learning-ai-source-limit')\n const pdfjs = await loadPdfjs()\n const loading = pdfjs.getDocument({ data: new Uint8Array(await blob.arrayBuffer()) })\n const abort = () => { void loading.destroy() }\n signal.addEventListener('abort', abort, { once: true })\n try {\n if (signal.aborted) throw new Error('learning-ai-source-read')\n const pdf = await loading.promise\n if (!pdf.numPages || pdf.numPages > 80) throw new Error('learning-ai-source-limit')\n const parts: MaterialPart[] = []\n let chars = 0, scans = 0, imageChars = 0\n for (let n = 1; n <= pdf.numPages; n++) {\n if (signal.aborted) throw new Error('learning-ai-source-read')\n const page = await pdf.getPage(n)\n const text = pdfPageText((await page.getTextContent()).items)\n if (isScannedPage(text)) {\n if (++scans > 12) throw new Error('learning-ai-source-limit')\n const image = await blobData(await drawPdfPage(pdf, n))\n imageChars += image.length\n if (imageChars > 8_000_000) throw new Error('learning-ai-source-limit')\n parts.push({ id: `page-${n}`, image })\n } else {\n chars += text.length\n if (chars > 120_000) throw new Error('learning-ai-source-limit')\n parts.push({ id: `page-${n}`, text })\n }\n }\n return { fileId, parts }\n } finally { signal.removeEventListener('abort', abort); await loading.destroy() }\n}\n"]}
|
package/dist-server/ai-reads.js
CHANGED
|
@@ -25,7 +25,7 @@ exports.LEARNING_ENROLLMENTS_READ = {
|
|
|
25
25
|
{ name: 'unitsDone', type: 'number', labelKey: 'field.learning-units-done' }
|
|
26
26
|
],
|
|
27
27
|
status: { field: 'state', values: ['ASSIGNED', 'IN_PROGRESS', 'COMPLETED', 'OVERDUE'] },
|
|
28
|
-
search: "learner's name
|
|
28
|
+
search: "learner's name or course",
|
|
29
29
|
route: 'learning-enrollment-status'
|
|
30
30
|
};
|
|
31
31
|
exports.LEARNING_CONTENT_READ = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-reads.js","sourceRoot":"","sources":["../server/ai-reads.ts"],"names":[],"mappings":";;;AA+DA,sDAIC;AAnED;;;GAGG;AACH,+DAA4K;AAE5K,kEAAgE;AAEnD,QAAA,yBAAyB,GAAa;IACjD,GAAG,EAAE,sBAAsB;IAC3B,KAAK,EAAE,kCAAkC;IACzC,IAAI,EAAE,4RAA4R;IAClS,SAAS,EAAE,qBAAqB;IAChC,SAAS,EAAE,6FAA6F;IACxG,OAAO,EAAE;QACP,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,wBAAwB,EAAE;QACvE,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,2BAA2B,EAAE;QACjF,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,4BAA4B,EAAE;QAC1E,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,sBAAsB,EAAE;QACnE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,uBAAuB,EAAE;QAClE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,0BAA0B,EAAE;QAC1E,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,6BAA6B,EAAE;QAC9E,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,2BAA2B,EAAE;KAC7E;IACD,MAAM,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE;IACvF,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"ai-reads.js","sourceRoot":"","sources":["../server/ai-reads.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * The assistant's reads and entry of learning (ADR-0194 decision 12) - registered at load; an application names the keys\n * in its assistant mode's `readTargets`.\n */\nimport { listReadTarget, registerDetailRead, registerEntryTarget, registerReadTarget, type DetailRead, type EntryTarget, type ListRead } from '@things-factory/ai-assistant'\n\nimport { choicesOfText } from './service/enrollment/learning-ai'\n\nexport const LEARNING_ENROLLMENTS_READ: ListRead = {\n key: 'learning.enrollments',\n title: 'title.learning-enrollment-status',\n name: 'Course enrolments (수강) - who is to take which course by when and where each stands: ASSIGNED (not started), IN_PROGRESS, COMPLETED or OVERDUE (open past its due day), with the days left to the due day. A manager reads everyone, a department head their people, anyone else their own.',\n listQuery: 'learningEnrollments',\n selection: 'id learner departmentName course state dueOn daysLeft startedAt completedAt unitsDone units',\n columns: [\n { name: 'learner', type: 'string', labelKey: 'field.learning-learner' },\n { name: 'departmentName', type: 'string', labelKey: 'field.learning-department' },\n { name: 'course', type: 'string', labelKey: 'field.learning-course-name' },\n { name: 'state', type: 'string', labelKey: 'field.learning-state' },\n { name: 'dueOn', type: 'date', labelKey: 'field.learning-due-on' },\n { name: 'daysLeft', type: 'number', labelKey: 'field.learning-days-left' },\n { name: 'completedAt', type: 'date', labelKey: 'field.learning-completed-on' },\n { name: 'unitsDone', type: 'number', labelKey: 'field.learning-units-done' }\n ],\n status: { field: 'state', values: ['ASSIGNED', 'IN_PROGRESS', 'COMPLETED', 'OVERDUE'] },\n search: \"learner's name or course\",\n route: 'learning-enrollment-status'\n}\n\nexport const LEARNING_CONTENT_READ: DetailRead = {\n key: 'learning.course-content',\n labelKey: 'title.learning-units',\n description:\n \"The units of one course version - names and texts - to answer a question on the course the person is taking (give their enrollmentId; only that course's version, refused while their quiz attempt is open - do not help with the quiz) or, for a manager, to draft questions of a version (give versionId). Answer only from these units.\",\n query: 'query ($enrollmentId: String, $versionId: String) { learningContent(enrollmentId: $enrollmentId, versionId: $versionId) { course version units { seq kind name text } } }',\n args: [\n { name: 'enrollmentId', type: 'string', axis: 'enrollment', description: 'The enrolment open on the course screen (learning-view?id=...)' },\n { name: 'versionId', type: 'string', axis: 'course-version', description: 'A version open in the course editor - managers only' }\n ]\n}\n\nexport const QUESTION_DRAFT_TARGET: EntryTarget = {\n key: 'learning.question-draft',\n labelKey: 'title.learning-questions',\n description:\n 'A quiz question drafted from the units of a DRAFT course version (read learning.course-content with its versionId first). It is entered marked as an AI draft; a manager reviews and saves it before the version can be published. Only a draft version takes it.',\n fields: [\n { name: 'versionId', type: 'string', required: true, axis: 'course-version', description: 'The draft version the question belongs to', labelKey: 'title.learning-versions' },\n { name: 'kind', type: 'enum', required: true, values: ['SINGLE', 'MULTIPLE', 'TRUE_FALSE'], valueKeyPrefix: 'text.learning-question-', labelKey: 'field.learning-question-kind' },\n { name: 'text', type: 'text', required: true, description: 'What it asks - from the units only', labelKey: 'field.learning-question-text' },\n { name: 'choices', type: 'text', required: true, description: 'One choice a line; start each right choice with *. TRUE_FALSE has two lines.', labelKey: 'field.learning-question-choices' },\n { name: 'explanation', type: 'text', description: 'Why the answer is right, citing the unit', labelKey: 'field.learning-question-explanation' }\n ],\n mutation: 'mutation ($versionId: String!, $question: CourseQuestionInput!) { addDraftedCourseQuestion(versionId: $versionId, question: $question) { id } }',\n variables: row => ({\n versionId: row.values.versionId,\n question: { kind: row.values.kind, text: row.values.text, choices: choicesOfText(String(row.values.choices ?? '')), explanation: row.values.explanation ?? null }\n }),\n appliedOf: (data, row) => ({ id: data?.addDraftedCourseQuestion?.id, label: String(row.values.text ?? '').slice(0, 60) }),\n screen: 'learning-course-editor',\n maxRows: 30\n}\n\nexport function registerLearningReads(): void {\n registerReadTarget(listReadTarget(LEARNING_ENROLLMENTS_READ))\n registerDetailRead(LEARNING_CONTENT_READ)\n registerEntryTarget(QUESTION_DRAFT_TARGET)\n}\n\n/** The keys an application's assistant mode names to read learning. */\nexport const LEARNING_READ_TARGETS = [LEARNING_ENROLLMENTS_READ.key, LEARNING_CONTENT_READ.key]\n\n/** The keys an application's assistant mode names to let the assistant enter learning records. */\nexport const LEARNING_ENTRY_TARGETS = [QUESTION_DRAFT_TARGET.key]\n"]}
|
|
@@ -10,7 +10,7 @@ export declare class CourseMutation {
|
|
|
10
10
|
createCourseVersion(courseId: string, fromId: string, context: ResolverContext): Promise<CourseVersion>;
|
|
11
11
|
updateCourseVersion(id: string, patch: CourseVersionPatch, context: ResolverContext): Promise<CourseVersion>;
|
|
12
12
|
saveCourseUnits(versionId: string, units: CourseUnitInput[], context: ResolverContext): Promise<CourseUnit[]>;
|
|
13
|
-
attachCourseUnitFile(unitId: string, file: FileUpload, durationSeconds: number, context: ResolverContext): Promise<CourseUnit>;
|
|
13
|
+
attachCourseUnitFile(unitId: string, file: FileUpload, durationSeconds: number, pageCount: number, context: ResolverContext): Promise<CourseUnit>;
|
|
14
14
|
saveCourseQuestions(versionId: string, questions: CourseQuestionInput[], context: ResolverContext): Promise<CourseQuestion[]>;
|
|
15
15
|
addDraftedCourseQuestion(versionId: string, question: CourseQuestionInput, context: ResolverContext): Promise<CourseQuestion>;
|
|
16
16
|
publishCourseVersion(id: string, context: ResolverContext): Promise<CourseVersion>;
|
|
@@ -28,8 +28,8 @@ let CourseMutation = class CourseMutation {
|
|
|
28
28
|
async saveCourseUnits(versionId, units, context) {
|
|
29
29
|
return await (0, course_service_1.saveCourseUnits)(versionId, units, context);
|
|
30
30
|
}
|
|
31
|
-
async attachCourseUnitFile(unitId, file, durationSeconds, context) {
|
|
32
|
-
return await (0, course_service_1.attachCourseUnitFile)(unitId, file, durationSeconds, context);
|
|
31
|
+
async attachCourseUnitFile(unitId, file, durationSeconds, pageCount, context) {
|
|
32
|
+
return await (0, course_service_1.attachCourseUnitFile)(unitId, file, { durationSeconds, pageCount }, context);
|
|
33
33
|
}
|
|
34
34
|
async saveCourseQuestions(versionId, questions, context) {
|
|
35
35
|
return await (0, course_service_1.saveCourseQuestions)(versionId, questions, context);
|
|
@@ -108,13 +108,14 @@ tslib_1.__decorate([
|
|
|
108
108
|
tslib_1.__decorate([
|
|
109
109
|
(0, type_graphql_1.Directive)('@privilege(category: "course", privilege: "mutation", domainOwnerGranted: true)'),
|
|
110
110
|
(0, type_graphql_1.Directive)('@transaction'),
|
|
111
|
-
(0, type_graphql_1.Mutation)(returns => course_unit_1.CourseUnit, { description: "To attach a video's or a PDF's file to a unit of a draft version - a video with its length." }),
|
|
111
|
+
(0, type_graphql_1.Mutation)(returns => course_unit_1.CourseUnit, { description: "To attach a video's or a PDF's file to a unit of a draft version - a video with its length, a PDF with its page count." }),
|
|
112
112
|
tslib_1.__param(0, (0, type_graphql_1.Arg)('unitId')),
|
|
113
113
|
tslib_1.__param(1, (0, type_graphql_1.Arg)('file', type => GraphQLUpload_js_1.default)),
|
|
114
|
-
tslib_1.__param(2, (0, type_graphql_1.Arg)('durationSeconds', type => type_graphql_1.Int, { nullable: true })),
|
|
115
|
-
tslib_1.__param(3, (0, type_graphql_1.
|
|
114
|
+
tslib_1.__param(2, (0, type_graphql_1.Arg)('durationSeconds', type => type_graphql_1.Int, { nullable: true, description: "A video's length in seconds, read from the file." })),
|
|
115
|
+
tslib_1.__param(3, (0, type_graphql_1.Arg)('pageCount', type => type_graphql_1.Int, { nullable: true, description: "A PDF's page count, read from the file." })),
|
|
116
|
+
tslib_1.__param(4, (0, type_graphql_1.Ctx)()),
|
|
116
117
|
tslib_1.__metadata("design:type", Function),
|
|
117
|
-
tslib_1.__metadata("design:paramtypes", [String, Object, Number, Object]),
|
|
118
|
+
tslib_1.__metadata("design:paramtypes", [String, Object, Number, Number, Object]),
|
|
118
119
|
tslib_1.__metadata("design:returntype", Promise)
|
|
119
120
|
], CourseMutation.prototype, "attachCourseUnitFile", null);
|
|
120
121
|
tslib_1.__decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"course-mutation.js","sourceRoot":"","sources":["../../../server/service/course/course-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FileUpload } from 'graphql-upload/GraphQLUpload.js'\nimport GraphQLUpload from 'graphql-upload/GraphQLUpload.js'\nimport { Arg, Ctx, Directive, Int, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { Refusal, SpeakRefusals } from '@things-factory/shell'\n\nimport { Course } from './course'\nimport { CourseQuestion } from './course-question'\nimport { CourseUnit } from './course-unit'\nimport { CourseVersion } from './course-version'\nimport { CoursePatch, CourseQuestionInput, CourseUnitInput, CourseVersionPatch, NewCourse } from './course-type'\nimport {\n addDraftedQuestions,\n attachCourseUnitFile,\n closeCourseVersion,\n createCourse,\n createCourseVersion,\n deleteCourseVersion,\n publishCourseVersion,\n saveCourseQuestions,\n saveCourseUnits,\n updateCourse,\n updateCourseVersion\n} from './course-service'\n\n\n/* The managers' writes of courses (ADR-0194 decisions 2-4) - `course` mutation, the task `learning.managing`. */\n@Resolver(Course)\n@UseMiddleware(SpeakRefusals)\nexport class CourseMutation {\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Course, { description: 'To make a course.' })\n async createCourse(@Arg('course', type => NewCourse) course: NewCourse, @Ctx() context: ResolverContext): Promise<Course> {\n return await createCourse(course, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Course, { description: \"To change a course's name, description, category or manager.\" })\n async updateCourse(@Arg('id') id: string, @Arg('patch', type => CoursePatch) patch: CoursePatch, @Ctx() context: ResolverContext): Promise<Course> {\n return await updateCourse(id, patch, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseVersion, { description: 'To make the next draft version of a course - a copy of `fromId`, or of the latest version.' })\n async createCourseVersion(@Arg('courseId') courseId: string, @Arg('fromId', { nullable: true }) fromId: string, @Ctx() context: ResolverContext): Promise<CourseVersion> {\n return await createCourseVersion(courseId, fromId, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseVersion, { description: 'To change what a draft version says - a published one never changes.' })\n async updateCourseVersion(@Arg('id') id: string, @Arg('patch', type => CourseVersionPatch) patch: CourseVersionPatch, @Ctx() context: ResolverContext): Promise<CourseVersion> {\n return await updateCourseVersion(id, patch, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [CourseUnit], { description: 'To save the units of a draft version, in order - the ones left out are deleted.' })\n async saveCourseUnits(@Arg('versionId') versionId: string, @Arg('units', type => [CourseUnitInput]) units: CourseUnitInput[], @Ctx() context: ResolverContext): Promise<CourseUnit[]> {\n return await saveCourseUnits(versionId, units, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseUnit, { description: \"To attach a video's or a PDF's file to a unit of a draft version - a video with its length.\" })\n async attachCourseUnitFile(\n @Arg('unitId') unitId: string,\n @Arg('file', type => GraphQLUpload) file: FileUpload,\n @Arg('durationSeconds', type => Int, { nullable: true }) durationSeconds: number,\n @Ctx() context: ResolverContext\n ): Promise<CourseUnit> {\n return await attachCourseUnitFile(unitId, file, durationSeconds, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [CourseQuestion], { description: 'To save the questions of a draft version, in order - the ones left out are deleted.' })\n async saveCourseQuestions(@Arg('versionId') versionId: string, @Arg('questions', type => [CourseQuestionInput]) questions: CourseQuestionInput[], @Ctx() context: ResolverContext): Promise<CourseQuestion[]> {\n return await saveCourseQuestions(versionId, questions, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseQuestion, { description: 'To add a question drafted by the assistant to a draft version - marked drafted until a manager saves it (ADR-0194 decision 12).' })\n async addDraftedCourseQuestion(@Arg('versionId') versionId: string, @Arg('question', type => CourseQuestionInput) question: CourseQuestionInput, @Ctx() context: ResolverContext): Promise<CourseQuestion> {\n const [saved] = await addDraftedQuestions(versionId, [question], context)\n if (!saved) throw new Refusal('learning-question-draft-invalid', {}, 'the drafted question is not complete - its text, two choices and the right ones')\n return saved\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseVersion, { description: 'To publish a draft version - the version published before is closed.' })\n async publishCourseVersion(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<CourseVersion> {\n return await publishCourseVersion(id, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseVersion, { description: 'To close the published version - nobody starts it any more.' })\n async closeCourseVersion(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<CourseVersion> {\n return await closeCourseVersion(id, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: 'To delete a draft version.' })\n async deleteCourseVersion(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n return await deleteCourseVersion(id, context)\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"course-mutation.js","sourceRoot":"","sources":["../../../server/service/course/course-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { FileUpload } from 'graphql-upload/GraphQLUpload.js'\nimport GraphQLUpload from 'graphql-upload/GraphQLUpload.js'\nimport { Arg, Ctx, Directive, Int, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { Refusal, SpeakRefusals } from '@things-factory/shell'\n\nimport { Course } from './course'\nimport { CourseQuestion } from './course-question'\nimport { CourseUnit } from './course-unit'\nimport { CourseVersion } from './course-version'\nimport { CoursePatch, CourseQuestionInput, CourseUnitInput, CourseVersionPatch, NewCourse } from './course-type'\nimport {\n addDraftedQuestions,\n attachCourseUnitFile,\n closeCourseVersion,\n createCourse,\n createCourseVersion,\n deleteCourseVersion,\n publishCourseVersion,\n saveCourseQuestions,\n saveCourseUnits,\n updateCourse,\n updateCourseVersion\n} from './course-service'\n\n\n/* The managers' writes of courses (ADR-0194 decisions 2-4) - `course` mutation, the task `learning.managing`. */\n@Resolver(Course)\n@UseMiddleware(SpeakRefusals)\nexport class CourseMutation {\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Course, { description: 'To make a course.' })\n async createCourse(@Arg('course', type => NewCourse) course: NewCourse, @Ctx() context: ResolverContext): Promise<Course> {\n return await createCourse(course, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Course, { description: \"To change a course's name, description, category or manager.\" })\n async updateCourse(@Arg('id') id: string, @Arg('patch', type => CoursePatch) patch: CoursePatch, @Ctx() context: ResolverContext): Promise<Course> {\n return await updateCourse(id, patch, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseVersion, { description: 'To make the next draft version of a course - a copy of `fromId`, or of the latest version.' })\n async createCourseVersion(@Arg('courseId') courseId: string, @Arg('fromId', { nullable: true }) fromId: string, @Ctx() context: ResolverContext): Promise<CourseVersion> {\n return await createCourseVersion(courseId, fromId, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseVersion, { description: 'To change what a draft version says - a published one never changes.' })\n async updateCourseVersion(@Arg('id') id: string, @Arg('patch', type => CourseVersionPatch) patch: CourseVersionPatch, @Ctx() context: ResolverContext): Promise<CourseVersion> {\n return await updateCourseVersion(id, patch, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [CourseUnit], { description: 'To save the units of a draft version, in order - the ones left out are deleted.' })\n async saveCourseUnits(@Arg('versionId') versionId: string, @Arg('units', type => [CourseUnitInput]) units: CourseUnitInput[], @Ctx() context: ResolverContext): Promise<CourseUnit[]> {\n return await saveCourseUnits(versionId, units, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseUnit, { description: \"To attach a video's or a PDF's file to a unit of a draft version - a video with its length, a PDF with its page count.\" })\n async attachCourseUnitFile(\n @Arg('unitId') unitId: string,\n @Arg('file', type => GraphQLUpload) file: FileUpload,\n @Arg('durationSeconds', type => Int, { nullable: true, description: \"A video's length in seconds, read from the file.\" }) durationSeconds: number,\n @Arg('pageCount', type => Int, { nullable: true, description: \"A PDF's page count, read from the file.\" }) pageCount: number,\n @Ctx() context: ResolverContext\n ): Promise<CourseUnit> {\n return await attachCourseUnitFile(unitId, file, { durationSeconds, pageCount }, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [CourseQuestion], { description: 'To save the questions of a draft version, in order - the ones left out are deleted.' })\n async saveCourseQuestions(@Arg('versionId') versionId: string, @Arg('questions', type => [CourseQuestionInput]) questions: CourseQuestionInput[], @Ctx() context: ResolverContext): Promise<CourseQuestion[]> {\n return await saveCourseQuestions(versionId, questions, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseQuestion, { description: 'To add a question drafted by the assistant to a draft version - marked drafted until a manager saves it (ADR-0194 decision 12).' })\n async addDraftedCourseQuestion(@Arg('versionId') versionId: string, @Arg('question', type => CourseQuestionInput) question: CourseQuestionInput, @Ctx() context: ResolverContext): Promise<CourseQuestion> {\n const [saved] = await addDraftedQuestions(versionId, [question], context)\n if (!saved) throw new Refusal('learning-question-draft-invalid', {}, 'the drafted question is not complete - its text, two choices and the right ones')\n return saved\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseVersion, { description: 'To publish a draft version - the version published before is closed.' })\n async publishCourseVersion(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<CourseVersion> {\n return await publishCourseVersion(id, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => CourseVersion, { description: 'To close the published version - nobody starts it any more.' })\n async closeCourseVersion(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<CourseVersion> {\n return await closeCourseVersion(id, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Boolean, { description: 'To delete a draft version.' })\n async deleteCourseVersion(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {\n return await deleteCourseVersion(id, context)\n }\n}\n"]}
|
|
@@ -19,6 +19,7 @@ export declare class CourseVersionFields {
|
|
|
19
19
|
questions(version: CourseVersion, context: ResolverContext): Promise<CourseQuestion[]>;
|
|
20
20
|
}
|
|
21
21
|
export declare class CourseUnitFields {
|
|
22
|
+
secondsPerPage(unit: CourseUnit): number | null;
|
|
22
23
|
file(unit: CourseUnit, context: ResolverContext): Promise<Attachment | null>;
|
|
23
24
|
}
|
|
24
25
|
export declare class CourseQuestionFields {
|
|
@@ -6,6 +6,7 @@ const type_graphql_1 = require("type-graphql");
|
|
|
6
6
|
const attachment_base_1 = require("@things-factory/attachment-base");
|
|
7
7
|
const auth_base_1 = require("@things-factory/auth-base");
|
|
8
8
|
const shell_1 = require("@things-factory/shell");
|
|
9
|
+
const progress_rules_1 = require("../enrollment/progress-rules");
|
|
9
10
|
const course_1 = require("./course");
|
|
10
11
|
const course_question_1 = require("./course-question");
|
|
11
12
|
const course_unit_1 = require("./course-unit");
|
|
@@ -32,7 +33,7 @@ let CourseQuery = class CourseQuery {
|
|
|
32
33
|
return await (0, shell_1.getRepository)(course_version_1.CourseVersion).findOne({ where: { id, domain: { id: context.state.domain.id } } });
|
|
33
34
|
}
|
|
34
35
|
async versions(course, context) {
|
|
35
|
-
if (!(await (0,
|
|
36
|
+
if (!(await (0, auth_base_1.requestHolds)(context, 'course', 'query')))
|
|
36
37
|
return [];
|
|
37
38
|
return await (0, shell_1.getRepository)(course_version_1.CourseVersion).find({ where: { course: { id: course.id } }, order: { number: 'DESC' } });
|
|
38
39
|
}
|
|
@@ -105,7 +106,7 @@ let CourseVersionFields = class CourseVersionFields {
|
|
|
105
106
|
return await (0, course_service_1.unitsOf)((0, course_service_1.learningManagerOf)(context), version.id);
|
|
106
107
|
}
|
|
107
108
|
async questions(version, context) {
|
|
108
|
-
if (!(await (0,
|
|
109
|
+
if (!(await (0, auth_base_1.requestHolds)(context, 'course', 'query')))
|
|
109
110
|
return [];
|
|
110
111
|
return await (0, course_service_1.questionsOf)((0, course_service_1.learningManagerOf)(context), version.id);
|
|
111
112
|
}
|
|
@@ -131,6 +132,9 @@ exports.CourseVersionFields = CourseVersionFields = tslib_1.__decorate([
|
|
|
131
132
|
(0, type_graphql_1.Resolver)(course_version_1.CourseVersion)
|
|
132
133
|
], CourseVersionFields);
|
|
133
134
|
let CourseUnitFields = class CourseUnitFields {
|
|
135
|
+
secondsPerPage(unit) {
|
|
136
|
+
return unit.kind === 'PDF' ? (0, progress_rules_1.secondsPerPage)(unit) : null;
|
|
137
|
+
}
|
|
134
138
|
async file(unit, context) {
|
|
135
139
|
const version = await (0, course_service_1.learningManagerOf)(context).getRepository(course_version_1.CourseVersion).findOne({ where: { id: unit.versionId }, withDeleted: true });
|
|
136
140
|
if (!(await (0, course_service_1.mayReadVersion)(context, version)))
|
|
@@ -139,6 +143,13 @@ let CourseUnitFields = class CourseUnitFields {
|
|
|
139
143
|
}
|
|
140
144
|
};
|
|
141
145
|
exports.CourseUnitFields = CourseUnitFields;
|
|
146
|
+
tslib_1.__decorate([
|
|
147
|
+
(0, type_graphql_1.FieldResolver)(type => type_graphql_1.Int, { nullable: true, description: 'The fewest seconds a PDF page is looked at to count as read - the screen and the server read the same.' }),
|
|
148
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
149
|
+
tslib_1.__metadata("design:type", Function),
|
|
150
|
+
tslib_1.__metadata("design:paramtypes", [course_unit_1.CourseUnit]),
|
|
151
|
+
tslib_1.__metadata("design:returntype", Number)
|
|
152
|
+
], CourseUnitFields.prototype, "secondsPerPage", null);
|
|
142
153
|
tslib_1.__decorate([
|
|
143
154
|
(0, type_graphql_1.FieldResolver)(type => attachment_base_1.Attachment, { nullable: true, description: "A video's or a PDF's file." }),
|
|
144
155
|
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
@@ -152,7 +163,7 @@ exports.CourseUnitFields = CourseUnitFields = tslib_1.__decorate([
|
|
|
152
163
|
], CourseUnitFields);
|
|
153
164
|
let CourseQuestionFields = class CourseQuestionFields {
|
|
154
165
|
async choices(question, context) {
|
|
155
|
-
if (!(await (0,
|
|
166
|
+
if (!(await (0, auth_base_1.requestHolds)(context, 'course', 'query')))
|
|
156
167
|
return [];
|
|
157
168
|
return question.choices ?? [];
|
|
158
169
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"course-query.js","sourceRoot":"","sources":["../../../server/service/course/course-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root, UseMiddleware } from 'type-graphql'\n\nimport { Attachment } from '@things-factory/attachment-base'\nimport { User } from '@things-factory/auth-base'\nimport { getQueryBuilderFromListParams, getRepository, ListParam, SpeakRefusals } from '@things-factory/shell'\n\nimport { Course } from './course'\nimport { CourseQuestion } from './course-question'\nimport { CourseUnit } from './course-unit'\nimport { CourseVersion } from './course-version'\nimport { CourseChoiceKey, CourseList } from './course-type'\nimport { holds, learningManagerOf, mayReadVersion, publishedVersionOf, questionsOf, unitFilesOf, unitsOf } from './course-service'\n\n\n/*\n * The managers' reads of courses (ADR-0194 decisions 2-4, 10, 11) - `course` query, the task `learning.managing`. A learner\n * reads a course through their enrolment (enrollment-query.ts), never these.\n */\n@Resolver(Course)\n@UseMiddleware(SpeakRefusals)\nexport class CourseQuery {\n @Directive('@privilege(category: \"course\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => CourseList, { description: 'The courses of the domain.' })\n async courses(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<CourseList> {\n const { domain } = context.state\n const qb = getQueryBuilderFromListParams({ domain, params, repository: getRepository(Course), alias: 'C', searchables: ['name', 'description', 'category'] })\n if (!params?.sortings?.length) qb.orderBy('C.name', 'ASC')\n const [items, total] = await qb.getManyAndCount()\n return { items, total }\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => Course, { nullable: true, description: 'One course.' })\n async course(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Course | null> {\n return await getRepository(Course).findOne({ where: { id, domain: { id: context.state.domain.id } } as any })\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => CourseVersion, { nullable: true, description: 'One version of a course, with its units and questions.' })\n async courseVersion(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<CourseVersion | null> {\n return await getRepository(CourseVersion).findOne({ where: { id, domain: { id: context.state.domain.id } } as any })\n }\n\n @FieldResolver(type => [CourseVersion], { description: 'Its versions, the newest first - for a manager; nothing for anyone else.' })\n async versions(@Root() course: Course, @Ctx() context: ResolverContext): Promise<CourseVersion[]> {\n if (!(await holds(context, 'course', 'query'))) return []\n return await getRepository(CourseVersion).find({ where: { course: { id: course.id } } as any, order: { number: 'DESC' } as any })\n }\n\n @FieldResolver(type => CourseVersion, { nullable: true, description: 'The version learners take now.' })\n async publishedVersion(@Root() course: Course, @Ctx() context: ResolverContext): Promise<CourseVersion | null> {\n return await publishedVersionOf(learningManagerOf(context), { id: course.domainId! }, course.id)\n }\n\n @FieldResolver(type => User, { nullable: true, description: 'Who keeps it.' })\n async manager(@Root() course: Course): Promise<User | null> {\n return course.managerId ? await getRepository(User).findOneBy({ id: course.managerId }) : null\n }\n}\n\n@Resolver(CourseVersion)\nexport class CourseVersionFields {\n @FieldResolver(type => [CourseUnit], { description: 'Its units, in order - for a manager, or a learner whose enrolment shows this version.' })\n async units(@Root() version: CourseVersion, @Ctx() context: ResolverContext): Promise<CourseUnit[]> {\n if (!(await mayReadVersion(context, version))) return []\n return await unitsOf(learningManagerOf(context), version.id)\n }\n\n @FieldResolver(type => [CourseQuestion], { description: 'Its questions, in order - for a manager; nothing for anyone else.' })\n async questions(@Root() version: CourseVersion, @Ctx() context: ResolverContext): Promise<CourseQuestion[]> {\n if (!(await holds(context, 'course', 'query'))) return []\n return await questionsOf(learningManagerOf(context), version.id)\n }\n}\n\n@Resolver(CourseUnit)\nexport class CourseUnitFields {\n @FieldResolver(type => Attachment, { nullable: true, description: \"A video's or a PDF's file.\" })\n async file(@Root() unit: CourseUnit, @Ctx() context: ResolverContext): Promise<Attachment | null> {\n const version = await learningManagerOf(context).getRepository(CourseVersion).findOne({ where: { id: unit.versionId } as any, withDeleted: true })\n if (!(await mayReadVersion(context, version))) return null\n return (await unitFilesOf(context.state.domain, [unit.id])).get(unit.id) ?? null\n }\n}\n\n@Resolver(CourseQuestion)\nexport class CourseQuestionFields {\n @FieldResolver(type => [CourseChoiceKey], { description: 'Its choices with which are right - for a manager only.' })\n async choices(@Root() question: CourseQuestion, @Ctx() context: ResolverContext): Promise<CourseChoiceKey[]> {\n if (!(await holds(context, 'course', 'query'))) return []\n return question.choices ?? []\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"course-query.js","sourceRoot":"","sources":["../../../server/service/course/course-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Args, Ctx, Directive, FieldResolver, Int, Query, Resolver, Root, UseMiddleware } from 'type-graphql'\n\nimport { Attachment } from '@things-factory/attachment-base'\nimport { requestHolds, User } from '@things-factory/auth-base'\nimport { getQueryBuilderFromListParams, getRepository, ListParam, SpeakRefusals } from '@things-factory/shell'\n\nimport { secondsPerPage } from '../enrollment/progress-rules'\nimport { Course } from './course'\nimport { CourseQuestion } from './course-question'\nimport { CourseUnit } from './course-unit'\nimport { CourseVersion } from './course-version'\nimport { CourseChoiceKey, CourseList } from './course-type'\nimport { learningManagerOf, mayReadVersion, publishedVersionOf, questionsOf, unitFilesOf, unitsOf } from './course-service'\n\n\n/*\n * The managers' reads of courses (ADR-0194 decisions 2-4, 10, 11) - `course` query, the task `learning.managing`. A learner\n * reads a course through their enrolment (enrollment-query.ts), never these.\n */\n@Resolver(Course)\n@UseMiddleware(SpeakRefusals)\nexport class CourseQuery {\n @Directive('@privilege(category: \"course\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => CourseList, { description: 'The courses of the domain.' })\n async courses(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<CourseList> {\n const { domain } = context.state\n const qb = getQueryBuilderFromListParams({ domain, params, repository: getRepository(Course), alias: 'C', searchables: ['name', 'description', 'category'] })\n if (!params?.sortings?.length) qb.orderBy('C.name', 'ASC')\n const [items, total] = await qb.getManyAndCount()\n return { items, total }\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => Course, { nullable: true, description: 'One course.' })\n async course(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Course | null> {\n return await getRepository(Course).findOne({ where: { id, domain: { id: context.state.domain.id } } as any })\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"query\", domainOwnerGranted: true)')\n @Query(returns => CourseVersion, { nullable: true, description: 'One version of a course, with its units and questions.' })\n async courseVersion(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<CourseVersion | null> {\n return await getRepository(CourseVersion).findOne({ where: { id, domain: { id: context.state.domain.id } } as any })\n }\n\n @FieldResolver(type => [CourseVersion], { description: 'Its versions, the newest first - for a manager; nothing for anyone else.' })\n async versions(@Root() course: Course, @Ctx() context: ResolverContext): Promise<CourseVersion[]> {\n if (!(await requestHolds(context, 'course', 'query'))) return []\n return await getRepository(CourseVersion).find({ where: { course: { id: course.id } } as any, order: { number: 'DESC' } as any })\n }\n\n @FieldResolver(type => CourseVersion, { nullable: true, description: 'The version learners take now.' })\n async publishedVersion(@Root() course: Course, @Ctx() context: ResolverContext): Promise<CourseVersion | null> {\n return await publishedVersionOf(learningManagerOf(context), { id: course.domainId! }, course.id)\n }\n\n @FieldResolver(type => User, { nullable: true, description: 'Who keeps it.' })\n async manager(@Root() course: Course): Promise<User | null> {\n return course.managerId ? await getRepository(User).findOneBy({ id: course.managerId }) : null\n }\n}\n\n@Resolver(CourseVersion)\nexport class CourseVersionFields {\n @FieldResolver(type => [CourseUnit], { description: 'Its units, in order - for a manager, or a learner whose enrolment shows this version.' })\n async units(@Root() version: CourseVersion, @Ctx() context: ResolverContext): Promise<CourseUnit[]> {\n if (!(await mayReadVersion(context, version))) return []\n return await unitsOf(learningManagerOf(context), version.id)\n }\n\n @FieldResolver(type => [CourseQuestion], { description: 'Its questions, in order - for a manager; nothing for anyone else.' })\n async questions(@Root() version: CourseVersion, @Ctx() context: ResolverContext): Promise<CourseQuestion[]> {\n if (!(await requestHolds(context, 'course', 'query'))) return []\n return await questionsOf(learningManagerOf(context), version.id)\n }\n}\n\n@Resolver(CourseUnit)\nexport class CourseUnitFields {\n @FieldResolver(type => Int, { nullable: true, description: 'The fewest seconds a PDF page is looked at to count as read - the screen and the server read the same.' })\n secondsPerPage(@Root() unit: CourseUnit): number | null {\n return unit.kind === 'PDF' ? secondsPerPage(unit) : null\n }\n\n @FieldResolver(type => Attachment, { nullable: true, description: \"A video's or a PDF's file.\" })\n async file(@Root() unit: CourseUnit, @Ctx() context: ResolverContext): Promise<Attachment | null> {\n const version = await learningManagerOf(context).getRepository(CourseVersion).findOne({ where: { id: unit.versionId } as any, withDeleted: true })\n if (!(await mayReadVersion(context, version))) return null\n return (await unitFilesOf(context.state.domain, [unit.id])).get(unit.id) ?? null\n }\n}\n\n@Resolver(CourseQuestion)\nexport class CourseQuestionFields {\n @FieldResolver(type => [CourseChoiceKey], { description: 'Its choices with which are right - for a manager only.' })\n async choices(@Root() question: CourseQuestion, @Ctx() context: ResolverContext): Promise<CourseChoiceKey[]> {\n if (!(await requestHolds(context, 'course', 'query'))) return []\n return question.choices ?? []\n }\n}\n"]}
|
|
@@ -17,6 +17,7 @@ export interface UnitInput {
|
|
|
17
17
|
body?: string | null;
|
|
18
18
|
url?: string | null;
|
|
19
19
|
durationSeconds?: number | null;
|
|
20
|
+
pageCount?: number | null;
|
|
20
21
|
minSeconds?: number | null;
|
|
21
22
|
}
|
|
22
23
|
/** Why a unit may not be kept as given. A video's or a PDF's file is attached apart - publishing asks for it. */
|
|
@@ -36,6 +37,7 @@ export interface PublishFacts {
|
|
|
36
37
|
units: {
|
|
37
38
|
kind: string;
|
|
38
39
|
durationSeconds?: number | null;
|
|
40
|
+
pageCount?: number | null;
|
|
39
41
|
hasFile: boolean;
|
|
40
42
|
}[];
|
|
41
43
|
questions: {
|
|
@@ -44,6 +46,6 @@ export interface PublishFacts {
|
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
48
|
* Why a draft may not be published (decisions 2-4): a unit or more; a video or a PDF with its file, a video with its
|
|
47
|
-
* length; a quiz with questions - as many as it draws, none of them a draft the assistant made that no manager looked over.
|
|
49
|
+
* length, a PDF with its page count; a quiz with questions - as many as it draws, none of them a draft the assistant made that no manager looked over.
|
|
48
50
|
*/
|
|
49
51
|
export declare function publishRefusal({ version, units, questions }: PublishFacts): string | null;
|
|
@@ -41,6 +41,8 @@ function unitInputRefusal(unit) {
|
|
|
41
41
|
return 'learning-unit-min-time-invalid';
|
|
42
42
|
if (unit.durationSeconds != null && (!Number.isInteger(unit.durationSeconds) || unit.durationSeconds < 1))
|
|
43
43
|
return 'learning-unit-duration-invalid';
|
|
44
|
+
if (unit.pageCount != null && (!Number.isInteger(unit.pageCount) || unit.pageCount < 1))
|
|
45
|
+
return 'learning-unit-pages-invalid';
|
|
44
46
|
return null;
|
|
45
47
|
}
|
|
46
48
|
/** Why a question may not be kept: its text, two choices or more (TRUE_FALSE exactly two), and the right ones its kind allows. */
|
|
@@ -63,7 +65,7 @@ function questionInputRefusal(question) {
|
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* Why a draft may not be published (decisions 2-4): a unit or more; a video or a PDF with its file, a video with its
|
|
66
|
-
* length; a quiz with questions - as many as it draws, none of them a draft the assistant made that no manager looked over.
|
|
68
|
+
* length, a PDF with its page count; a quiz with questions - as many as it draws, none of them a draft the assistant made that no manager looked over.
|
|
67
69
|
*/
|
|
68
70
|
function publishRefusal({ version, units, questions }) {
|
|
69
71
|
if (version.status !== 'DRAFT')
|
|
@@ -75,6 +77,8 @@ function publishRefusal({ version, units, questions }) {
|
|
|
75
77
|
return 'learning-unit-file-required';
|
|
76
78
|
if (unit.kind === 'VIDEO' && !unit.durationSeconds)
|
|
77
79
|
return 'learning-unit-duration-required';
|
|
80
|
+
if (unit.kind === 'PDF' && !unit.pageCount)
|
|
81
|
+
return 'learning-unit-pages-required';
|
|
78
82
|
}
|
|
79
83
|
if (version.passScore != null) {
|
|
80
84
|
if (!questions.length)
|