@things-factory/learning 0.0.0-stage → 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.
Files changed (167) hide show
  1. package/dist-client/bootstrap.d.ts +2 -0
  2. package/dist-client/bootstrap.js +4 -0
  3. package/dist-client/bootstrap.js.map +1 -0
  4. package/dist-client/components/learning-assign-form.d.ts +24 -0
  5. package/dist-client/components/learning-assign-form.js +77 -0
  6. package/dist-client/components/learning-assign-form.js.map +1 -0
  7. package/dist-client/components/learning-course-form.d.ts +29 -0
  8. package/dist-client/components/learning-course-form.js +104 -0
  9. package/dist-client/components/learning-course-form.js.map +1 -0
  10. package/dist-client/components/learning-material-form.d.ts +41 -0
  11. package/dist-client/components/learning-material-form.js +202 -0
  12. package/dist-client/components/learning-material-form.js.map +1 -0
  13. package/dist-client/components/learning-question-form.d.ts +33 -0
  14. package/dist-client/components/learning-question-form.js +142 -0
  15. package/dist-client/components/learning-question-form.js.map +1 -0
  16. package/dist-client/components/learning-todo.d.ts +12 -0
  17. package/dist-client/components/learning-todo.js +43 -0
  18. package/dist-client/components/learning-todo.js.map +1 -0
  19. package/dist-client/components/learning-unit-form.d.ts +44 -0
  20. package/dist-client/components/learning-unit-form.js +151 -0
  21. package/dist-client/components/learning-unit-form.js.map +1 -0
  22. package/dist-client/index.d.ts +2 -0
  23. package/dist-client/index.js +3 -0
  24. package/dist-client/index.js.map +1 -0
  25. package/dist-client/pages/learning-course-editor.d.ts +63 -0
  26. package/dist-client/pages/learning-course-editor.js +476 -0
  27. package/dist-client/pages/learning-course-editor.js.map +1 -0
  28. package/dist-client/pages/learning-course-list.d.ts +37 -0
  29. package/dist-client/pages/learning-course-list.js +92 -0
  30. package/dist-client/pages/learning-course-list.js.map +1 -0
  31. package/dist-client/pages/learning-enrollment-status.d.ts +65 -0
  32. package/dist-client/pages/learning-enrollment-status.js +211 -0
  33. package/dist-client/pages/learning-enrollment-status.js.map +1 -0
  34. package/dist-client/pages/learning-view.d.ts +63 -0
  35. package/dist-client/pages/learning-view.js +446 -0
  36. package/dist-client/pages/learning-view.js.map +1 -0
  37. package/dist-client/pages/my-learning.d.ts +22 -0
  38. package/dist-client/pages/my-learning.js +110 -0
  39. package/dist-client/pages/my-learning.js.map +1 -0
  40. package/dist-client/route.d.ts +1 -0
  41. package/dist-client/route.js +21 -0
  42. package/dist-client/route.js.map +1 -0
  43. package/dist-client/tsconfig.tsbuildinfo +1 -0
  44. package/dist-client/utils/learning-api.d.ts +61 -0
  45. package/dist-client/utils/learning-api.js +85 -0
  46. package/dist-client/utils/learning-api.js.map +1 -0
  47. package/dist-client/utils/learning-words.d.ts +20 -0
  48. package/dist-client/utils/learning-words.js +22 -0
  49. package/dist-client/utils/learning-words.js.map +1 -0
  50. package/dist-client/utils/material-source.d.ts +12 -0
  51. package/dist-client/utils/material-source.js +77 -0
  52. package/dist-client/utils/material-source.js.map +1 -0
  53. package/dist-server/activities.d.ts +38 -0
  54. package/dist-server/activities.js +42 -0
  55. package/dist-server/activities.js.map +1 -0
  56. package/dist-server/ai-reads.d.ts +9 -0
  57. package/dist-server/ai-reads.js +70 -0
  58. package/dist-server/ai-reads.js.map +1 -0
  59. package/dist-server/attachment-reader.d.ts +1 -0
  60. package/dist-server/attachment-reader.js +23 -0
  61. package/dist-server/attachment-reader.js.map +1 -0
  62. package/dist-server/index.d.ts +4 -0
  63. package/dist-server/index.js +33 -0
  64. package/dist-server/index.js.map +1 -0
  65. package/dist-server/learning-subjects.d.ts +10 -0
  66. package/dist-server/learning-subjects.js +31 -0
  67. package/dist-server/learning-subjects.js.map +1 -0
  68. package/dist-server/role-tasks.d.ts +2 -0
  69. package/dist-server/role-tasks.js +17 -0
  70. package/dist-server/role-tasks.js.map +1 -0
  71. package/dist-server/service/course/course-mutation.d.ts +19 -0
  72. package/dist-server/service/course/course-mutation.js +177 -0
  73. package/dist-server/service/course/course-mutation.js.map +1 -0
  74. package/dist-server/service/course/course-query.d.ts +27 -0
  75. package/dist-server/service/course/course-query.js +183 -0
  76. package/dist-server/service/course/course-query.js.map +1 -0
  77. package/dist-server/service/course/course-question.d.ts +41 -0
  78. package/dist-server/service/course/course-question.js +116 -0
  79. package/dist-server/service/course/course-question.js.map +1 -0
  80. package/dist-server/service/course/course-rules.d.ts +51 -0
  81. package/dist-server/service/course/course-rules.js +93 -0
  82. package/dist-server/service/course/course-rules.js.map +1 -0
  83. package/dist-server/service/course/course-service.d.ts +72 -0
  84. package/dist-server/service/course/course-service.js +398 -0
  85. package/dist-server/service/course/course-service.js.map +1 -0
  86. package/dist-server/service/course/course-type.d.ts +56 -0
  87. package/dist-server/service/course/course-type.js +202 -0
  88. package/dist-server/service/course/course-type.js.map +1 -0
  89. package/dist-server/service/course/course-unit.d.ts +37 -0
  90. package/dist-server/service/course/course-unit.js +128 -0
  91. package/dist-server/service/course/course-unit.js.map +1 -0
  92. package/dist-server/service/course/course-version.d.ts +45 -0
  93. package/dist-server/service/course/course-version.js +151 -0
  94. package/dist-server/service/course/course-version.js.map +1 -0
  95. package/dist-server/service/course/course.d.ts +23 -0
  96. package/dist-server/service/course/course.js +92 -0
  97. package/dist-server/service/course/course.js.map +1 -0
  98. package/dist-server/service/course/index.d.ts +15 -0
  99. package/dist-server/service/course/index.js +25 -0
  100. package/dist-server/service/course/index.js.map +1 -0
  101. package/dist-server/service/course/material-audio.d.ts +1 -0
  102. package/dist-server/service/course/material-audio.js +43 -0
  103. package/dist-server/service/course/material-audio.js.map +1 -0
  104. package/dist-server/service/course/material-draft-mutation.d.ts +13 -0
  105. package/dist-server/service/course/material-draft-mutation.js +55 -0
  106. package/dist-server/service/course/material-draft-mutation.js.map +1 -0
  107. package/dist-server/service/course/material-draft-rules.d.ts +38 -0
  108. package/dist-server/service/course/material-draft-rules.js +83 -0
  109. package/dist-server/service/course/material-draft-rules.js.map +1 -0
  110. package/dist-server/service/course/material-draft-service.d.ts +15 -0
  111. package/dist-server/service/course/material-draft-service.js +162 -0
  112. package/dist-server/service/course/material-draft-service.js.map +1 -0
  113. package/dist-server/service/enrollment/enrollment-mutation.d.ts +11 -0
  114. package/dist-server/service/enrollment/enrollment-mutation.js +124 -0
  115. package/dist-server/service/enrollment/enrollment-mutation.js.map +1 -0
  116. package/dist-server/service/enrollment/enrollment-query.d.ts +24 -0
  117. package/dist-server/service/enrollment/enrollment-query.js +236 -0
  118. package/dist-server/service/enrollment/enrollment-query.js.map +1 -0
  119. package/dist-server/service/enrollment/enrollment-remind.d.ts +1 -0
  120. package/dist-server/service/enrollment/enrollment-remind.js +45 -0
  121. package/dist-server/service/enrollment/enrollment-remind.js.map +1 -0
  122. package/dist-server/service/enrollment/enrollment-service.d.ts +110 -0
  123. package/dist-server/service/enrollment/enrollment-service.js +427 -0
  124. package/dist-server/service/enrollment/enrollment-service.js.map +1 -0
  125. package/dist-server/service/enrollment/enrollment-status.d.ts +53 -0
  126. package/dist-server/service/enrollment/enrollment-status.js +194 -0
  127. package/dist-server/service/enrollment/enrollment-status.js.map +1 -0
  128. package/dist-server/service/enrollment/enrollment-type.d.ts +82 -0
  129. package/dist-server/service/enrollment/enrollment-type.js +293 -0
  130. package/dist-server/service/enrollment/enrollment-type.js.map +1 -0
  131. package/dist-server/service/enrollment/enrollment.d.ts +53 -0
  132. package/dist-server/service/enrollment/enrollment.js +191 -0
  133. package/dist-server/service/enrollment/enrollment.js.map +1 -0
  134. package/dist-server/service/enrollment/index.d.ts +15 -0
  135. package/dist-server/service/enrollment/index.js +26 -0
  136. package/dist-server/service/enrollment/index.js.map +1 -0
  137. package/dist-server/service/enrollment/learning-ai.d.ts +45 -0
  138. package/dist-server/service/enrollment/learning-ai.js +93 -0
  139. package/dist-server/service/enrollment/learning-ai.js.map +1 -0
  140. package/dist-server/service/enrollment/learning-documents.d.ts +7 -0
  141. package/dist-server/service/enrollment/learning-documents.js +159 -0
  142. package/dist-server/service/enrollment/learning-documents.js.map +1 -0
  143. package/dist-server/service/enrollment/learning-outcome.d.ts +10 -0
  144. package/dist-server/service/enrollment/learning-outcome.js +16 -0
  145. package/dist-server/service/enrollment/learning-outcome.js.map +1 -0
  146. package/dist-server/service/enrollment/progress-rules.d.ts +119 -0
  147. package/dist-server/service/enrollment/progress-rules.js +132 -0
  148. package/dist-server/service/enrollment/progress-rules.js.map +1 -0
  149. package/dist-server/service/enrollment/quiz-attempt.d.ts +34 -0
  150. package/dist-server/service/enrollment/quiz-attempt.js +105 -0
  151. package/dist-server/service/enrollment/quiz-attempt.js.map +1 -0
  152. package/dist-server/service/enrollment/unit-progress.d.ts +34 -0
  153. package/dist-server/service/enrollment/unit-progress.js +134 -0
  154. package/dist-server/service/enrollment/unit-progress.js.map +1 -0
  155. package/dist-server/service/index.d.ts +14 -0
  156. package/dist-server/service/index.js +43 -0
  157. package/dist-server/service/index.js.map +1 -0
  158. package/dist-server/tsconfig.tsbuildinfo +1 -0
  159. package/package.json +57 -4
  160. package/things-factory.config.js +14 -0
  161. package/translations/en.json +278 -0
  162. package/translations/es.json +278 -0
  163. package/translations/ja.json +278 -0
  164. package/translations/ko.json +278 -0
  165. package/translations/ms.json +278 -0
  166. package/translations/zh.json +278 -0
  167. package/README.md +0 -3
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.proposeMaterial = proposeMaterial;
4
+ exports.applyMaterial = applyMaterial;
5
+ exports.transcribeMaterial = transcribeMaterial;
6
+ const material_audio_1 = require("./material-audio");
7
+ const crypto_1 = require("crypto");
8
+ const ai_client_base_1 = require("@things-factory/ai-client-base");
9
+ const attachment_base_1 = require("@things-factory/attachment-base");
10
+ const auth_base_1 = require("@things-factory/auth-base");
11
+ const shell_1 = require("@things-factory/shell");
12
+ const course_unit_1 = require("./course-unit");
13
+ const course_version_1 = require("./course-version");
14
+ const course_question_1 = require("./course-question");
15
+ const course_service_1 = require("./course-service");
16
+ const material_draft_rules_1 = require("./material-draft-rules");
17
+ async function scope(unitId, context) {
18
+ if (!(await (0, auth_base_1.requestHolds)(context, 'course', 'mutation')))
19
+ throw new shell_1.Refusal('learning-not-found', {}, 'the record was not found');
20
+ const { domain } = context.state;
21
+ const tx = (0, course_service_1.learningManagerOf)(context);
22
+ const unit = await tx.getRepository(course_unit_1.CourseUnit).findOne({ where: { id: unitId, domain: { id: domain.id } } });
23
+ const version = unit && await tx.getRepository(course_version_1.CourseVersion).findOne({ where: { id: unit.versionId, domain: { id: domain.id } } });
24
+ if (!unit || !version)
25
+ throw new shell_1.Refusal('learning-not-found', {}, 'the record was not found');
26
+ if (version.status !== course_version_1.CourseVersionStatus.DRAFT)
27
+ throw new shell_1.Refusal('learning-version-not-draft', {}, 'only draft versions take material drafts');
28
+ if (![course_unit_1.CourseUnitKind.PDF, course_unit_1.CourseUnitKind.VIDEO].includes(unit.kind))
29
+ throw new shell_1.Refusal('learning-ai-material-invalid', {}, 'choose a PDF or video with its source');
30
+ const file = await tx.getRepository(attachment_base_1.Attachment).findOne({ where: { domain: { id: domain.id }, refBy: unit.id, refType: course_service_1.COURSE_UNIT_REF_TYPE } });
31
+ if (!file)
32
+ throw new shell_1.Refusal('learning-unit-file-required', {}, 'upload the source file first');
33
+ const units = await (0, course_service_1.unitsOf)(tx, version.id), questions = await (0, course_service_1.questionsOf)(tx, version.id);
34
+ const token = (0, crypto_1.createHash)('sha256').update(JSON.stringify({ version, units, questions, fileId: file.id })).digest('hex');
35
+ return { tx, unit, version, file, units, questions, token };
36
+ }
37
+ function checkedSource(value, at) {
38
+ try {
39
+ const source = (0, material_draft_rules_1.materialSource)(value);
40
+ if (source.fileId !== at.file.id)
41
+ throw new Error('changed source');
42
+ const prefix = at.unit.kind === course_unit_1.CourseUnitKind.PDF ? 'page-' : 'caption-';
43
+ if (source.parts.some(p => !p.id.startsWith(prefix)))
44
+ throw new Error('wrong material kind');
45
+ if (at.unit.kind === course_unit_1.CourseUnitKind.VIDEO && source.parts.some(p => p.image))
46
+ throw new Error('captions only');
47
+ if (at.unit.kind === course_unit_1.CourseUnitKind.PDF && (source.parts.length !== at.unit.pageCount || source.parts.some((p, i) => p.id !== `page-${i + 1}`)))
48
+ throw new Error('incomplete PDF');
49
+ return source;
50
+ }
51
+ catch {
52
+ throw new shell_1.Refusal('learning-ai-material-invalid', {}, 'the complete material could not be read; check the pages or captions and limits');
53
+ }
54
+ }
55
+ /** No course write and no provider request until the manager, draft, file and full source have been checked. */
56
+ async function proposeMaterial(unitId, sourceInput, instruction, context) {
57
+ const at = await scope(unitId, context);
58
+ const source = checkedSource(sourceInput, at);
59
+ if (typeof instruction !== 'string' || instruction.length > 4000)
60
+ throw new shell_1.Refusal('learning-ai-material-invalid', {}, 'the request is too long');
61
+ const client = await (0, ai_client_base_1.aiClientFor)({ domain: context.state.domain, user: context.state.user, modeKind: 'learning.material-draft' });
62
+ const content = [{ type: 'text', text: JSON.stringify({ file: at.file.name, kind: at.unit.kind, instruction, source: 'Read every supplied part. Video content is captions only.' }) }];
63
+ for (const part of source.parts) {
64
+ content.push({ type: 'text', text: `SOURCE ${part.id}\n${part.text ?? '(scanned page image follows)'}` });
65
+ if (part.image)
66
+ content.push({ type: 'image', source: { kind: 'base64', data: Buffer.from(part.image.split(',')[1], 'base64'), mediaType: 'image/jpeg' } });
67
+ }
68
+ const controller = new AbortController();
69
+ const timer = setTimeout(() => controller.abort(), 120_000);
70
+ try {
71
+ const messages = [{ role: 'user', content }];
72
+ const raw = await client.generateJSON(messages, { systemPrompt: material_draft_rules_1.MATERIAL_DRAFT_PROMPT, maxTokens: 12000, signal: controller.signal });
73
+ let draft;
74
+ try {
75
+ draft = (0, material_draft_rules_1.materialDraft)(raw, source);
76
+ }
77
+ catch {
78
+ throw new shell_1.Refusal('learning-ai-draft-invalid', {}, 'the AI draft is incomplete or has invalid references; no course content was changed');
79
+ }
80
+ return { draft, token: at.token, fileName: at.file.name, sourceKind: at.unit.kind };
81
+ }
82
+ finally {
83
+ clearTimeout(timer);
84
+ }
85
+ }
86
+ /** One transaction appends the reviewed preview. Existing units/questions/settings are never replaced. */
87
+ async function applyMaterial(unitId, sourceInput, draftInput, token, context) {
88
+ const at = await scope(unitId, context);
89
+ const source = checkedSource(sourceInput, at);
90
+ if (token !== at.token)
91
+ throw new shell_1.Refusal('learning-ai-draft-stale', {}, 'the version changed; generate a new preview');
92
+ let draft;
93
+ try {
94
+ draft = (0, material_draft_rules_1.materialDraft)(draftInput, source);
95
+ }
96
+ catch {
97
+ throw new shell_1.Refusal('learning-ai-draft-invalid', {}, 'check the draft and its source references');
98
+ }
99
+ // Claim before inserting; another apply/publish cannot consume the same version snapshot.
100
+ const claim = await at.tx.getRepository(course_version_1.CourseVersion).createQueryBuilder().update()
101
+ .set({ updatedAt: () => 'updated_at' })
102
+ .where({ id: at.version.id, status: course_version_1.CourseVersionStatus.DRAFT }).execute();
103
+ if (!claim.affected || (await scope(unitId, context)).token !== token)
104
+ throw new shell_1.Refusal('learning-ai-draft-stale', {}, 'the version changed; generate a new preview');
105
+ const { domain, user } = context.state;
106
+ const refs = (ids) => `${at.file.name} (${ids.join(', ')})`;
107
+ const introduction = `${draft.introduction}\n\n${draft.objectives.map(s => `- ${s}`).join('\n')}`;
108
+ const additions = [{ name: draft.title, body: introduction, minSeconds: 60 }, ...draft.units.map(u => ({ name: u.name, body: `${u.body}\n\nSource: ${refs(u.references)}`, minSeconds: u.minSeconds }))];
109
+ let seq = at.units.reduce((n, u) => Math.max(n, u.seq), 0);
110
+ for (const u of additions)
111
+ await at.tx.getRepository(course_unit_1.CourseUnit).save({ ...u, kind: course_unit_1.CourseUnitKind.MARKDOWN, seq: ++seq, domain, version: at.version, creator: user, updater: user });
112
+ let questionSeq = at.questions.reduce((n, q) => Math.max(n, q.seq), 0);
113
+ for (const q of draft.questions)
114
+ await at.tx.getRepository(course_question_1.CourseQuestion).save({
115
+ domain, version: at.version, seq: ++questionSeq, kind: q.kind, text: q.text,
116
+ choices: q.choices.map(c => ({ ...c, id: (0, crypto_1.randomUUID)() })), explanation: `${q.explanation}\n\nSource: ${refs(q.references)}`,
117
+ drafted: true, creator: user, updater: user
118
+ });
119
+ // Review notes are manager-facing, not a learner's pretend company procedure.
120
+ const note = [`AI draft: ${at.file.name}; ${at.unit.kind === course_unit_1.CourseUnitKind.VIDEO ? 'captions only' : 'PDF pages'}`, ...draft.missing.map(s => `Review: ${s}`)].join('\n');
121
+ const evidence = at.unit.kind === course_unit_1.CourseUnitKind.VIDEO ? source.parts.map(p => `${p.id}\n${p.text}`).join('\n\n') : '';
122
+ const seconds = [...at.units, ...additions].reduce((sum, u) => sum + (u.kind === course_unit_1.CourseUnitKind.LINK ? 0 : (u.durationSeconds ?? u.minSeconds ?? 60)), 0);
123
+ await at.tx.getRepository(course_version_1.CourseVersion).update(at.version.id, {
124
+ sourceNote: [at.version.sourceNote, note, `Source file: ${at.file.id}`, evidence].filter(Boolean).join('\n\n'),
125
+ minSeconds: Math.max(at.version.minSeconds ?? 0, seconds),
126
+ passScore: at.version.passScore ?? 80,
127
+ questionCount: at.version.questionCount ?? at.questions.length + draft.questions.length,
128
+ updater: user
129
+ });
130
+ return at.version.id;
131
+ }
132
+ /** Transcribe an existing authorized video through the same tenant AI/metering boundary. */
133
+ async function transcribeMaterial(unitId, context) {
134
+ const at = await scope(unitId, context);
135
+ if (at.unit.kind !== course_unit_1.CourseUnitKind.VIDEO || !at.unit.durationSeconds || at.unit.durationSeconds > 3600 || Number(at.file.size) > 250 * 1024 * 1024)
136
+ throw new shell_1.Refusal('learning-ai-video-limit', {}, 'choose a video up to 60 minutes and 250 MB');
137
+ const client = await (0, ai_client_base_1.aiClientFor)({ domain: context.state.domain, user: context.state.user, modeKind: 'learning.material-transcription' });
138
+ if (!client.transcribe)
139
+ throw new shell_1.Refusal('learning-ai-transcription-unavailable', {}, 'this AI agent cannot transcribe audio; use a speech-capable agent or supply captions');
140
+ const controller = new AbortController();
141
+ const timer = setTimeout(() => controller.abort(), 180_000);
142
+ try {
143
+ let audio;
144
+ try {
145
+ audio = await (0, material_audio_1.speechMp3)(Buffer.from(await attachment_base_1.STORAGE.readFile(at.file.path)), controller.signal);
146
+ }
147
+ catch {
148
+ throw new shell_1.Refusal('learning-ai-audio-unreadable', {}, 'audio could not be extracted; check ffmpeg, video format and audio track, or provide captions');
149
+ }
150
+ const answer = await client.transcribe(audio, { signal: controller.signal });
151
+ if (!answer.text?.trim() || !answer.segments?.length || answer.segments.some(s => !Number.isFinite(s.start) || !Number.isFinite(s.end) || s.start < 0 || s.end < s.start || s.end > at.unit.durationSeconds + 5))
152
+ throw new shell_1.Refusal('learning-ai-audio-unreadable', {}, 'the audio transcript is empty or invalid');
153
+ const captions = answer.segments.map(s => `[${s.start.toFixed(1)} - ${s.end.toFixed(1)} s] ${s.text.trim()}`).join('\n');
154
+ if (captions.length > 120_000)
155
+ throw new shell_1.Refusal('learning-ai-video-limit', {}, 'the transcript is too long; split the material');
156
+ return { fileId: at.file.id, captions };
157
+ }
158
+ finally {
159
+ clearTimeout(timer);
160
+ }
161
+ }
162
+ //# sourceMappingURL=material-draft-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"material-draft-service.js","sourceRoot":"","sources":["../../../server/service/course/material-draft-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { speechMp3 } from './material-audio'\nimport { createHash, randomUUID } from 'crypto'\nimport { aiClientFor, type AIMessage, type AIContentPart } from '@things-factory/ai-client-base'\nimport { Attachment, STORAGE } from '@things-factory/attachment-base'\nimport { requestHolds } from '@things-factory/auth-base'\nimport { Refusal } from '@things-factory/shell'\nimport { CourseUnit, CourseUnitKind } from './course-unit'\nimport { CourseVersion, CourseVersionStatus } from './course-version'\nimport { CourseQuestion, CourseQuestionKind } from './course-question'\nimport { COURSE_UNIT_REF_TYPE, learningManagerOf, unitsOf, questionsOf } from './course-service'\nimport { materialDraft, materialSource, MATERIAL_DRAFT_PROMPT } from './material-draft-rules'\n\nasync function scope(unitId: string, context: any) {\n if (!(await requestHolds(context, 'course', 'mutation'))) throw new Refusal('learning-not-found', {}, 'the record was not found')\n const { domain } = context.state\n const tx = learningManagerOf(context)\n const unit = await tx.getRepository(CourseUnit).findOne({ where: { id: unitId, domain: { id: domain.id } } as any })\n const version = unit && await tx.getRepository(CourseVersion).findOne({ where: { id: unit.versionId, domain: { id: domain.id } } as any })\n if (!unit || !version) throw new Refusal('learning-not-found', {}, 'the record was not found')\n if (version.status !== CourseVersionStatus.DRAFT) throw new Refusal('learning-version-not-draft', {}, 'only draft versions take material drafts')\n if (![CourseUnitKind.PDF, CourseUnitKind.VIDEO].includes(unit.kind)) throw new Refusal('learning-ai-material-invalid', {}, 'choose a PDF or video with its source')\n const file = await tx.getRepository(Attachment).findOne({ where: { domain: { id: domain.id }, refBy: unit.id, refType: COURSE_UNIT_REF_TYPE } as any })\n if (!file) throw new Refusal('learning-unit-file-required', {}, 'upload the source file first')\n const units = await unitsOf(tx, version.id), questions = await questionsOf(tx, version.id)\n const token = createHash('sha256').update(JSON.stringify({ version, units, questions, fileId: file.id })).digest('hex')\n return { tx, unit, version, file, units, questions, token }\n}\nfunction checkedSource(value: unknown, at: Awaited<ReturnType<typeof scope>>) {\n try {\n const source = materialSource(value)\n if (source.fileId !== at.file.id) throw new Error('changed source')\n const prefix = at.unit.kind === CourseUnitKind.PDF ? 'page-' : 'caption-'\n if (source.parts.some(p => !p.id.startsWith(prefix))) throw new Error('wrong material kind')\n if (at.unit.kind === CourseUnitKind.VIDEO && source.parts.some(p => p.image)) throw new Error('captions only')\n if (at.unit.kind === CourseUnitKind.PDF && (source.parts.length !== at.unit.pageCount || source.parts.some((p, i) => p.id !== `page-${i + 1}`))) throw new Error('incomplete PDF')\n return source\n } catch { throw new Refusal('learning-ai-material-invalid', {}, 'the complete material could not be read; check the pages or captions and limits') }\n}\n\n/** No course write and no provider request until the manager, draft, file and full source have been checked. */\nexport async function proposeMaterial(unitId: string, sourceInput: unknown, instruction: string, context: any) {\n const at = await scope(unitId, context)\n const source = checkedSource(sourceInput, at)\n if (typeof instruction !== 'string' || instruction.length > 4000) throw new Refusal('learning-ai-material-invalid', {}, 'the request is too long')\n const client = await aiClientFor({ domain: context.state.domain, user: context.state.user, modeKind: 'learning.material-draft' })\n const content: AIContentPart[] = [{ type: 'text', text: JSON.stringify({ file: at.file.name, kind: at.unit.kind, instruction, source: 'Read every supplied part. Video content is captions only.' }) }]\n for (const part of source.parts) {\n content.push({ type: 'text', text: `SOURCE ${part.id}\\n${part.text ?? '(scanned page image follows)'}` })\n if (part.image) content.push({ type: 'image', source: { kind: 'base64', data: Buffer.from(part.image.split(',')[1], 'base64'), mediaType: 'image/jpeg' } })\n }\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), 120_000)\n try {\n const messages: AIMessage[] = [{ role: 'user', content }]\n const raw = await client.generateJSON(messages, { systemPrompt: MATERIAL_DRAFT_PROMPT, maxTokens: 12000, signal: controller.signal })\n let draft\n try { draft = materialDraft(raw, source) } catch { throw new Refusal('learning-ai-draft-invalid', {}, 'the AI draft is incomplete or has invalid references; no course content was changed') }\n return { draft, token: at.token, fileName: at.file.name, sourceKind: at.unit.kind }\n } finally { clearTimeout(timer) }\n}\n\n/** One transaction appends the reviewed preview. Existing units/questions/settings are never replaced. */\nexport async function applyMaterial(unitId: string, sourceInput: unknown, draftInput: unknown, token: string, context: any) {\n const at = await scope(unitId, context)\n const source = checkedSource(sourceInput, at)\n if (token !== at.token) throw new Refusal('learning-ai-draft-stale', {}, 'the version changed; generate a new preview')\n let draft\n try { draft = materialDraft(draftInput, source) } catch { throw new Refusal('learning-ai-draft-invalid', {}, 'check the draft and its source references') }\n // Claim before inserting; another apply/publish cannot consume the same version snapshot.\n const claim = await at.tx.getRepository(CourseVersion).createQueryBuilder().update()\n .set({ updatedAt: () => 'updated_at' })\n .where({ id: at.version.id, status: CourseVersionStatus.DRAFT }).execute()\n if (!claim.affected || (await scope(unitId, context)).token !== token)\n throw new Refusal('learning-ai-draft-stale', {}, 'the version changed; generate a new preview')\n const { domain, user } = context.state\n const refs = (ids: string[]) => `${at.file.name} (${ids.join(', ')})`\n const introduction = `${draft.introduction}\\n\\n${draft.objectives.map(s => `- ${s}`).join('\\n')}`\n const additions = [{ name: draft.title, body: introduction, minSeconds: 60 }, ...draft.units.map(u => ({ name: u.name, body: `${u.body}\\n\\nSource: ${refs(u.references)}`, minSeconds: u.minSeconds }))]\n let seq = at.units.reduce((n, u) => Math.max(n, u.seq), 0)\n for (const u of additions) await at.tx.getRepository(CourseUnit).save({ ...u, kind: CourseUnitKind.MARKDOWN, seq: ++seq, domain, version: at.version, creator: user, updater: user } as any)\n let questionSeq = at.questions.reduce((n, q) => Math.max(n, q.seq), 0)\n for (const q of draft.questions) await at.tx.getRepository(CourseQuestion).save({\n domain, version: at.version, seq: ++questionSeq, kind: q.kind as CourseQuestionKind, text: q.text,\n choices: q.choices.map(c => ({ ...c, id: randomUUID() })), explanation: `${q.explanation}\\n\\nSource: ${refs(q.references)}`,\n drafted: true, creator: user, updater: user\n } as any)\n // Review notes are manager-facing, not a learner's pretend company procedure.\n const note = [`AI draft: ${at.file.name}; ${at.unit.kind === CourseUnitKind.VIDEO ? 'captions only' : 'PDF pages'}`, ...draft.missing.map(s => `Review: ${s}`)].join('\\n')\n const evidence = at.unit.kind === CourseUnitKind.VIDEO ? source.parts.map(p => `${p.id}\\n${p.text}`).join('\\n\\n') : ''\n const seconds = [...at.units, ...additions].reduce((sum, u: any) => sum + (u.kind === CourseUnitKind.LINK ? 0 : (u.durationSeconds ?? u.minSeconds ?? 60)), 0)\n await at.tx.getRepository(CourseVersion).update(at.version.id, {\n sourceNote: [at.version.sourceNote, note, `Source file: ${at.file.id}`, evidence].filter(Boolean).join('\\n\\n'),\n minSeconds: Math.max(at.version.minSeconds ?? 0, seconds),\n passScore: at.version.passScore ?? 80,\n questionCount: at.version.questionCount ?? at.questions.length + draft.questions.length,\n updater: user\n })\n return at.version.id\n}\n\n/** Transcribe an existing authorized video through the same tenant AI/metering boundary. */\nexport async function transcribeMaterial(unitId: string, context: any) {\n const at = await scope(unitId, context)\n if (at.unit.kind !== CourseUnitKind.VIDEO || !at.unit.durationSeconds || at.unit.durationSeconds > 3600 || Number(at.file.size) > 250 * 1024 * 1024)\n throw new Refusal('learning-ai-video-limit', {}, 'choose a video up to 60 minutes and 250 MB')\n const client = await aiClientFor({ domain: context.state.domain, user: context.state.user, modeKind: 'learning.material-transcription' })\n if (!client.transcribe) throw new Refusal('learning-ai-transcription-unavailable', {}, 'this AI agent cannot transcribe audio; use a speech-capable agent or supply captions')\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), 180_000)\n try {\n let audio: Buffer\n try { audio = await speechMp3(Buffer.from(await STORAGE.readFile(at.file.path)), controller.signal) }\n catch { throw new Refusal('learning-ai-audio-unreadable', {}, 'audio could not be extracted; check ffmpeg, video format and audio track, or provide captions') }\n const answer = await client.transcribe(audio, { signal: controller.signal })\n if (!answer.text?.trim() || !answer.segments?.length || answer.segments.some(s => !Number.isFinite(s.start) || !Number.isFinite(s.end) || s.start < 0 || s.end < s.start || s.end > at.unit.durationSeconds! + 5))\n throw new Refusal('learning-ai-audio-unreadable', {}, 'the audio transcript is empty or invalid')\n const captions = answer.segments.map(s => `[${s.start.toFixed(1)} - ${s.end.toFixed(1)} s] ${s.text.trim()}`).join('\\n')\n if (captions.length > 120_000) throw new Refusal('learning-ai-video-limit', {}, 'the transcript is too long; split the material')\n return { fileId: at.file.id, captions }\n } finally { clearTimeout(timer) }\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import { Enrollment } from './enrollment';
2
+ import { LearningAudienceInput, LearningTickResult, QuizAttemptView, QuizResultView } from './enrollment-type';
3
+ export declare class EnrollmentMutation {
4
+ recordLearningTick(enrollmentId: string, unitId: string, seconds: number, position: number, context: ResolverContext): Promise<LearningTickResult>;
5
+ acknowledgeEnrollment(id: string, context: ResolverContext): Promise<Enrollment>;
6
+ startQuizAttempt(enrollmentId: string, context: ResolverContext): Promise<QuizAttemptView>;
7
+ submitQuizAttempt(attemptId: string, answers: Record<string, string[]>, context: ResolverContext): Promise<QuizResultView>;
8
+ assignCourse(courseId: string, audiences: LearningAudienceInput[], dueOn: string, context: ResolverContext): Promise<Enrollment[]>;
9
+ cancelEnrollment(id: string, reason: string, context: ResolverContext): Promise<Enrollment>;
10
+ remindEnrollments(ids: string[], context: ResolverContext): Promise<number>;
11
+ }
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EnrollmentMutation = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const type_graphql_1 = require("type-graphql");
6
+ const shell_1 = require("@things-factory/shell");
7
+ const enrollment_1 = require("./enrollment");
8
+ const enrollment_service_1 = require("./enrollment-service");
9
+ const enrollment_remind_1 = require("./enrollment-remind");
10
+ const enrollment_type_1 = require("./enrollment-type");
11
+ /*
12
+ * The writes of enrolments (ADR-0194 decisions 4-8): a learner's ticks, acknowledgement and quiz (`learning` mutation,
13
+ * the task `learning.taking`, on their own enrolments only); a manager's assigning, cancelling and reminding (`course`
14
+ * mutation, `learning.managing`).
15
+ */
16
+ let EnrollmentMutation = class EnrollmentMutation {
17
+ async recordLearningTick(enrollmentId, unitId, seconds, position, context) {
18
+ return await (0, enrollment_service_1.recordTick)(enrollmentId, unitId, seconds, position, context);
19
+ }
20
+ async acknowledgeEnrollment(id, context) {
21
+ return await (0, enrollment_service_1.acknowledgeEnrollment)(id, context);
22
+ }
23
+ async startQuizAttempt(enrollmentId, context) {
24
+ const { attempt, questions } = await (0, enrollment_service_1.startQuizAttempt)(enrollmentId, context);
25
+ return { attemptId: attempt.id, questions };
26
+ }
27
+ async submitQuizAttempt(attemptId, answers, context) {
28
+ return await (0, enrollment_service_1.submitQuizAttempt)(attemptId, answers, context);
29
+ }
30
+ async assignCourse(courseId, audiences, dueOn, context) {
31
+ return await (0, enrollment_service_1.enroll)({ kind: enrollment_service_1.DIRECT_SOURCE_KIND, id: '' }, await (0, enrollment_service_1.learnersOf)(audiences, context), courseId, dueOn, context);
32
+ }
33
+ async cancelEnrollment(id, reason, context) {
34
+ return await (0, enrollment_service_1.cancelEnrollment)(id, reason, context);
35
+ }
36
+ async remindEnrollments(ids, context) {
37
+ return await (0, enrollment_remind_1.remindEnrollments)(ids, context);
38
+ }
39
+ };
40
+ exports.EnrollmentMutation = EnrollmentMutation;
41
+ tslib_1.__decorate([
42
+ (0, type_graphql_1.Directive)('@privilege(category: "learning", privilege: "mutation", domainOwnerGranted: true)'),
43
+ (0, type_graphql_1.Directive)('@transaction'),
44
+ (0, type_graphql_1.Mutation)(returns => enrollment_type_1.LearningTickResult, {
45
+ description: 'To record a tick of a unit looked at - the server counts the time since the last tick (15 seconds at most) and refuses a video position ahead of what was watched.'
46
+ }),
47
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('enrollmentId')),
48
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('unitId')),
49
+ tslib_1.__param(2, (0, type_graphql_1.Arg)('seconds', type => type_graphql_1.Int, { description: 'Seconds looked at since the last tick, as the screen says - it may lower what is counted, never raise it.' })),
50
+ tslib_1.__param(3, (0, type_graphql_1.Arg)('position', type => type_graphql_1.Int, { description: 'Where the video stands, in seconds - 0 for a document.' })),
51
+ tslib_1.__param(4, (0, type_graphql_1.Ctx)()),
52
+ tslib_1.__metadata("design:type", Function),
53
+ tslib_1.__metadata("design:paramtypes", [String, String, Number, Number, Object]),
54
+ tslib_1.__metadata("design:returntype", Promise)
55
+ ], EnrollmentMutation.prototype, "recordLearningTick", null);
56
+ tslib_1.__decorate([
57
+ (0, type_graphql_1.Directive)('@privilege(category: "learning", privilege: "mutation", domainOwnerGranted: true)'),
58
+ (0, type_graphql_1.Directive)('@transaction'),
59
+ (0, type_graphql_1.Mutation)(returns => enrollment_1.Enrollment, { description: "To confirm the course's acknowledgement." }),
60
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
61
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
62
+ tslib_1.__metadata("design:type", Function),
63
+ tslib_1.__metadata("design:paramtypes", [String, Object]),
64
+ tslib_1.__metadata("design:returntype", Promise)
65
+ ], EnrollmentMutation.prototype, "acknowledgeEnrollment", null);
66
+ tslib_1.__decorate([
67
+ (0, type_graphql_1.Directive)('@privilege(category: "learning", privilege: "mutation", domainOwnerGranted: true)'),
68
+ (0, type_graphql_1.Directive)('@transaction'),
69
+ (0, type_graphql_1.Mutation)(returns => enrollment_type_1.QuizAttemptView, { description: 'To start a quiz attempt - the questions and choices drawn and shuffled, never the answers.' }),
70
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('enrollmentId')),
71
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
72
+ tslib_1.__metadata("design:type", Function),
73
+ tslib_1.__metadata("design:paramtypes", [String, Object]),
74
+ tslib_1.__metadata("design:returntype", Promise)
75
+ ], EnrollmentMutation.prototype, "startQuizAttempt", null);
76
+ tslib_1.__decorate([
77
+ (0, type_graphql_1.Directive)('@privilege(category: "learning", privilege: "mutation", domainOwnerGranted: true)'),
78
+ (0, type_graphql_1.Directive)('@transaction'),
79
+ (0, type_graphql_1.Mutation)(returns => enrollment_type_1.QuizResultView, { description: "To send an attempt's answers - scored on the server; the score and whether it passed come back, never the right answers." }),
80
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('attemptId')),
81
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('answers', type => shell_1.ScalarObject, { description: 'The choice ids picked, by question id.' })),
82
+ tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
83
+ tslib_1.__metadata("design:type", Function),
84
+ tslib_1.__metadata("design:paramtypes", [String, Object, Object]),
85
+ tslib_1.__metadata("design:returntype", Promise)
86
+ ], EnrollmentMutation.prototype, "submitQuizAttempt", null);
87
+ tslib_1.__decorate([
88
+ (0, type_graphql_1.Directive)('@privilege(category: "course", privilege: "mutation", domainOwnerGranted: true)'),
89
+ (0, type_graphql_1.Directive)('@transaction'),
90
+ (0, type_graphql_1.Mutation)(returns => [enrollment_1.Enrollment], { description: 'To assign a course to an audience - the people it names now - to be completed by a day; each gets a to-do.' }),
91
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('courseId')),
92
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('audiences', type => [enrollment_type_1.LearningAudienceInput])),
93
+ tslib_1.__param(2, (0, type_graphql_1.Arg)('dueOn', { nullable: true, description: 'YYYY-MM-DD.' })),
94
+ tslib_1.__param(3, (0, type_graphql_1.Ctx)()),
95
+ tslib_1.__metadata("design:type", Function),
96
+ tslib_1.__metadata("design:paramtypes", [String, Array, String, Object]),
97
+ tslib_1.__metadata("design:returntype", Promise)
98
+ ], EnrollmentMutation.prototype, "assignCourse", null);
99
+ tslib_1.__decorate([
100
+ (0, type_graphql_1.Directive)('@privilege(category: "course", privilege: "mutation", domainOwnerGranted: true)'),
101
+ (0, type_graphql_1.Directive)('@transaction'),
102
+ (0, type_graphql_1.Mutation)(returns => enrollment_1.Enrollment, { description: 'To cancel an enrolment, with the reason - a completed one stays on record as cancelled, and whoever asked for it is told.' }),
103
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
104
+ tslib_1.__param(1, (0, type_graphql_1.Arg)('reason')),
105
+ tslib_1.__param(2, (0, type_graphql_1.Ctx)()),
106
+ tslib_1.__metadata("design:type", Function),
107
+ tslib_1.__metadata("design:paramtypes", [String, String, Object]),
108
+ tslib_1.__metadata("design:returntype", Promise)
109
+ ], EnrollmentMutation.prototype, "cancelEnrollment", null);
110
+ tslib_1.__decorate([
111
+ (0, type_graphql_1.Directive)('@privilege(category: "course", privilege: "mutation", domainOwnerGranted: true)'),
112
+ (0, type_graphql_1.Directive)('@transaction'),
113
+ (0, type_graphql_1.Mutation)(returns => type_graphql_1.Int, { description: 'To remind the learners of open enrolments - answers how many were reminded.' }),
114
+ tslib_1.__param(0, (0, type_graphql_1.Arg)('ids', type => [String])),
115
+ tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
116
+ tslib_1.__metadata("design:type", Function),
117
+ tslib_1.__metadata("design:paramtypes", [Array, Object]),
118
+ tslib_1.__metadata("design:returntype", Promise)
119
+ ], EnrollmentMutation.prototype, "remindEnrollments", null);
120
+ exports.EnrollmentMutation = EnrollmentMutation = tslib_1.__decorate([
121
+ (0, type_graphql_1.Resolver)(enrollment_1.Enrollment),
122
+ (0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
123
+ ], EnrollmentMutation);
124
+ //# sourceMappingURL=enrollment-mutation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enrollment-mutation.js","sourceRoot":"","sources":["../../../server/service/enrollment/enrollment-mutation.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Int, Mutation, Resolver, UseMiddleware } from 'type-graphql'\n\nimport { ScalarObject, SpeakRefusals } from '@things-factory/shell'\n\nimport { Enrollment } from './enrollment'\nimport { acknowledgeEnrollment, cancelEnrollment, DIRECT_SOURCE_KIND, enroll, learnersOf, recordTick, startQuizAttempt, submitQuizAttempt } from './enrollment-service'\nimport { remindEnrollments } from './enrollment-remind'\nimport { LearningAudienceInput, LearningTickResult, QuizAttemptView, QuizResultView } from './enrollment-type'\n\n\n/*\n * The writes of enrolments (ADR-0194 decisions 4-8): a learner's ticks, acknowledgement and quiz (`learning` mutation,\n * the task `learning.taking`, on their own enrolments only); a manager's assigning, cancelling and reminding (`course`\n * mutation, `learning.managing`).\n */\n@Resolver(Enrollment)\n@UseMiddleware(SpeakRefusals)\nexport class EnrollmentMutation {\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => LearningTickResult, {\n description: 'To record a tick of a unit looked at - the server counts the time since the last tick (15 seconds at most) and refuses a video position ahead of what was watched.'\n })\n async recordLearningTick(\n @Arg('enrollmentId') enrollmentId: string,\n @Arg('unitId') unitId: string,\n @Arg('seconds', type => Int, { description: 'Seconds looked at since the last tick, as the screen says - it may lower what is counted, never raise it.' }) seconds: number,\n @Arg('position', type => Int, { description: 'Where the video stands, in seconds - 0 for a document.' }) position: number,\n @Ctx() context: ResolverContext\n ): Promise<LearningTickResult> {\n return await recordTick(enrollmentId, unitId, seconds, position, context)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Enrollment, { description: \"To confirm the course's acknowledgement.\" })\n async acknowledgeEnrollment(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Enrollment> {\n return await acknowledgeEnrollment(id, context)\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => QuizAttemptView, { description: 'To start a quiz attempt - the questions and choices drawn and shuffled, never the answers.' })\n async startQuizAttempt(@Arg('enrollmentId') enrollmentId: string, @Ctx() context: ResolverContext): Promise<QuizAttemptView> {\n const { attempt, questions } = await startQuizAttempt(enrollmentId, context)\n return { attemptId: attempt.id, questions }\n }\n\n @Directive('@privilege(category: \"learning\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => QuizResultView, { description: \"To send an attempt's answers - scored on the server; the score and whether it passed come back, never the right answers.\" })\n async submitQuizAttempt(\n @Arg('attemptId') attemptId: string,\n @Arg('answers', type => ScalarObject, { description: 'The choice ids picked, by question id.' }) answers: Record<string, string[]>,\n @Ctx() context: ResolverContext\n ): Promise<QuizResultView> {\n return await submitQuizAttempt(attemptId, answers, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => [Enrollment], { description: 'To assign a course to an audience - the people it names now - to be completed by a day; each gets a to-do.' })\n async assignCourse(\n @Arg('courseId') courseId: string,\n @Arg('audiences', type => [LearningAudienceInput]) audiences: LearningAudienceInput[],\n @Arg('dueOn', { nullable: true, description: 'YYYY-MM-DD.' }) dueOn: string,\n @Ctx() context: ResolverContext\n ): Promise<Enrollment[]> {\n return await enroll({ kind: DIRECT_SOURCE_KIND, id: '' }, await learnersOf(audiences, context), courseId, dueOn, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Enrollment, { description: 'To cancel an enrolment, with the reason - a completed one stays on record as cancelled, and whoever asked for it is told.' })\n async cancelEnrollment(@Arg('id') id: string, @Arg('reason') reason: string, @Ctx() context: ResolverContext): Promise<Enrollment> {\n return await cancelEnrollment(id, reason, context)\n }\n\n @Directive('@privilege(category: \"course\", privilege: \"mutation\", domainOwnerGranted: true)')\n @Directive('@transaction')\n @Mutation(returns => Int, { description: 'To remind the learners of open enrolments - answers how many were reminded.' })\n async remindEnrollments(@Arg('ids', type => [String]) ids: string[], @Ctx() context: ResolverContext): Promise<number> {\n return await remindEnrollments(ids, context)\n }\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import { User } from '@things-factory/auth-base';
2
+ import { ListParam, ListValueCount } from '@things-factory/shell';
3
+ import { Course } from '../course/course';
4
+ import { CourseVersion } from '../course/course-version';
5
+ import { Enrollment } from './enrollment';
6
+ import { EnrollmentStatusList, EnrollmentStatusRow, LearningContent, LearningEnrollmentList, LearningUnit } from './enrollment-type';
7
+ export declare class EnrollmentQuery {
8
+ myEnrollments(context: ResolverContext): Promise<Enrollment[]>;
9
+ myEnrollment(id: string, context: ResolverContext): Promise<Enrollment>;
10
+ teamEnrollmentStatus(params: ListParam, context: ResolverContext): Promise<EnrollmentStatusList>;
11
+ enrollmentStatus(params: ListParam, context: ResolverContext): Promise<EnrollmentStatusList>;
12
+ enrollmentStateCounts(params: ListParam, context: ResolverContext): Promise<ListValueCount[]>;
13
+ completionRegister(courseId: string, context: ResolverContext): Promise<EnrollmentStatusRow[]>;
14
+ learningEnrollments(params: ListParam, context: ResolverContext): Promise<LearningEnrollmentList>;
15
+ learningContent(enrollmentId: string, versionId: string, context: ResolverContext): Promise<LearningContent>;
16
+ course(enrollment: Enrollment): Promise<Course | null>;
17
+ user(enrollment: Enrollment): Promise<User | null>;
18
+ version(enrollment: Enrollment, context: ResolverContext): Promise<CourseVersion | null>;
19
+ state(enrollment: Enrollment, context: ResolverContext): string;
20
+ units(enrollment: Enrollment, context: ResolverContext): Promise<LearningUnit[]>;
21
+ missing(enrollment: Enrollment, context: ResolverContext): Promise<string[]>;
22
+ attemptsLeft(enrollment: Enrollment, context: ResolverContext): Promise<number | null>;
23
+ quizSize(enrollment: Enrollment, context: ResolverContext): Promise<number>;
24
+ }