@things-factory/learning 0.0.0-stage → 10.1.260
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/bootstrap.d.ts +2 -0
- package/dist-client/bootstrap.js +4 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/components/learning-assign-form.d.ts +24 -0
- package/dist-client/components/learning-assign-form.js +77 -0
- package/dist-client/components/learning-assign-form.js.map +1 -0
- package/dist-client/components/learning-course-form.d.ts +29 -0
- package/dist-client/components/learning-course-form.js +104 -0
- package/dist-client/components/learning-course-form.js.map +1 -0
- package/dist-client/components/learning-question-form.d.ts +33 -0
- package/dist-client/components/learning-question-form.js +142 -0
- package/dist-client/components/learning-question-form.js.map +1 -0
- package/dist-client/components/learning-todo.d.ts +12 -0
- package/dist-client/components/learning-todo.js +43 -0
- package/dist-client/components/learning-todo.js.map +1 -0
- package/dist-client/components/learning-unit-form.d.ts +41 -0
- package/dist-client/components/learning-unit-form.js +143 -0
- package/dist-client/components/learning-unit-form.js.map +1 -0
- package/dist-client/index.d.ts +2 -0
- package/dist-client/index.js +3 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/pages/learning-course-editor.d.ts +62 -0
- package/dist-client/pages/learning-course-editor.js +463 -0
- package/dist-client/pages/learning-course-editor.js.map +1 -0
- package/dist-client/pages/learning-course-list.d.ts +37 -0
- package/dist-client/pages/learning-course-list.js +88 -0
- package/dist-client/pages/learning-course-list.js.map +1 -0
- package/dist-client/pages/learning-enrollment-status.d.ts +53 -0
- package/dist-client/pages/learning-enrollment-status.js +228 -0
- package/dist-client/pages/learning-enrollment-status.js.map +1 -0
- package/dist-client/pages/learning-view.d.ts +52 -0
- package/dist-client/pages/learning-view.js +371 -0
- package/dist-client/pages/learning-view.js.map +1 -0
- package/dist-client/pages/my-learning.d.ts +22 -0
- package/dist-client/pages/my-learning.js +110 -0
- package/dist-client/pages/my-learning.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +21 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-client/utils/learning-api.d.ts +16 -0
- package/dist-client/utils/learning-api.js +36 -0
- package/dist-client/utils/learning-api.js.map +1 -0
- package/dist-client/utils/learning-words.d.ts +20 -0
- package/dist-client/utils/learning-words.js +22 -0
- package/dist-client/utils/learning-words.js.map +1 -0
- package/dist-server/activities.d.ts +38 -0
- package/dist-server/activities.js +42 -0
- package/dist-server/activities.js.map +1 -0
- package/dist-server/ai-reads.d.ts +9 -0
- package/dist-server/ai-reads.js +70 -0
- package/dist-server/ai-reads.js.map +1 -0
- package/dist-server/attachment-reader.d.ts +1 -0
- package/dist-server/attachment-reader.js +23 -0
- package/dist-server/attachment-reader.js.map +1 -0
- package/dist-server/index.d.ts +4 -0
- package/dist-server/index.js +33 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/learning-subjects.d.ts +10 -0
- package/dist-server/learning-subjects.js +31 -0
- package/dist-server/learning-subjects.js.map +1 -0
- package/dist-server/role-tasks.d.ts +2 -0
- package/dist-server/role-tasks.js +17 -0
- package/dist-server/role-tasks.js.map +1 -0
- package/dist-server/service/course/course-mutation.d.ts +19 -0
- package/dist-server/service/course/course-mutation.js +176 -0
- package/dist-server/service/course/course-mutation.js.map +1 -0
- package/dist-server/service/course/course-query.d.ts +26 -0
- package/dist-server/service/course/course-query.js +172 -0
- package/dist-server/service/course/course-query.js.map +1 -0
- package/dist-server/service/course/course-question.d.ts +41 -0
- package/dist-server/service/course/course-question.js +116 -0
- package/dist-server/service/course/course-question.js.map +1 -0
- package/dist-server/service/course/course-rules.d.ts +49 -0
- package/dist-server/service/course/course-rules.js +89 -0
- package/dist-server/service/course/course-rules.js.map +1 -0
- package/dist-server/service/course/course-service.d.ts +71 -0
- package/dist-server/service/course/course-service.js +402 -0
- package/dist-server/service/course/course-service.js.map +1 -0
- package/dist-server/service/course/course-type.d.ts +55 -0
- package/dist-server/service/course/course-type.js +198 -0
- package/dist-server/service/course/course-type.js.map +1 -0
- package/dist-server/service/course/course-unit.d.ts +36 -0
- package/dist-server/service/course/course-unit.js +123 -0
- package/dist-server/service/course/course-unit.js.map +1 -0
- package/dist-server/service/course/course-version.d.ts +45 -0
- package/dist-server/service/course/course-version.js +151 -0
- package/dist-server/service/course/course-version.js.map +1 -0
- package/dist-server/service/course/course.d.ts +23 -0
- package/dist-server/service/course/course.js +92 -0
- package/dist-server/service/course/course.js.map +1 -0
- package/dist-server/service/course/index.d.ts +14 -0
- package/dist-server/service/course/index.js +24 -0
- package/dist-server/service/course/index.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-mutation.d.ts +11 -0
- package/dist-server/service/enrollment/enrollment-mutation.js +124 -0
- package/dist-server/service/enrollment/enrollment-mutation.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-query.d.ts +23 -0
- package/dist-server/service/enrollment/enrollment-query.js +245 -0
- package/dist-server/service/enrollment/enrollment-query.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-remind.d.ts +1 -0
- package/dist-server/service/enrollment/enrollment-remind.js +45 -0
- package/dist-server/service/enrollment/enrollment-remind.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-service.d.ts +110 -0
- package/dist-server/service/enrollment/enrollment-service.js +417 -0
- package/dist-server/service/enrollment/enrollment-service.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-status.d.ts +40 -0
- package/dist-server/service/enrollment/enrollment-status.js +135 -0
- package/dist-server/service/enrollment/enrollment-status.js.map +1 -0
- package/dist-server/service/enrollment/enrollment-type.d.ts +95 -0
- package/dist-server/service/enrollment/enrollment-type.js +341 -0
- package/dist-server/service/enrollment/enrollment-type.js.map +1 -0
- package/dist-server/service/enrollment/enrollment.d.ts +52 -0
- package/dist-server/service/enrollment/enrollment.js +186 -0
- package/dist-server/service/enrollment/enrollment.js.map +1 -0
- package/dist-server/service/enrollment/index.d.ts +15 -0
- package/dist-server/service/enrollment/index.js +26 -0
- package/dist-server/service/enrollment/index.js.map +1 -0
- package/dist-server/service/enrollment/learning-ai.d.ts +38 -0
- package/dist-server/service/enrollment/learning-ai.js +96 -0
- package/dist-server/service/enrollment/learning-ai.js.map +1 -0
- package/dist-server/service/enrollment/learning-documents.d.ts +7 -0
- package/dist-server/service/enrollment/learning-documents.js +160 -0
- package/dist-server/service/enrollment/learning-documents.js.map +1 -0
- package/dist-server/service/enrollment/learning-outcome.d.ts +10 -0
- package/dist-server/service/enrollment/learning-outcome.js +16 -0
- package/dist-server/service/enrollment/learning-outcome.js.map +1 -0
- package/dist-server/service/enrollment/progress-rules.d.ts +94 -0
- package/dist-server/service/enrollment/progress-rules.js +97 -0
- package/dist-server/service/enrollment/progress-rules.js.map +1 -0
- package/dist-server/service/enrollment/quiz-attempt.d.ts +34 -0
- package/dist-server/service/enrollment/quiz-attempt.js +105 -0
- package/dist-server/service/enrollment/quiz-attempt.js.map +1 -0
- package/dist-server/service/enrollment/unit-progress.d.ts +30 -0
- package/dist-server/service/enrollment/unit-progress.js +114 -0
- package/dist-server/service/enrollment/unit-progress.js.map +1 -0
- package/dist-server/service/index.d.ts +14 -0
- package/dist-server/service/index.js +43 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +56 -4
- package/things-factory.config.js +14 -0
- package/translations/en.json +255 -0
- package/translations/es.json +255 -0
- package/translations/ja.json +255 -0
- package/translations/ko.json +255 -0
- package/translations/ms.json +255 -0
- package/translations/zh.json +255 -0
- package/README.md +0 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Attachment } from '@things-factory/attachment-base';
|
|
2
|
+
import { User } from '@things-factory/auth-base';
|
|
3
|
+
import { ListParam } from '@things-factory/shell';
|
|
4
|
+
import { Course } from './course';
|
|
5
|
+
import { CourseQuestion } from './course-question';
|
|
6
|
+
import { CourseUnit } from './course-unit';
|
|
7
|
+
import { CourseVersion } from './course-version';
|
|
8
|
+
import { CourseChoiceKey, CourseList } from './course-type';
|
|
9
|
+
export declare class CourseQuery {
|
|
10
|
+
courses(params: ListParam, context: ResolverContext): Promise<CourseList>;
|
|
11
|
+
course(id: string, context: ResolverContext): Promise<Course | null>;
|
|
12
|
+
courseVersion(id: string, context: ResolverContext): Promise<CourseVersion | null>;
|
|
13
|
+
versions(course: Course, context: ResolverContext): Promise<CourseVersion[]>;
|
|
14
|
+
publishedVersion(course: Course, context: ResolverContext): Promise<CourseVersion | null>;
|
|
15
|
+
manager(course: Course): Promise<User | null>;
|
|
16
|
+
}
|
|
17
|
+
export declare class CourseVersionFields {
|
|
18
|
+
units(version: CourseVersion, context: ResolverContext): Promise<CourseUnit[]>;
|
|
19
|
+
questions(version: CourseVersion, context: ResolverContext): Promise<CourseQuestion[]>;
|
|
20
|
+
}
|
|
21
|
+
export declare class CourseUnitFields {
|
|
22
|
+
file(unit: CourseUnit, context: ResolverContext): Promise<Attachment | null>;
|
|
23
|
+
}
|
|
24
|
+
export declare class CourseQuestionFields {
|
|
25
|
+
choices(question: CourseQuestion, context: ResolverContext): Promise<CourseChoiceKey[]>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CourseQuestionFields = exports.CourseUnitFields = exports.CourseVersionFields = exports.CourseQuery = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
const attachment_base_1 = require("@things-factory/attachment-base");
|
|
7
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
8
|
+
const shell_1 = require("@things-factory/shell");
|
|
9
|
+
const course_1 = require("./course");
|
|
10
|
+
const course_question_1 = require("./course-question");
|
|
11
|
+
const course_unit_1 = require("./course-unit");
|
|
12
|
+
const course_version_1 = require("./course-version");
|
|
13
|
+
const course_type_1 = require("./course-type");
|
|
14
|
+
const course_service_1 = require("./course-service");
|
|
15
|
+
/*
|
|
16
|
+
* The managers' reads of courses (ADR-0194 decisions 2-4, 10, 11) - `course` query, the task `learning.managing`. A learner
|
|
17
|
+
* reads a course through their enrolment (enrollment-query.ts), never these.
|
|
18
|
+
*/
|
|
19
|
+
let CourseQuery = class CourseQuery {
|
|
20
|
+
async courses(params, context) {
|
|
21
|
+
const { domain } = context.state;
|
|
22
|
+
const qb = (0, shell_1.getQueryBuilderFromListParams)({ domain, params, repository: (0, shell_1.getRepository)(course_1.Course), alias: 'C', searchables: ['name', 'description', 'category'] });
|
|
23
|
+
if (!params?.sortings?.length)
|
|
24
|
+
qb.orderBy('C.name', 'ASC');
|
|
25
|
+
const [items, total] = await qb.getManyAndCount();
|
|
26
|
+
return { items, total };
|
|
27
|
+
}
|
|
28
|
+
async course(id, context) {
|
|
29
|
+
return await (0, shell_1.getRepository)(course_1.Course).findOne({ where: { id, domain: { id: context.state.domain.id } } });
|
|
30
|
+
}
|
|
31
|
+
async courseVersion(id, context) {
|
|
32
|
+
return await (0, shell_1.getRepository)(course_version_1.CourseVersion).findOne({ where: { id, domain: { id: context.state.domain.id } } });
|
|
33
|
+
}
|
|
34
|
+
async versions(course, context) {
|
|
35
|
+
if (!(await (0, course_service_1.holds)(context, 'course', 'query')))
|
|
36
|
+
return [];
|
|
37
|
+
return await (0, shell_1.getRepository)(course_version_1.CourseVersion).find({ where: { course: { id: course.id } }, order: { number: 'DESC' } });
|
|
38
|
+
}
|
|
39
|
+
async publishedVersion(course, context) {
|
|
40
|
+
return await (0, course_service_1.publishedVersionOf)((0, course_service_1.learningManagerOf)(context), { id: course.domainId }, course.id);
|
|
41
|
+
}
|
|
42
|
+
async manager(course) {
|
|
43
|
+
return course.managerId ? await (0, shell_1.getRepository)(auth_base_1.User).findOneBy({ id: course.managerId }) : null;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
exports.CourseQuery = CourseQuery;
|
|
47
|
+
tslib_1.__decorate([
|
|
48
|
+
(0, type_graphql_1.Directive)('@privilege(category: "course", privilege: "query", domainOwnerGranted: true)'),
|
|
49
|
+
(0, type_graphql_1.Query)(returns => course_type_1.CourseList, { description: 'The courses of the domain.' }),
|
|
50
|
+
tslib_1.__param(0, (0, type_graphql_1.Args)(type => shell_1.ListParam)),
|
|
51
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
52
|
+
tslib_1.__metadata("design:type", Function),
|
|
53
|
+
tslib_1.__metadata("design:paramtypes", [shell_1.ListParam, Object]),
|
|
54
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
55
|
+
], CourseQuery.prototype, "courses", null);
|
|
56
|
+
tslib_1.__decorate([
|
|
57
|
+
(0, type_graphql_1.Directive)('@privilege(category: "course", privilege: "query", domainOwnerGranted: true)'),
|
|
58
|
+
(0, type_graphql_1.Query)(returns => course_1.Course, { nullable: true, description: 'One course.' }),
|
|
59
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
|
|
60
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
61
|
+
tslib_1.__metadata("design:type", Function),
|
|
62
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
63
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
64
|
+
], CourseQuery.prototype, "course", null);
|
|
65
|
+
tslib_1.__decorate([
|
|
66
|
+
(0, type_graphql_1.Directive)('@privilege(category: "course", privilege: "query", domainOwnerGranted: true)'),
|
|
67
|
+
(0, type_graphql_1.Query)(returns => course_version_1.CourseVersion, { nullable: true, description: 'One version of a course, with its units and questions.' }),
|
|
68
|
+
tslib_1.__param(0, (0, type_graphql_1.Arg)('id')),
|
|
69
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
70
|
+
tslib_1.__metadata("design:type", Function),
|
|
71
|
+
tslib_1.__metadata("design:paramtypes", [String, Object]),
|
|
72
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
73
|
+
], CourseQuery.prototype, "courseVersion", null);
|
|
74
|
+
tslib_1.__decorate([
|
|
75
|
+
(0, type_graphql_1.FieldResolver)(type => [course_version_1.CourseVersion], { description: 'Its versions, the newest first - for a manager; nothing for anyone else.' }),
|
|
76
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
77
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
78
|
+
tslib_1.__metadata("design:type", Function),
|
|
79
|
+
tslib_1.__metadata("design:paramtypes", [course_1.Course, Object]),
|
|
80
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
81
|
+
], CourseQuery.prototype, "versions", null);
|
|
82
|
+
tslib_1.__decorate([
|
|
83
|
+
(0, type_graphql_1.FieldResolver)(type => course_version_1.CourseVersion, { nullable: true, description: 'The version learners take now.' }),
|
|
84
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
85
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
86
|
+
tslib_1.__metadata("design:type", Function),
|
|
87
|
+
tslib_1.__metadata("design:paramtypes", [course_1.Course, Object]),
|
|
88
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
89
|
+
], CourseQuery.prototype, "publishedVersion", null);
|
|
90
|
+
tslib_1.__decorate([
|
|
91
|
+
(0, type_graphql_1.FieldResolver)(type => auth_base_1.User, { nullable: true, description: 'Who keeps it.' }),
|
|
92
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
93
|
+
tslib_1.__metadata("design:type", Function),
|
|
94
|
+
tslib_1.__metadata("design:paramtypes", [course_1.Course]),
|
|
95
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
96
|
+
], CourseQuery.prototype, "manager", null);
|
|
97
|
+
exports.CourseQuery = CourseQuery = tslib_1.__decorate([
|
|
98
|
+
(0, type_graphql_1.Resolver)(course_1.Course),
|
|
99
|
+
(0, type_graphql_1.UseMiddleware)(shell_1.SpeakRefusals)
|
|
100
|
+
], CourseQuery);
|
|
101
|
+
let CourseVersionFields = class CourseVersionFields {
|
|
102
|
+
async units(version, context) {
|
|
103
|
+
if (!(await (0, course_service_1.mayReadVersion)(context, version)))
|
|
104
|
+
return [];
|
|
105
|
+
return await (0, course_service_1.unitsOf)((0, course_service_1.learningManagerOf)(context), version.id);
|
|
106
|
+
}
|
|
107
|
+
async questions(version, context) {
|
|
108
|
+
if (!(await (0, course_service_1.holds)(context, 'course', 'query')))
|
|
109
|
+
return [];
|
|
110
|
+
return await (0, course_service_1.questionsOf)((0, course_service_1.learningManagerOf)(context), version.id);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
exports.CourseVersionFields = CourseVersionFields;
|
|
114
|
+
tslib_1.__decorate([
|
|
115
|
+
(0, type_graphql_1.FieldResolver)(type => [course_unit_1.CourseUnit], { description: 'Its units, in order - for a manager, or a learner whose enrolment shows this version.' }),
|
|
116
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
117
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
118
|
+
tslib_1.__metadata("design:type", Function),
|
|
119
|
+
tslib_1.__metadata("design:paramtypes", [course_version_1.CourseVersion, Object]),
|
|
120
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
121
|
+
], CourseVersionFields.prototype, "units", null);
|
|
122
|
+
tslib_1.__decorate([
|
|
123
|
+
(0, type_graphql_1.FieldResolver)(type => [course_question_1.CourseQuestion], { description: 'Its questions, in order - for a manager; nothing for anyone else.' }),
|
|
124
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
125
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
126
|
+
tslib_1.__metadata("design:type", Function),
|
|
127
|
+
tslib_1.__metadata("design:paramtypes", [course_version_1.CourseVersion, Object]),
|
|
128
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
129
|
+
], CourseVersionFields.prototype, "questions", null);
|
|
130
|
+
exports.CourseVersionFields = CourseVersionFields = tslib_1.__decorate([
|
|
131
|
+
(0, type_graphql_1.Resolver)(course_version_1.CourseVersion)
|
|
132
|
+
], CourseVersionFields);
|
|
133
|
+
let CourseUnitFields = class CourseUnitFields {
|
|
134
|
+
async file(unit, context) {
|
|
135
|
+
const version = await (0, course_service_1.learningManagerOf)(context).getRepository(course_version_1.CourseVersion).findOne({ where: { id: unit.versionId }, withDeleted: true });
|
|
136
|
+
if (!(await (0, course_service_1.mayReadVersion)(context, version)))
|
|
137
|
+
return null;
|
|
138
|
+
return (await (0, course_service_1.unitFilesOf)(context.state.domain, [unit.id])).get(unit.id) ?? null;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
exports.CourseUnitFields = CourseUnitFields;
|
|
142
|
+
tslib_1.__decorate([
|
|
143
|
+
(0, type_graphql_1.FieldResolver)(type => attachment_base_1.Attachment, { nullable: true, description: "A video's or a PDF's file." }),
|
|
144
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
145
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
146
|
+
tslib_1.__metadata("design:type", Function),
|
|
147
|
+
tslib_1.__metadata("design:paramtypes", [course_unit_1.CourseUnit, Object]),
|
|
148
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
149
|
+
], CourseUnitFields.prototype, "file", null);
|
|
150
|
+
exports.CourseUnitFields = CourseUnitFields = tslib_1.__decorate([
|
|
151
|
+
(0, type_graphql_1.Resolver)(course_unit_1.CourseUnit)
|
|
152
|
+
], CourseUnitFields);
|
|
153
|
+
let CourseQuestionFields = class CourseQuestionFields {
|
|
154
|
+
async choices(question, context) {
|
|
155
|
+
if (!(await (0, course_service_1.holds)(context, 'course', 'query')))
|
|
156
|
+
return [];
|
|
157
|
+
return question.choices ?? [];
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
exports.CourseQuestionFields = CourseQuestionFields;
|
|
161
|
+
tslib_1.__decorate([
|
|
162
|
+
(0, type_graphql_1.FieldResolver)(type => [course_type_1.CourseChoiceKey], { description: 'Its choices with which are right - for a manager only.' }),
|
|
163
|
+
tslib_1.__param(0, (0, type_graphql_1.Root)()),
|
|
164
|
+
tslib_1.__param(1, (0, type_graphql_1.Ctx)()),
|
|
165
|
+
tslib_1.__metadata("design:type", Function),
|
|
166
|
+
tslib_1.__metadata("design:paramtypes", [course_question_1.CourseQuestion, Object]),
|
|
167
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
168
|
+
], CourseQuestionFields.prototype, "choices", null);
|
|
169
|
+
exports.CourseQuestionFields = CourseQuestionFields = tslib_1.__decorate([
|
|
170
|
+
(0, type_graphql_1.Resolver)(course_question_1.CourseQuestion)
|
|
171
|
+
], CourseQuestionFields);
|
|
172
|
+
//# sourceMappingURL=course-query.js.map
|
|
@@ -0,0 +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"]}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { User } from '@things-factory/auth-base';
|
|
2
|
+
import { Domain } from '@things-factory/shell';
|
|
3
|
+
import { CourseVersion } from './course-version';
|
|
4
|
+
/** How a question is answered. */
|
|
5
|
+
export declare enum CourseQuestionKind {
|
|
6
|
+
SINGLE = "SINGLE",
|
|
7
|
+
MULTIPLE = "MULTIPLE",
|
|
8
|
+
TRUE_FALSE = "TRUE_FALSE"
|
|
9
|
+
}
|
|
10
|
+
/** One choice of a question, with whether it is right - kept in the row, never sent to a learner. */
|
|
11
|
+
export interface CourseChoice {
|
|
12
|
+
id: string;
|
|
13
|
+
text: string;
|
|
14
|
+
correct: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 문항 (ADR-0194 decision 4) - one question of a version's quiz: its choices with which are right, and why. Whether a
|
|
18
|
+
* choice is right is read by the server's scoring and the managers' editor only; a learner is drawn the question without
|
|
19
|
+
* it. One the assistant drafted stays `drafted` until a manager looks it over (decision 12).
|
|
20
|
+
*/
|
|
21
|
+
export declare class CourseQuestion {
|
|
22
|
+
readonly id: string;
|
|
23
|
+
domain?: Domain;
|
|
24
|
+
domainId?: string;
|
|
25
|
+
version?: CourseVersion;
|
|
26
|
+
versionId?: string;
|
|
27
|
+
seq: number;
|
|
28
|
+
kind: CourseQuestionKind;
|
|
29
|
+
text: string;
|
|
30
|
+
/** The choices with which are right - not a field: a learner never reads it. */
|
|
31
|
+
choices: CourseChoice[];
|
|
32
|
+
explanation?: string;
|
|
33
|
+
drafted: boolean;
|
|
34
|
+
createdAt?: Date;
|
|
35
|
+
updatedAt?: Date;
|
|
36
|
+
deletedAt?: Date;
|
|
37
|
+
creator?: User;
|
|
38
|
+
creatorId?: string;
|
|
39
|
+
updater?: User;
|
|
40
|
+
updaterId?: string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CourseQuestion = exports.CourseQuestionKind = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const type_graphql_1 = require("type-graphql");
|
|
6
|
+
const typeorm_1 = require("typeorm");
|
|
7
|
+
const auth_base_1 = require("@things-factory/auth-base");
|
|
8
|
+
const shell_1 = require("@things-factory/shell");
|
|
9
|
+
const course_version_1 = require("./course-version");
|
|
10
|
+
/** How a question is answered. */
|
|
11
|
+
var CourseQuestionKind;
|
|
12
|
+
(function (CourseQuestionKind) {
|
|
13
|
+
CourseQuestionKind["SINGLE"] = "SINGLE";
|
|
14
|
+
CourseQuestionKind["MULTIPLE"] = "MULTIPLE";
|
|
15
|
+
CourseQuestionKind["TRUE_FALSE"] = "TRUE_FALSE";
|
|
16
|
+
})(CourseQuestionKind || (exports.CourseQuestionKind = CourseQuestionKind = {}));
|
|
17
|
+
(0, type_graphql_1.registerEnumType)(CourseQuestionKind, { name: 'CourseQuestionKind', description: 'SINGLE - one choice; MULTIPLE - every right choice; TRUE_FALSE - true or false.' });
|
|
18
|
+
/**
|
|
19
|
+
* 문항 (ADR-0194 decision 4) - one question of a version's quiz: its choices with which are right, and why. Whether a
|
|
20
|
+
* choice is right is read by the server's scoring and the managers' editor only; a learner is drawn the question without
|
|
21
|
+
* it. One the assistant drafted stays `drafted` until a manager looks it over (decision 12).
|
|
22
|
+
*/
|
|
23
|
+
let CourseQuestion = class CourseQuestion {
|
|
24
|
+
};
|
|
25
|
+
exports.CourseQuestion = CourseQuestion;
|
|
26
|
+
tslib_1.__decorate([
|
|
27
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
28
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { description: 'Question id.' }),
|
|
29
|
+
tslib_1.__metadata("design:type", String)
|
|
30
|
+
], CourseQuestion.prototype, "id", void 0);
|
|
31
|
+
tslib_1.__decorate([
|
|
32
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
33
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true, description: 'The domain.' }),
|
|
34
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
35
|
+
], CourseQuestion.prototype, "domain", void 0);
|
|
36
|
+
tslib_1.__decorate([
|
|
37
|
+
(0, typeorm_1.RelationId)((row) => row.domain),
|
|
38
|
+
tslib_1.__metadata("design:type", String)
|
|
39
|
+
], CourseQuestion.prototype, "domainId", void 0);
|
|
40
|
+
tslib_1.__decorate([
|
|
41
|
+
(0, typeorm_1.ManyToOne)(type => course_version_1.CourseVersion),
|
|
42
|
+
(0, type_graphql_1.Field)(type => course_version_1.CourseVersion, { nullable: true, description: 'The version it is part of.' }),
|
|
43
|
+
tslib_1.__metadata("design:type", course_version_1.CourseVersion)
|
|
44
|
+
], CourseQuestion.prototype, "version", void 0);
|
|
45
|
+
tslib_1.__decorate([
|
|
46
|
+
(0, typeorm_1.RelationId)((row) => row.version),
|
|
47
|
+
tslib_1.__metadata("design:type", String)
|
|
48
|
+
], CourseQuestion.prototype, "versionId", void 0);
|
|
49
|
+
tslib_1.__decorate([
|
|
50
|
+
(0, typeorm_1.Column)({ type: 'int' }),
|
|
51
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Its place in the version, from 1.' }),
|
|
52
|
+
tslib_1.__metadata("design:type", Number)
|
|
53
|
+
], CourseQuestion.prototype, "seq", void 0);
|
|
54
|
+
tslib_1.__decorate([
|
|
55
|
+
(0, typeorm_1.Column)(),
|
|
56
|
+
(0, type_graphql_1.Field)(type => CourseQuestionKind, { description: 'How it is answered.' }),
|
|
57
|
+
tslib_1.__metadata("design:type", String)
|
|
58
|
+
], CourseQuestion.prototype, "kind", void 0);
|
|
59
|
+
tslib_1.__decorate([
|
|
60
|
+
(0, typeorm_1.Column)({ type: 'text' }),
|
|
61
|
+
(0, type_graphql_1.Field)({ description: 'What it asks.' }),
|
|
62
|
+
tslib_1.__metadata("design:type", String)
|
|
63
|
+
], CourseQuestion.prototype, "text", void 0);
|
|
64
|
+
tslib_1.__decorate([
|
|
65
|
+
(0, typeorm_1.Column)({ type: 'simple-json' }),
|
|
66
|
+
tslib_1.__metadata("design:type", Array)
|
|
67
|
+
], CourseQuestion.prototype, "choices", void 0);
|
|
68
|
+
tslib_1.__decorate([
|
|
69
|
+
(0, typeorm_1.Column)({ type: 'text', nullable: true }),
|
|
70
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Why the answer is what it is.' }),
|
|
71
|
+
tslib_1.__metadata("design:type", String)
|
|
72
|
+
], CourseQuestion.prototype, "explanation", void 0);
|
|
73
|
+
tslib_1.__decorate([
|
|
74
|
+
(0, typeorm_1.Column)({ default: false }),
|
|
75
|
+
(0, type_graphql_1.Field)({ description: 'Drafted by the assistant and not looked over by a manager yet.' }),
|
|
76
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
77
|
+
], CourseQuestion.prototype, "drafted", void 0);
|
|
78
|
+
tslib_1.__decorate([
|
|
79
|
+
(0, typeorm_1.CreateDateColumn)(),
|
|
80
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Creation timestamp.' }),
|
|
81
|
+
tslib_1.__metadata("design:type", Date)
|
|
82
|
+
], CourseQuestion.prototype, "createdAt", void 0);
|
|
83
|
+
tslib_1.__decorate([
|
|
84
|
+
(0, typeorm_1.UpdateDateColumn)(),
|
|
85
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Last update timestamp.' }),
|
|
86
|
+
tslib_1.__metadata("design:type", Date)
|
|
87
|
+
], CourseQuestion.prototype, "updatedAt", void 0);
|
|
88
|
+
tslib_1.__decorate([
|
|
89
|
+
(0, typeorm_1.DeleteDateColumn)(),
|
|
90
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Soft delete timestamp.' }),
|
|
91
|
+
tslib_1.__metadata("design:type", Date)
|
|
92
|
+
], CourseQuestion.prototype, "deletedAt", void 0);
|
|
93
|
+
tslib_1.__decorate([
|
|
94
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
95
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'Who made it.' }),
|
|
96
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
97
|
+
], CourseQuestion.prototype, "creator", void 0);
|
|
98
|
+
tslib_1.__decorate([
|
|
99
|
+
(0, typeorm_1.RelationId)((row) => row.creator),
|
|
100
|
+
tslib_1.__metadata("design:type", String)
|
|
101
|
+
], CourseQuestion.prototype, "creatorId", void 0);
|
|
102
|
+
tslib_1.__decorate([
|
|
103
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
104
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'Who changed it last.' }),
|
|
105
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
106
|
+
], CourseQuestion.prototype, "updater", void 0);
|
|
107
|
+
tslib_1.__decorate([
|
|
108
|
+
(0, typeorm_1.RelationId)((row) => row.updater),
|
|
109
|
+
tslib_1.__metadata("design:type", String)
|
|
110
|
+
], CourseQuestion.prototype, "updaterId", void 0);
|
|
111
|
+
exports.CourseQuestion = CourseQuestion = tslib_1.__decorate([
|
|
112
|
+
(0, typeorm_1.Entity)({ name: 'learning_course_questions' }),
|
|
113
|
+
(0, typeorm_1.Index)('ix_learning_course_question_0', (row) => [row.domain, row.version, row.seq]),
|
|
114
|
+
(0, type_graphql_1.ObjectType)({ description: "A question of a course version's quiz - which choices are right is read only by managers." })
|
|
115
|
+
], CourseQuestion);
|
|
116
|
+
//# sourceMappingURL=course-question.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"course-question.js","sourceRoot":"","sources":["../../../server/service/course/course-question.ts"],"names":[],"mappings":";;;;AAAA,+CAA2E;AAC3E,qCAAoJ;AAEpJ,yDAAgD;AAChD,iDAA8C;AAE9C,qDAAgD;AAEhD,kCAAkC;AAClC,IAAY,kBAIX;AAJD,WAAY,kBAAkB;IAC5B,uCAAiB,CAAA;IACjB,2CAAqB,CAAA;IACrB,+CAAyB,CAAA;AAC3B,CAAC,EAJW,kBAAkB,kCAAlB,kBAAkB,QAI7B;AAED,IAAA,+BAAgB,EAAC,kBAAkB,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,WAAW,EAAE,iFAAiF,EAAE,CAAC,CAAA;AASpK;;;;GAIG;AAII,IAAM,cAAc,GAApB,MAAM,cAAc;CAoE1B,CAAA;AApEY,wCAAc;AAGhB;IAFR,IAAA,gCAAsB,EAAC,MAAM,CAAC;IAC9B,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,iBAAE,EAAE,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC;;0CAChC;AAInB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,CAAC;IACzB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,cAAM,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC;sCAC7D,cAAM;8CAAA;AAGf;IADC,IAAA,oBAAU,EAAC,CAAC,GAAmB,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC;;gDAC/B;AAIjB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,8BAAa,CAAC;IAChC,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,8BAAa,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,4BAA4B,EAAE,CAAC;sCAClF,8BAAa;+CAAA;AAGvB;IADC,IAAA,oBAAU,EAAC,CAAC,GAAmB,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;;iDAC/B;AAIlB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACvB,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAG,EAAE,EAAE,WAAW,EAAE,mCAAmC,EAAE,CAAC;;2CAC9D;AAIX;IAFC,IAAA,gBAAM,GAAE;IACR,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,kBAAkB,EAAE,EAAE,WAAW,EAAE,qBAAqB,EAAE,CAAC;;4CAClD;AAIxB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACxB,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC;;4CAC5B;AAIZ;IADC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,aAAa,EAAE,CAAC;;+CACT;AAIvB;IAFC,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACxC,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;;mDACpD;AAIpB;IAFC,IAAA,gBAAM,EAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAC1B,IAAA,oBAAK,EAAC,EAAE,WAAW,EAAE,gEAAgE,EAAE,CAAC;;+CACzE;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,qBAAqB,EAAE,CAAC;sCAClD,IAAI;iDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,wBAAwB,EAAE,CAAC;sCACrD,IAAI;iDAAA;AAIhB;IAFC,IAAA,0BAAgB,GAAE;IAClB,IAAA,oBAAK,EAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,wBAAwB,EAAE,CAAC;sCACrD,IAAI;iDAAA;AAIhB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC;sCAC3D,gBAAI;+CAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,GAAmB,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;;iDAC/B;AAIlB;IAFC,IAAA,mBAAS,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAC3C,IAAA,oBAAK,EAAC,IAAI,CAAC,EAAE,CAAC,gBAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,sBAAsB,EAAE,CAAC;sCACnE,gBAAI;+CAAA;AAGd;IADC,IAAA,oBAAU,EAAC,CAAC,GAAmB,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;;iDAC/B;yBAnEP,cAAc;IAH1B,IAAA,gBAAM,EAAC,EAAE,IAAI,EAAE,2BAA2B,EAAE,CAAC;IAC7C,IAAA,eAAK,EAAC,+BAA+B,EAAE,CAAC,GAAmB,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IACnG,IAAA,yBAAU,EAAC,EAAE,WAAW,EAAE,2FAA2F,EAAE,CAAC;GAC5G,cAAc,CAoE1B","sourcesContent":["import { Field, ID, Int, ObjectType, registerEnumType } from 'type-graphql'\nimport { Column, CreateDateColumn, DeleteDateColumn, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, UpdateDateColumn } from 'typeorm'\n\nimport { User } from '@things-factory/auth-base'\nimport { Domain } from '@things-factory/shell'\n\nimport { CourseVersion } from './course-version'\n\n/** How a question is answered. */\nexport enum CourseQuestionKind {\n SINGLE = 'SINGLE',\n MULTIPLE = 'MULTIPLE',\n TRUE_FALSE = 'TRUE_FALSE'\n}\n\nregisterEnumType(CourseQuestionKind, { name: 'CourseQuestionKind', description: 'SINGLE - one choice; MULTIPLE - every right choice; TRUE_FALSE - true or false.' })\n\n/** One choice of a question, with whether it is right - kept in the row, never sent to a learner. */\nexport interface CourseChoice {\n id: string\n text: string\n correct: boolean\n}\n\n/**\n * 문항 (ADR-0194 decision 4) - one question of a version's quiz: its choices with which are right, and why. Whether a\n * choice is right is read by the server's scoring and the managers' editor only; a learner is drawn the question without\n * it. One the assistant drafted stays `drafted` until a manager looks it over (decision 12).\n */\n@Entity({ name: 'learning_course_questions' })\n@Index('ix_learning_course_question_0', (row: CourseQuestion) => [row.domain, row.version, row.seq])\n@ObjectType({ description: \"A question of a course version's quiz - which choices are right is read only by managers.\" })\nexport class CourseQuestion {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Question id.' })\n readonly id: string\n\n @ManyToOne(type => Domain)\n @Field(type => Domain, { nullable: true, description: 'The domain.' })\n domain?: Domain\n\n @RelationId((row: CourseQuestion) => row.domain)\n domainId?: string\n\n @ManyToOne(type => CourseVersion)\n @Field(type => CourseVersion, { nullable: true, description: 'The version it is part of.' })\n version?: CourseVersion\n\n @RelationId((row: CourseQuestion) => row.version)\n versionId?: string\n\n @Column({ type: 'int' })\n @Field(type => Int, { description: 'Its place in the version, from 1.' })\n seq: number\n\n @Column()\n @Field(type => CourseQuestionKind, { description: 'How it is answered.' })\n kind: CourseQuestionKind\n\n @Column({ type: 'text' })\n @Field({ description: 'What it asks.' })\n text: string\n\n /** The choices with which are right - not a field: a learner never reads it. */\n @Column({ type: 'simple-json' })\n choices: CourseChoice[]\n\n @Column({ type: 'text', nullable: true })\n @Field({ nullable: true, description: 'Why the answer is what it is.' })\n explanation?: string\n\n @Column({ default: false })\n @Field({ description: 'Drafted by the assistant and not looked over by a manager yet.' })\n drafted: boolean\n\n @CreateDateColumn()\n @Field({ nullable: true, description: 'Creation timestamp.' })\n createdAt?: Date\n\n @UpdateDateColumn()\n @Field({ nullable: true, description: 'Last update timestamp.' })\n updatedAt?: Date\n\n @DeleteDateColumn()\n @Field({ nullable: true, description: 'Soft delete timestamp.' })\n deletedAt?: Date\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who made it.' })\n creator?: User\n\n @RelationId((row: CourseQuestion) => row.creator)\n creatorId?: string\n\n @ManyToOne(type => User, { nullable: true })\n @Field(type => User, { nullable: true, description: 'Who changed it last.' })\n updater?: User\n\n @RelationId((row: CourseQuestion) => row.updater)\n updaterId?: string\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface VersionShape {
|
|
2
|
+
status: string;
|
|
3
|
+
passScore?: number | null;
|
|
4
|
+
questionCount?: number | null;
|
|
5
|
+
attemptLimit?: number | null;
|
|
6
|
+
minSeconds?: number | null;
|
|
7
|
+
}
|
|
8
|
+
/** Why a version may not be written to - a published or closed one never changes (decision 2). */
|
|
9
|
+
export declare function versionEditRefusal(version: {
|
|
10
|
+
status: string;
|
|
11
|
+
} | null | undefined): string | null;
|
|
12
|
+
/** Why what a version says may not be kept. */
|
|
13
|
+
export declare function versionInputRefusal(input: VersionShape): string | null;
|
|
14
|
+
export interface UnitInput {
|
|
15
|
+
kind: string;
|
|
16
|
+
name?: string | null;
|
|
17
|
+
body?: string | null;
|
|
18
|
+
url?: string | null;
|
|
19
|
+
durationSeconds?: number | null;
|
|
20
|
+
minSeconds?: number | null;
|
|
21
|
+
}
|
|
22
|
+
/** Why a unit may not be kept as given. A video's or a PDF's file is attached apart - publishing asks for it. */
|
|
23
|
+
export declare function unitInputRefusal(unit: UnitInput): string | null;
|
|
24
|
+
export interface QuestionInput {
|
|
25
|
+
kind: string;
|
|
26
|
+
text?: string | null;
|
|
27
|
+
choices: {
|
|
28
|
+
text: string;
|
|
29
|
+
correct: boolean;
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
/** Why a question may not be kept: its text, two choices or more (TRUE_FALSE exactly two), and the right ones its kind allows. */
|
|
33
|
+
export declare function questionInputRefusal(question: QuestionInput): string | null;
|
|
34
|
+
export interface PublishFacts {
|
|
35
|
+
version: VersionShape;
|
|
36
|
+
units: {
|
|
37
|
+
kind: string;
|
|
38
|
+
durationSeconds?: number | null;
|
|
39
|
+
hasFile: boolean;
|
|
40
|
+
}[];
|
|
41
|
+
questions: {
|
|
42
|
+
drafted: boolean;
|
|
43
|
+
}[];
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 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.
|
|
48
|
+
*/
|
|
49
|
+
export declare function publishRefusal({ version, units, questions }: PublishFacts): string | null;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* What may be done to a course's versions (ADR-0194 decisions 2-4) - data in, the refusal's code out, no database.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.versionEditRefusal = versionEditRefusal;
|
|
7
|
+
exports.versionInputRefusal = versionInputRefusal;
|
|
8
|
+
exports.unitInputRefusal = unitInputRefusal;
|
|
9
|
+
exports.questionInputRefusal = questionInputRefusal;
|
|
10
|
+
exports.publishRefusal = publishRefusal;
|
|
11
|
+
/** Why a version may not be written to - a published or closed one never changes (decision 2). */
|
|
12
|
+
function versionEditRefusal(version) {
|
|
13
|
+
if (!version)
|
|
14
|
+
return 'learning-version-not-found';
|
|
15
|
+
return version.status === 'DRAFT' ? null : 'learning-version-not-draft';
|
|
16
|
+
}
|
|
17
|
+
/** Why what a version says may not be kept. */
|
|
18
|
+
function versionInputRefusal(input) {
|
|
19
|
+
if (input.minSeconds != null && (!Number.isInteger(input.minSeconds) || input.minSeconds < 0))
|
|
20
|
+
return 'learning-min-time-invalid';
|
|
21
|
+
if (input.passScore != null && (!Number.isInteger(input.passScore) || input.passScore < 1 || input.passScore > 100))
|
|
22
|
+
return 'learning-pass-score-invalid';
|
|
23
|
+
if (input.questionCount != null && (!Number.isInteger(input.questionCount) || input.questionCount < 1))
|
|
24
|
+
return 'learning-question-count-invalid';
|
|
25
|
+
if (input.attemptLimit != null && (!Number.isInteger(input.attemptLimit) || input.attemptLimit < 1))
|
|
26
|
+
return 'learning-attempt-limit-invalid';
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
const KINDS = ['VIDEO', 'PDF', 'MARKDOWN', 'LINK'];
|
|
30
|
+
/** Why a unit may not be kept as given. A video's or a PDF's file is attached apart - publishing asks for it. */
|
|
31
|
+
function unitInputRefusal(unit) {
|
|
32
|
+
if (!KINDS.includes(unit.kind))
|
|
33
|
+
return 'learning-unit-kind-invalid';
|
|
34
|
+
if (!unit.name?.trim())
|
|
35
|
+
return 'learning-unit-name-required';
|
|
36
|
+
if (unit.kind === 'MARKDOWN' && !unit.body?.trim())
|
|
37
|
+
return 'learning-unit-body-required';
|
|
38
|
+
if (unit.kind === 'LINK' && !/^https?:\/\//i.test(unit.url?.trim() ?? ''))
|
|
39
|
+
return 'learning-unit-url-invalid';
|
|
40
|
+
if (unit.minSeconds != null && (!Number.isInteger(unit.minSeconds) || unit.minSeconds < 1))
|
|
41
|
+
return 'learning-unit-min-time-invalid';
|
|
42
|
+
if (unit.durationSeconds != null && (!Number.isInteger(unit.durationSeconds) || unit.durationSeconds < 1))
|
|
43
|
+
return 'learning-unit-duration-invalid';
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
/** Why a question may not be kept: its text, two choices or more (TRUE_FALSE exactly two), and the right ones its kind allows. */
|
|
47
|
+
function questionInputRefusal(question) {
|
|
48
|
+
if (!['SINGLE', 'MULTIPLE', 'TRUE_FALSE'].includes(question.kind))
|
|
49
|
+
return 'learning-question-kind-invalid';
|
|
50
|
+
if (!question.text?.trim())
|
|
51
|
+
return 'learning-question-text-required';
|
|
52
|
+
const choices = question.choices ?? [];
|
|
53
|
+
if (choices.length < 2 || choices.some(one => !one.text?.trim()))
|
|
54
|
+
return 'learning-question-choices-required';
|
|
55
|
+
if (question.kind === 'TRUE_FALSE' && choices.length !== 2)
|
|
56
|
+
return 'learning-question-choices-required';
|
|
57
|
+
const right = choices.filter(one => one.correct).length;
|
|
58
|
+
if (right === 0)
|
|
59
|
+
return 'learning-question-answer-required';
|
|
60
|
+
if (question.kind !== 'MULTIPLE' && right !== 1)
|
|
61
|
+
return 'learning-question-one-answer';
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* 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.
|
|
67
|
+
*/
|
|
68
|
+
function publishRefusal({ version, units, questions }) {
|
|
69
|
+
if (version.status !== 'DRAFT')
|
|
70
|
+
return 'learning-version-not-draft';
|
|
71
|
+
if (!units.length)
|
|
72
|
+
return 'learning-units-required';
|
|
73
|
+
for (const unit of units) {
|
|
74
|
+
if ((unit.kind === 'VIDEO' || unit.kind === 'PDF') && !unit.hasFile)
|
|
75
|
+
return 'learning-unit-file-required';
|
|
76
|
+
if (unit.kind === 'VIDEO' && !unit.durationSeconds)
|
|
77
|
+
return 'learning-unit-duration-required';
|
|
78
|
+
}
|
|
79
|
+
if (version.passScore != null) {
|
|
80
|
+
if (!questions.length)
|
|
81
|
+
return 'learning-questions-required';
|
|
82
|
+
if (version.questionCount != null && version.questionCount > questions.length)
|
|
83
|
+
return 'learning-question-count-too-many';
|
|
84
|
+
}
|
|
85
|
+
if (questions.some(one => one.drafted))
|
|
86
|
+
return 'learning-questions-drafted';
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=course-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"course-rules.js","sourceRoot":"","sources":["../../../server/service/course/course-rules.ts"],"names":[],"mappings":";AAAA;;GAEG;;AAWH,gDAGC;AAGD,kDAMC;AAcD,4CAQC;AASD,oDAUC;AAYD,wCAaC;AA/ED,kGAAkG;AAClG,SAAgB,kBAAkB,CAAC,OAA8C;IAC/E,IAAI,CAAC,OAAO;QAAE,OAAO,4BAA4B,CAAA;IACjD,OAAO,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,4BAA4B,CAAA;AACzE,CAAC;AAED,+CAA+C;AAC/C,SAAgB,mBAAmB,CAAC,KAAmB;IACrD,IAAI,KAAK,CAAC,UAAU,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC;QAAE,OAAO,2BAA2B,CAAA;IACjI,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC;QAAE,OAAO,6BAA6B,CAAA;IACzJ,IAAI,KAAK,CAAC,aAAa,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,aAAa,GAAG,CAAC,CAAC;QAAE,OAAO,iCAAiC,CAAA;IAChJ,IAAI,KAAK,CAAC,YAAY,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC;QAAE,OAAO,gCAAgC,CAAA;IAC5I,OAAO,IAAI,CAAA;AACb,CAAC;AAWD,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,CAAA;AAElD,iHAAiH;AACjH,SAAgB,gBAAgB,CAAC,IAAe;IAC9C,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,4BAA4B,CAAA;IACnE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;QAAE,OAAO,6BAA6B,CAAA;IAC5D,IAAI,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE;QAAE,OAAO,6BAA6B,CAAA;IACxF,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QAAE,OAAO,2BAA2B,CAAA;IAC7G,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC;QAAE,OAAO,gCAAgC,CAAA;IACnI,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC,eAAe,GAAG,CAAC,CAAC;QAAE,OAAO,gCAAgC,CAAA;IAClJ,OAAO,IAAI,CAAA;AACb,CAAC;AAQD,kIAAkI;AAClI,SAAgB,oBAAoB,CAAC,QAAuB;IAC1D,IAAI,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,gCAAgC,CAAA;IAC1G,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE;QAAE,OAAO,iCAAiC,CAAA;IACpE,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAA;IACtC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;QAAE,OAAO,oCAAoC,CAAA;IAC7G,IAAI,QAAQ,CAAC,IAAI,KAAK,YAAY,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,oCAAoC,CAAA;IACvG,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,MAAM,CAAA;IACvD,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,mCAAmC,CAAA;IAC3D,IAAI,QAAQ,CAAC,IAAI,KAAK,UAAU,IAAI,KAAK,KAAK,CAAC;QAAE,OAAO,8BAA8B,CAAA;IACtF,OAAO,IAAI,CAAA;AACb,CAAC;AAQD;;;GAGG;AACH,SAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAgB;IACxE,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO;QAAE,OAAO,4BAA4B,CAAA;IACnE,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,yBAAyB,CAAA;IACnD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO,6BAA6B,CAAA;QACzG,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe;YAAE,OAAO,iCAAiC,CAAA;IAC9F,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,SAAS,CAAC,MAAM;YAAE,OAAO,6BAA6B,CAAA;QAC3D,IAAI,OAAO,CAAC,aAAa,IAAI,IAAI,IAAI,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,MAAM;YAAE,OAAO,kCAAkC,CAAA;IAC1H,CAAC;IACD,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;QAAE,OAAO,4BAA4B,CAAA;IAC3E,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["/*\n * What may be done to a course's versions (ADR-0194 decisions 2-4) - data in, the refusal's code out, no database.\n */\n\nexport interface VersionShape {\n status: string\n passScore?: number | null\n questionCount?: number | null\n attemptLimit?: number | null\n minSeconds?: number | null\n}\n\n/** Why a version may not be written to - a published or closed one never changes (decision 2). */\nexport function versionEditRefusal(version: { status: string } | null | undefined): string | null {\n if (!version) return 'learning-version-not-found'\n return version.status === 'DRAFT' ? null : 'learning-version-not-draft'\n}\n\n/** Why what a version says may not be kept. */\nexport function versionInputRefusal(input: VersionShape): string | null {\n if (input.minSeconds != null && (!Number.isInteger(input.minSeconds) || input.minSeconds < 0)) return 'learning-min-time-invalid'\n if (input.passScore != null && (!Number.isInteger(input.passScore) || input.passScore < 1 || input.passScore > 100)) return 'learning-pass-score-invalid'\n if (input.questionCount != null && (!Number.isInteger(input.questionCount) || input.questionCount < 1)) return 'learning-question-count-invalid'\n if (input.attemptLimit != null && (!Number.isInteger(input.attemptLimit) || input.attemptLimit < 1)) return 'learning-attempt-limit-invalid'\n return null\n}\n\nexport interface UnitInput {\n kind: string\n name?: string | null\n body?: string | null\n url?: string | null\n durationSeconds?: number | null\n minSeconds?: number | null\n}\n\nconst KINDS = ['VIDEO', 'PDF', 'MARKDOWN', 'LINK']\n\n/** Why a unit may not be kept as given. A video's or a PDF's file is attached apart - publishing asks for it. */\nexport function unitInputRefusal(unit: UnitInput): string | null {\n if (!KINDS.includes(unit.kind)) return 'learning-unit-kind-invalid'\n if (!unit.name?.trim()) return 'learning-unit-name-required'\n if (unit.kind === 'MARKDOWN' && !unit.body?.trim()) return 'learning-unit-body-required'\n if (unit.kind === 'LINK' && !/^https?:\\/\\//i.test(unit.url?.trim() ?? '')) return 'learning-unit-url-invalid'\n if (unit.minSeconds != null && (!Number.isInteger(unit.minSeconds) || unit.minSeconds < 1)) return 'learning-unit-min-time-invalid'\n if (unit.durationSeconds != null && (!Number.isInteger(unit.durationSeconds) || unit.durationSeconds < 1)) return 'learning-unit-duration-invalid'\n return null\n}\n\nexport interface QuestionInput {\n kind: string\n text?: string | null\n choices: { text: string; correct: boolean }[]\n}\n\n/** Why a question may not be kept: its text, two choices or more (TRUE_FALSE exactly two), and the right ones its kind allows. */\nexport function questionInputRefusal(question: QuestionInput): string | null {\n if (!['SINGLE', 'MULTIPLE', 'TRUE_FALSE'].includes(question.kind)) return 'learning-question-kind-invalid'\n if (!question.text?.trim()) return 'learning-question-text-required'\n const choices = question.choices ?? []\n if (choices.length < 2 || choices.some(one => !one.text?.trim())) return 'learning-question-choices-required'\n if (question.kind === 'TRUE_FALSE' && choices.length !== 2) return 'learning-question-choices-required'\n const right = choices.filter(one => one.correct).length\n if (right === 0) return 'learning-question-answer-required'\n if (question.kind !== 'MULTIPLE' && right !== 1) return 'learning-question-one-answer'\n return null\n}\n\nexport interface PublishFacts {\n version: VersionShape\n units: { kind: string; durationSeconds?: number | null; hasFile: boolean }[]\n questions: { drafted: boolean }[]\n}\n\n/**\n * 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\n * length; a quiz with questions - as many as it draws, none of them a draft the assistant made that no manager looked over.\n */\nexport function publishRefusal({ version, units, questions }: PublishFacts): string | null {\n if (version.status !== 'DRAFT') return 'learning-version-not-draft'\n if (!units.length) return 'learning-units-required'\n for (const unit of units) {\n if ((unit.kind === 'VIDEO' || unit.kind === 'PDF') && !unit.hasFile) return 'learning-unit-file-required'\n if (unit.kind === 'VIDEO' && !unit.durationSeconds) return 'learning-unit-duration-required'\n }\n if (version.passScore != null) {\n if (!questions.length) return 'learning-questions-required'\n if (version.questionCount != null && version.questionCount > questions.length) return 'learning-question-count-too-many'\n }\n if (questions.some(one => one.drafted)) return 'learning-questions-drafted'\n return null\n}\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { EntityManager } from 'typeorm';
|
|
2
|
+
import { Attachment } from '@things-factory/attachment-base';
|
|
3
|
+
import { Course } from './course';
|
|
4
|
+
import { CourseQuestion } from './course-question';
|
|
5
|
+
import { CourseUnit } from './course-unit';
|
|
6
|
+
import { CourseVersion } from './course-version';
|
|
7
|
+
import { CoursePatch, CourseQuestionInput, CourseUnitInput, CourseVersionPatch, NewCourse } from './course-type';
|
|
8
|
+
/** A unit's file hangs on it under this `refType` (attachment-base). */
|
|
9
|
+
export declare const COURSE_UNIT_REF_TYPE = "CourseUnit";
|
|
10
|
+
/** The request's transaction, or the data source's manager for a read that runs without one. */
|
|
11
|
+
export declare function learningManagerOf(context: any): EntityManager;
|
|
12
|
+
/** Whether the request holds `category` of this module (`course`, `learning`) - asked once per request. */
|
|
13
|
+
export declare function holds(context: any, category: 'course' | 'learning', privilege?: 'query' | 'mutation'): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the request may read what a version says - its units and their files: a manager (`course` query) any version;
|
|
16
|
+
* anyone else only the version an enrolment of theirs shows - its own once started, the published one before. Asked on
|
|
17
|
+
* every path to a version, not only the top-level reads: a learner reached a draft through
|
|
18
|
+
* `myEnrollment → course → versions → units` (2026-11-08).
|
|
19
|
+
*/
|
|
20
|
+
export declare function mayReadVersion(context: any, version: {
|
|
21
|
+
id: string;
|
|
22
|
+
courseId?: string;
|
|
23
|
+
status: string;
|
|
24
|
+
} | null | undefined): Promise<boolean>;
|
|
25
|
+
export declare function courseOf(tx: EntityManager, domain: {
|
|
26
|
+
id: string;
|
|
27
|
+
}, id: string): Promise<Course>;
|
|
28
|
+
export declare function createCourse(input: NewCourse, context: any): Promise<Course>;
|
|
29
|
+
export declare function updateCourse(id: string, patch: CoursePatch, context: any): Promise<Course>;
|
|
30
|
+
export declare function versionOf(tx: EntityManager, domain: {
|
|
31
|
+
id: string;
|
|
32
|
+
}, id: string): Promise<CourseVersion>;
|
|
33
|
+
/** The version learners take now - the one published, or none. */
|
|
34
|
+
export declare function publishedVersionOf(tx: EntityManager, domain: {
|
|
35
|
+
id: string;
|
|
36
|
+
}, courseId: string): Promise<CourseVersion | null>;
|
|
37
|
+
export declare function unitsOf(tx: EntityManager, versionId: string): Promise<CourseUnit[]>;
|
|
38
|
+
export declare function questionsOf(tx: EntityManager, versionId: string): Promise<CourseQuestion[]>;
|
|
39
|
+
/**
|
|
40
|
+
* A new draft of a course - the next number, with what `fromId` says copied (the latest version when not given): its
|
|
41
|
+
* settings, units with their files, and questions. One draft at a time.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createCourseVersion(courseId: string, fromId: string | null | undefined, context: any): Promise<CourseVersion>;
|
|
44
|
+
export declare function updateCourseVersion(id: string, patch: CourseVersionPatch, context: any): Promise<CourseVersion>;
|
|
45
|
+
/**
|
|
46
|
+
* The units of a draft, in the order given - new ones made, the ones given changed, the ones left out deleted with
|
|
47
|
+
* their files.
|
|
48
|
+
*/
|
|
49
|
+
export declare function saveCourseUnits(versionId: string, units: CourseUnitInput[], context: any): Promise<CourseUnit[]>;
|
|
50
|
+
/** A video's or a PDF's file for a unit of a draft - it replaces the one before. A video's length comes with it. */
|
|
51
|
+
export declare function attachCourseUnitFile(unitId: string, file: any, durationSeconds: number | null | undefined, context: any): Promise<CourseUnit>;
|
|
52
|
+
/**
|
|
53
|
+
* The questions of a draft, in the order given - saved by a manager, so each is looked over (`drafted` off). The ones
|
|
54
|
+
* left out are deleted.
|
|
55
|
+
*/
|
|
56
|
+
export declare function saveCourseQuestions(versionId: string, questions: CourseQuestionInput[], context: any): Promise<CourseQuestion[]>;
|
|
57
|
+
/** Questions the assistant drafted, added at the end of a draft - each `drafted` until a manager looks it over (decision 12). */
|
|
58
|
+
export declare function addDraftedQuestions(versionId: string, questions: CourseQuestionInput[], context: any): Promise<CourseQuestion[]>;
|
|
59
|
+
/**
|
|
60
|
+
* Publishes a draft: what it says is checked (decisions 2-4), it is claimed by an update that only a draft matches -
|
|
61
|
+
* two at once publish once - and the version published before closes. Enrolments in progress end on their own version.
|
|
62
|
+
*/
|
|
63
|
+
export declare function publishCourseVersion(id: string, context: any): Promise<CourseVersion>;
|
|
64
|
+
/** Closes the published version - nobody starts it any more; those who started end on it. */
|
|
65
|
+
export declare function closeCourseVersion(id: string, context: any): Promise<CourseVersion>;
|
|
66
|
+
/** A draft no learner saw is deleted with its units, their files and its questions. */
|
|
67
|
+
export declare function deleteCourseVersion(id: string, context: any): Promise<boolean>;
|
|
68
|
+
/** The file of each unit given - for a video's or a PDF's address. */
|
|
69
|
+
export declare function unitFilesOf(domain: {
|
|
70
|
+
id: string;
|
|
71
|
+
}, unitIds: string[]): Promise<Map<string, Attachment>>;
|