@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,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* What the server decides of learning by itself (ADR-0194 decisions 4, 6, 8) - data in, verdict out, no database: the
|
|
4
|
+
* time a tick adds, where a video may stand, when a unit is done, whether an enrolment is complete, and a quiz's score.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.overdue = exports.counts = exports.DEFAULT_UNIT_SECONDS = exports.POSITION_SLACK_SECONDS = exports.TICK_SECONDS = void 0;
|
|
8
|
+
exports.tickVerdict = tickVerdict;
|
|
9
|
+
exports.unitDone = unitDone;
|
|
10
|
+
exports.missingOf = missingOf;
|
|
11
|
+
exports.scoreOf = scoreOf;
|
|
12
|
+
exports.drawQuestions = drawQuestions;
|
|
13
|
+
/** The longest a tick is counted for, in seconds - the screen sends one every 15 seconds looked at. */
|
|
14
|
+
exports.TICK_SECONDS = 15;
|
|
15
|
+
/** How far ahead of what was watched a position still reads as watching - the video's own step between two updates. */
|
|
16
|
+
exports.POSITION_SLACK_SECONDS = 2;
|
|
17
|
+
/** How long a PDF or a text is looked at to be done when its unit says nothing. */
|
|
18
|
+
exports.DEFAULT_UNIT_SECONDS = 60;
|
|
19
|
+
/**
|
|
20
|
+
* What one tick does (decision 6). The time added is the gap since the last tick, at most 15 seconds and at most what
|
|
21
|
+
* the screen says - a tick with no tick before it starts the clock and adds nothing. A video's position may not be ahead
|
|
22
|
+
* of what was watched by more than the time that passed: a move ahead is refused, a move back is free.
|
|
23
|
+
*/
|
|
24
|
+
function tickVerdict(progress, tick, now, video) {
|
|
25
|
+
const gap = progress.lastTickAt ? Math.max(0, (now.getTime() - new Date(progress.lastTickAt).getTime()) / 1000) : 0;
|
|
26
|
+
const said = Math.max(0, Number(tick.seconds) || 0);
|
|
27
|
+
const add = progress.lastTickAt ? Math.floor(Math.min(exports.TICK_SECONDS, gap, said)) : 0;
|
|
28
|
+
const position = Math.max(0, Math.floor(Number(tick.position) || 0));
|
|
29
|
+
if (!video)
|
|
30
|
+
return { add, watchedSeconds: progress.watchedSeconds, positionSeconds: 0 };
|
|
31
|
+
const allowed = progress.watchedSeconds + Math.min(exports.TICK_SECONDS, gap) + exports.POSITION_SLACK_SECONDS;
|
|
32
|
+
if (position > allowed)
|
|
33
|
+
return { refusal: 'position-ahead', allowed: Math.floor(progress.watchedSeconds) };
|
|
34
|
+
return { add, watchedSeconds: Math.max(progress.watchedSeconds, position), positionSeconds: position };
|
|
35
|
+
}
|
|
36
|
+
/** Whether a unit counts toward completion - a link does not: the server cannot know what was watched elsewhere. */
|
|
37
|
+
const counts = (unit) => unit.kind !== 'LINK';
|
|
38
|
+
exports.counts = counts;
|
|
39
|
+
/**
|
|
40
|
+
* Whether a unit is done (decision 3): a video watched to its end (its length known) **and** its running time counted
|
|
41
|
+
* by the server - a skip or a faster playback reaches neither; a PDF or a text looked at for its time. A link is never
|
|
42
|
+
* done - it does not count.
|
|
43
|
+
*/
|
|
44
|
+
function unitDone(unit, progress) {
|
|
45
|
+
if (unit.kind === 'VIDEO')
|
|
46
|
+
return !!unit.durationSeconds && progress.watchedSeconds >= unit.durationSeconds - exports.POSITION_SLACK_SECONDS && progress.learnedSeconds >= unit.durationSeconds - exports.TICK_SECONDS;
|
|
47
|
+
if (unit.kind === 'PDF' || unit.kind === 'MARKDOWN')
|
|
48
|
+
return progress.learnedSeconds >= (unit.minSeconds ?? exports.DEFAULT_UNIT_SECONDS);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* What still stands between an enrolment and its completion (decision 8) - every unit done, the least time, the quiz
|
|
53
|
+
* passed when there is one, the acknowledgement confirmed when one is asked. Complete when nothing is missing.
|
|
54
|
+
*/
|
|
55
|
+
function missingOf(facts) {
|
|
56
|
+
const missing = [];
|
|
57
|
+
if (facts.unitsDone < facts.units)
|
|
58
|
+
missing.push('units');
|
|
59
|
+
if (facts.learnedSeconds < facts.minSeconds)
|
|
60
|
+
missing.push('time');
|
|
61
|
+
if (facts.quiz && !facts.passed)
|
|
62
|
+
missing.push('quiz');
|
|
63
|
+
if (facts.acknowledgement && !facts.acknowledged)
|
|
64
|
+
missing.push('acknowledgement');
|
|
65
|
+
return missing;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A quiz's score, out of 100 (decision 4): each question is right when the choices picked are exactly the right ones -
|
|
69
|
+
* one for SINGLE and TRUE_FALSE, every right one and no other for MULTIPLE.
|
|
70
|
+
*/
|
|
71
|
+
function scoreOf(questions, answers) {
|
|
72
|
+
if (!questions.length)
|
|
73
|
+
return 0;
|
|
74
|
+
const right = questions.filter(question => {
|
|
75
|
+
const picked = new Set(answers?.[question.id] ?? []);
|
|
76
|
+
const correct = question.choices.filter(one => one.correct).map(one => one.id);
|
|
77
|
+
return picked.size === correct.length && correct.every(id => picked.has(id));
|
|
78
|
+
}).length;
|
|
79
|
+
return Math.round((right / questions.length) * 100);
|
|
80
|
+
}
|
|
81
|
+
/** The questions an attempt draws - `count` of them (all when empty), in a shuffled order, each with its choices shuffled. */
|
|
82
|
+
function drawQuestions(questions, count, random = Math.random) {
|
|
83
|
+
const shuffle = (items) => {
|
|
84
|
+
const out = [...items];
|
|
85
|
+
for (let i = out.length - 1; i > 0; i--) {
|
|
86
|
+
const j = Math.floor(random() * (i + 1));
|
|
87
|
+
[out[i], out[j]] = [out[j], out[i]];
|
|
88
|
+
}
|
|
89
|
+
return out;
|
|
90
|
+
};
|
|
91
|
+
const picked = shuffle(questions).slice(0, count && count > 0 ? count : questions.length);
|
|
92
|
+
return picked.map(question => ({ questionId: question.id, choiceIds: shuffle(question.choices.map(one => one.id)) }));
|
|
93
|
+
}
|
|
94
|
+
/** The day an enrolment reads as overdue: open, and its due day before today. */
|
|
95
|
+
const overdue = (enrollment, today) => (enrollment.status === 'ASSIGNED' || enrollment.status === 'IN_PROGRESS') && !!enrollment.dueOn && enrollment.dueOn < today;
|
|
96
|
+
exports.overdue = overdue;
|
|
97
|
+
//# sourceMappingURL=progress-rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress-rules.js","sourceRoot":"","sources":["../../../server/service/enrollment/progress-rules.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * What the server decides of learning by itself (ADR-0194 decisions 4, 6, 8) - data in, verdict out, no database: the\n * time a tick adds, where a video may stand, when a unit is done, whether an enrolment is complete, and a quiz's score.\n */\n\n/** The longest a tick is counted for, in seconds - the screen sends one every 15 seconds looked at. */\nexport const TICK_SECONDS = 15\n/** How far ahead of what was watched a position still reads as watching - the video's own step between two updates. */\nexport const POSITION_SLACK_SECONDS = 2\n/** How long a PDF or a text is looked at to be done when its unit says nothing. */\nexport const DEFAULT_UNIT_SECONDS = 60\n\nexport interface ProgressNow {\n learnedSeconds: number\n watchedSeconds: number\n lastTickAt?: Date | null\n}\n\nexport interface Tick {\n /** What the screen says it looked at since its last tick - it may lower what is counted, never raise it. */\n seconds: number\n /** Where the video stands, in seconds. */\n position: number\n}\n\nexport type TickVerdict = { refusal: 'position-ahead'; allowed: number } | { add: number; watchedSeconds: number; positionSeconds: number }\n\n/**\n * What one tick does (decision 6). The time added is the gap since the last tick, at most 15 seconds and at most what\n * the screen says - a tick with no tick before it starts the clock and adds nothing. A video's position may not be ahead\n * of what was watched by more than the time that passed: a move ahead is refused, a move back is free.\n */\nexport function tickVerdict(progress: ProgressNow, tick: Tick, now: Date, video: boolean): TickVerdict {\n const gap = progress.lastTickAt ? Math.max(0, (now.getTime() - new Date(progress.lastTickAt).getTime()) / 1000) : 0\n const said = Math.max(0, Number(tick.seconds) || 0)\n const add = progress.lastTickAt ? Math.floor(Math.min(TICK_SECONDS, gap, said)) : 0\n const position = Math.max(0, Math.floor(Number(tick.position) || 0))\n if (!video) return { add, watchedSeconds: progress.watchedSeconds, positionSeconds: 0 }\n const allowed = progress.watchedSeconds + Math.min(TICK_SECONDS, gap) + POSITION_SLACK_SECONDS\n if (position > allowed) return { refusal: 'position-ahead', allowed: Math.floor(progress.watchedSeconds) }\n return { add, watchedSeconds: Math.max(progress.watchedSeconds, position), positionSeconds: position }\n}\n\nexport interface UnitShape {\n kind: string\n durationSeconds?: number | null\n minSeconds?: number | null\n}\n\n/** Whether a unit counts toward completion - a link does not: the server cannot know what was watched elsewhere. */\nexport const counts = (unit: UnitShape) => unit.kind !== 'LINK'\n\n/**\n * Whether a unit is done (decision 3): a video watched to its end (its length known) **and** its running time counted\n * by the server - a skip or a faster playback reaches neither; a PDF or a text looked at for its time. A link is never\n * done - it does not count.\n */\nexport function unitDone(unit: UnitShape, progress: { learnedSeconds: number; watchedSeconds: number }): boolean {\n if (unit.kind === 'VIDEO')\n return !!unit.durationSeconds && progress.watchedSeconds >= unit.durationSeconds - POSITION_SLACK_SECONDS && progress.learnedSeconds >= unit.durationSeconds - TICK_SECONDS\n if (unit.kind === 'PDF' || unit.kind === 'MARKDOWN') return progress.learnedSeconds >= (unit.minSeconds ?? DEFAULT_UNIT_SECONDS)\n return false\n}\n\nexport interface CompletionFacts {\n /** The units that count, and the ones of them done. */\n units: number\n unitsDone: number\n learnedSeconds: number\n minSeconds: number\n /** The version has a quiz - questions and a pass score. */\n quiz: boolean\n passed: boolean\n /** The version asks for an acknowledgement. */\n acknowledgement: boolean\n acknowledged: boolean\n}\n\nexport type Missing = 'units' | 'time' | 'quiz' | 'acknowledgement'\n\n/**\n * What still stands between an enrolment and its completion (decision 8) - every unit done, the least time, the quiz\n * passed when there is one, the acknowledgement confirmed when one is asked. Complete when nothing is missing.\n */\nexport function missingOf(facts: CompletionFacts): Missing[] {\n const missing: Missing[] = []\n if (facts.unitsDone < facts.units) missing.push('units')\n if (facts.learnedSeconds < facts.minSeconds) missing.push('time')\n if (facts.quiz && !facts.passed) missing.push('quiz')\n if (facts.acknowledgement && !facts.acknowledged) missing.push('acknowledgement')\n return missing\n}\n\nexport interface QuestionKey {\n id: string\n kind: string\n choices: { id: string; correct: boolean }[]\n}\n\n/**\n * A quiz's score, out of 100 (decision 4): each question is right when the choices picked are exactly the right ones -\n * one for SINGLE and TRUE_FALSE, every right one and no other for MULTIPLE.\n */\nexport function scoreOf(questions: QuestionKey[], answers: Record<string, string[]>): number {\n if (!questions.length) return 0\n const right = questions.filter(question => {\n const picked = new Set(answers?.[question.id] ?? [])\n const correct = question.choices.filter(one => one.correct).map(one => one.id)\n return picked.size === correct.length && correct.every(id => picked.has(id))\n }).length\n return Math.round((right / questions.length) * 100)\n}\n\n/** The questions an attempt draws - `count` of them (all when empty), in a shuffled order, each with its choices shuffled. */\nexport function drawQuestions<Q extends { id: string; choices: { id: string }[] }>(questions: Q[], count: number | null | undefined, random: () => number = Math.random) {\n const shuffle = <T>(items: T[]): T[] => {\n const out = [...items]\n for (let i = out.length - 1; i > 0; i--) {\n const j = Math.floor(random() * (i + 1))\n ;[out[i], out[j]] = [out[j], out[i]]\n }\n return out\n }\n const picked = shuffle(questions).slice(0, count && count > 0 ? count : questions.length)\n return picked.map(question => ({ questionId: question.id, choiceIds: shuffle(question.choices.map(one => one.id)) }))\n}\n\n/** The day an enrolment reads as overdue: open, and its due day before today. */\nexport const overdue = (enrollment: { status: string; dueOn?: string | null }, today: string) =>\n (enrollment.status === 'ASSIGNED' || enrollment.status === 'IN_PROGRESS') && !!enrollment.dueOn && enrollment.dueOn < today\n"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { User } from '@things-factory/auth-base';
|
|
2
|
+
import { Domain } from '@things-factory/shell';
|
|
3
|
+
import { Enrollment } from './enrollment';
|
|
4
|
+
/** One question as an attempt drew it - its choices in the order shown. */
|
|
5
|
+
export interface DrawnQuestion {
|
|
6
|
+
questionId: string;
|
|
7
|
+
choiceIds: string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* 응시 (ADR-0194 decision 4) - one attempt at a version's quiz: the questions it drew and their choices in shuffled
|
|
11
|
+
* order, and once sent, the answers and the score the server gave. The drawn questions are kept so the answers are
|
|
12
|
+
* scored against what was shown.
|
|
13
|
+
*/
|
|
14
|
+
export declare class QuizAttempt {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
domain?: Domain;
|
|
17
|
+
domainId?: string;
|
|
18
|
+
enrollment?: Enrollment;
|
|
19
|
+
enrollmentId?: string;
|
|
20
|
+
/** The questions drawn, in order, with their choices in the order shown - not a field. */
|
|
21
|
+
drawn: DrawnQuestion[];
|
|
22
|
+
/** The choices picked for each question - not a field. */
|
|
23
|
+
answers?: Record<string, string[]>;
|
|
24
|
+
submittedAt?: Date;
|
|
25
|
+
score?: number;
|
|
26
|
+
passed?: boolean;
|
|
27
|
+
createdAt?: Date;
|
|
28
|
+
updatedAt?: Date;
|
|
29
|
+
deletedAt?: Date;
|
|
30
|
+
creator?: User;
|
|
31
|
+
creatorId?: string;
|
|
32
|
+
updater?: User;
|
|
33
|
+
updaterId?: string;
|
|
34
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QuizAttempt = 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 enrollment_1 = require("./enrollment");
|
|
10
|
+
/**
|
|
11
|
+
* 응시 (ADR-0194 decision 4) - one attempt at a version's quiz: the questions it drew and their choices in shuffled
|
|
12
|
+
* order, and once sent, the answers and the score the server gave. The drawn questions are kept so the answers are
|
|
13
|
+
* scored against what was shown.
|
|
14
|
+
*/
|
|
15
|
+
let QuizAttempt = class QuizAttempt {
|
|
16
|
+
};
|
|
17
|
+
exports.QuizAttempt = QuizAttempt;
|
|
18
|
+
tslib_1.__decorate([
|
|
19
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
20
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { description: 'Attempt id.' }),
|
|
21
|
+
tslib_1.__metadata("design:type", String)
|
|
22
|
+
], QuizAttempt.prototype, "id", void 0);
|
|
23
|
+
tslib_1.__decorate([
|
|
24
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
25
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true, description: 'The domain.' }),
|
|
26
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
27
|
+
], QuizAttempt.prototype, "domain", void 0);
|
|
28
|
+
tslib_1.__decorate([
|
|
29
|
+
(0, typeorm_1.RelationId)((row) => row.domain),
|
|
30
|
+
tslib_1.__metadata("design:type", String)
|
|
31
|
+
], QuizAttempt.prototype, "domainId", void 0);
|
|
32
|
+
tslib_1.__decorate([
|
|
33
|
+
(0, typeorm_1.ManyToOne)(type => enrollment_1.Enrollment),
|
|
34
|
+
(0, type_graphql_1.Field)(type => enrollment_1.Enrollment, { nullable: true, description: 'The enrolment.' }),
|
|
35
|
+
tslib_1.__metadata("design:type", enrollment_1.Enrollment)
|
|
36
|
+
], QuizAttempt.prototype, "enrollment", void 0);
|
|
37
|
+
tslib_1.__decorate([
|
|
38
|
+
(0, typeorm_1.RelationId)((row) => row.enrollment),
|
|
39
|
+
tslib_1.__metadata("design:type", String)
|
|
40
|
+
], QuizAttempt.prototype, "enrollmentId", void 0);
|
|
41
|
+
tslib_1.__decorate([
|
|
42
|
+
(0, typeorm_1.Column)({ type: 'simple-json' }),
|
|
43
|
+
tslib_1.__metadata("design:type", Array)
|
|
44
|
+
], QuizAttempt.prototype, "drawn", void 0);
|
|
45
|
+
tslib_1.__decorate([
|
|
46
|
+
(0, typeorm_1.Column)({ type: 'simple-json', nullable: true }),
|
|
47
|
+
tslib_1.__metadata("design:type", Object)
|
|
48
|
+
], QuizAttempt.prototype, "answers", void 0);
|
|
49
|
+
tslib_1.__decorate([
|
|
50
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
51
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'When the answers were sent.' }),
|
|
52
|
+
tslib_1.__metadata("design:type", Date)
|
|
53
|
+
], QuizAttempt.prototype, "submittedAt", void 0);
|
|
54
|
+
tslib_1.__decorate([
|
|
55
|
+
(0, typeorm_1.Column)({ type: 'int', nullable: true }),
|
|
56
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { nullable: true, description: 'The score, out of 100.' }),
|
|
57
|
+
tslib_1.__metadata("design:type", Number)
|
|
58
|
+
], QuizAttempt.prototype, "score", void 0);
|
|
59
|
+
tslib_1.__decorate([
|
|
60
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
61
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Whether it passed.' }),
|
|
62
|
+
tslib_1.__metadata("design:type", Boolean)
|
|
63
|
+
], QuizAttempt.prototype, "passed", void 0);
|
|
64
|
+
tslib_1.__decorate([
|
|
65
|
+
(0, typeorm_1.CreateDateColumn)(),
|
|
66
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Creation timestamp.' }),
|
|
67
|
+
tslib_1.__metadata("design:type", Date)
|
|
68
|
+
], QuizAttempt.prototype, "createdAt", void 0);
|
|
69
|
+
tslib_1.__decorate([
|
|
70
|
+
(0, typeorm_1.UpdateDateColumn)(),
|
|
71
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Last update timestamp.' }),
|
|
72
|
+
tslib_1.__metadata("design:type", Date)
|
|
73
|
+
], QuizAttempt.prototype, "updatedAt", void 0);
|
|
74
|
+
tslib_1.__decorate([
|
|
75
|
+
(0, typeorm_1.DeleteDateColumn)(),
|
|
76
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Soft delete timestamp.' }),
|
|
77
|
+
tslib_1.__metadata("design:type", Date)
|
|
78
|
+
], QuizAttempt.prototype, "deletedAt", void 0);
|
|
79
|
+
tslib_1.__decorate([
|
|
80
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
81
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'Who made it.' }),
|
|
82
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
83
|
+
], QuizAttempt.prototype, "creator", void 0);
|
|
84
|
+
tslib_1.__decorate([
|
|
85
|
+
(0, typeorm_1.RelationId)((row) => row.creator),
|
|
86
|
+
tslib_1.__metadata("design:type", String)
|
|
87
|
+
], QuizAttempt.prototype, "creatorId", void 0);
|
|
88
|
+
tslib_1.__decorate([
|
|
89
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
90
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'Who changed it last.' }),
|
|
91
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
92
|
+
], QuizAttempt.prototype, "updater", void 0);
|
|
93
|
+
tslib_1.__decorate([
|
|
94
|
+
(0, typeorm_1.RelationId)((row) => row.updater),
|
|
95
|
+
tslib_1.__metadata("design:type", String)
|
|
96
|
+
], QuizAttempt.prototype, "updaterId", void 0);
|
|
97
|
+
exports.QuizAttempt = QuizAttempt = tslib_1.__decorate([
|
|
98
|
+
(0, typeorm_1.Entity)({ name: 'learning_quiz_attempts' }),
|
|
99
|
+
(0, typeorm_1.Index)('ix_learning_quiz_attempt_0', (row) => [row.domain, row.enrollment, row.createdAt])
|
|
100
|
+
/* One unsent attempt per enrolment - the table holds it too, whatever two requests do at once. */
|
|
101
|
+
,
|
|
102
|
+
(0, typeorm_1.Index)('ix_learning_quiz_attempt_1', (row) => [row.domain, row.enrollment], { unique: true, where: '"deleted_at" IS NULL AND "submitted_at" IS NULL' }),
|
|
103
|
+
(0, type_graphql_1.ObjectType)({ description: 'One attempt at a quiz - the questions drawn, the answers sent and the score the server gave.' })
|
|
104
|
+
], QuizAttempt);
|
|
105
|
+
//# sourceMappingURL=quiz-attempt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quiz-attempt.js","sourceRoot":"","sources":["../../../server/service/enrollment/quiz-attempt.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, ID, Int, ObjectType } 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 { Enrollment } from './enrollment'\n\n/** One question as an attempt drew it - its choices in the order shown. */\nexport interface DrawnQuestion {\n questionId: string\n choiceIds: string[]\n}\n\n/**\n * 응시 (ADR-0194 decision 4) - one attempt at a version's quiz: the questions it drew and their choices in shuffled\n * order, and once sent, the answers and the score the server gave. The drawn questions are kept so the answers are\n * scored against what was shown.\n */\n@Entity({ name: 'learning_quiz_attempts' })\n@Index('ix_learning_quiz_attempt_0', (row: QuizAttempt) => [row.domain, row.enrollment, row.createdAt])\n/* One unsent attempt per enrolment - the table holds it too, whatever two requests do at once. */\n@Index('ix_learning_quiz_attempt_1', (row: QuizAttempt) => [row.domain, row.enrollment], { unique: true, where: '\"deleted_at\" IS NULL AND \"submitted_at\" IS NULL' })\n@ObjectType({ description: 'One attempt at a quiz - the questions drawn, the answers sent and the score the server gave.' })\nexport class QuizAttempt {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Attempt 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: QuizAttempt) => row.domain)\n domainId?: string\n\n @ManyToOne(type => Enrollment)\n @Field(type => Enrollment, { nullable: true, description: 'The enrolment.' })\n enrollment?: Enrollment\n\n @RelationId((row: QuizAttempt) => row.enrollment)\n enrollmentId?: string\n\n /** The questions drawn, in order, with their choices in the order shown - not a field. */\n @Column({ type: 'simple-json' })\n drawn: DrawnQuestion[]\n\n /** The choices picked for each question - not a field. */\n @Column({ type: 'simple-json', nullable: true })\n answers?: Record<string, string[]>\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When the answers were sent.' })\n submittedAt?: Date\n\n @Column({ type: 'int', nullable: true })\n @Field(type => Int, { nullable: true, description: 'The score, out of 100.' })\n score?: number\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'Whether it passed.' })\n passed?: 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: QuizAttempt) => 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: QuizAttempt) => row.updater)\n updaterId?: string\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { User } from '@things-factory/auth-base';
|
|
2
|
+
import { Domain } from '@things-factory/shell';
|
|
3
|
+
import { CourseUnit } from '../course/course-unit';
|
|
4
|
+
import { Enrollment } from './enrollment';
|
|
5
|
+
/**
|
|
6
|
+
* 진도 (ADR-0194 decision 6) - how far one enrolment went through one unit: the seconds the server counted, how far the
|
|
7
|
+
* video was watched, and whether it is done. Never deleted - another device carries on from it. The time is counted from
|
|
8
|
+
* the gap since the last tick, at most 15 seconds each - never a sum the screen sends.
|
|
9
|
+
*/
|
|
10
|
+
export declare class UnitProgress {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
domain?: Domain;
|
|
13
|
+
domainId?: string;
|
|
14
|
+
enrollment?: Enrollment;
|
|
15
|
+
enrollmentId?: string;
|
|
16
|
+
unit?: CourseUnit;
|
|
17
|
+
unitId?: string;
|
|
18
|
+
learnedSeconds: number;
|
|
19
|
+
watchedSeconds: number;
|
|
20
|
+
positionSeconds: number;
|
|
21
|
+
lastTickAt?: Date;
|
|
22
|
+
doneAt?: Date;
|
|
23
|
+
createdAt?: Date;
|
|
24
|
+
updatedAt?: Date;
|
|
25
|
+
deletedAt?: Date;
|
|
26
|
+
creator?: User;
|
|
27
|
+
creatorId?: string;
|
|
28
|
+
updater?: User;
|
|
29
|
+
updaterId?: string;
|
|
30
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UnitProgress = 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_unit_1 = require("../course/course-unit");
|
|
10
|
+
const enrollment_1 = require("./enrollment");
|
|
11
|
+
/**
|
|
12
|
+
* 진도 (ADR-0194 decision 6) - how far one enrolment went through one unit: the seconds the server counted, how far the
|
|
13
|
+
* video was watched, and whether it is done. Never deleted - another device carries on from it. The time is counted from
|
|
14
|
+
* the gap since the last tick, at most 15 seconds each - never a sum the screen sends.
|
|
15
|
+
*/
|
|
16
|
+
let UnitProgress = class UnitProgress {
|
|
17
|
+
};
|
|
18
|
+
exports.UnitProgress = UnitProgress;
|
|
19
|
+
tslib_1.__decorate([
|
|
20
|
+
(0, typeorm_1.PrimaryGeneratedColumn)('uuid'),
|
|
21
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.ID, { description: 'Progress id.' }),
|
|
22
|
+
tslib_1.__metadata("design:type", String)
|
|
23
|
+
], UnitProgress.prototype, "id", void 0);
|
|
24
|
+
tslib_1.__decorate([
|
|
25
|
+
(0, typeorm_1.ManyToOne)(type => shell_1.Domain),
|
|
26
|
+
(0, type_graphql_1.Field)(type => shell_1.Domain, { nullable: true, description: 'The domain.' }),
|
|
27
|
+
tslib_1.__metadata("design:type", shell_1.Domain)
|
|
28
|
+
], UnitProgress.prototype, "domain", void 0);
|
|
29
|
+
tslib_1.__decorate([
|
|
30
|
+
(0, typeorm_1.RelationId)((row) => row.domain),
|
|
31
|
+
tslib_1.__metadata("design:type", String)
|
|
32
|
+
], UnitProgress.prototype, "domainId", void 0);
|
|
33
|
+
tslib_1.__decorate([
|
|
34
|
+
(0, typeorm_1.ManyToOne)(type => enrollment_1.Enrollment),
|
|
35
|
+
(0, type_graphql_1.Field)(type => enrollment_1.Enrollment, { nullable: true, description: 'The enrolment.' }),
|
|
36
|
+
tslib_1.__metadata("design:type", enrollment_1.Enrollment)
|
|
37
|
+
], UnitProgress.prototype, "enrollment", void 0);
|
|
38
|
+
tslib_1.__decorate([
|
|
39
|
+
(0, typeorm_1.RelationId)((row) => row.enrollment),
|
|
40
|
+
tslib_1.__metadata("design:type", String)
|
|
41
|
+
], UnitProgress.prototype, "enrollmentId", void 0);
|
|
42
|
+
tslib_1.__decorate([
|
|
43
|
+
(0, typeorm_1.ManyToOne)(type => course_unit_1.CourseUnit),
|
|
44
|
+
(0, type_graphql_1.Field)(type => course_unit_1.CourseUnit, { nullable: true, description: 'The unit.' }),
|
|
45
|
+
tslib_1.__metadata("design:type", course_unit_1.CourseUnit)
|
|
46
|
+
], UnitProgress.prototype, "unit", void 0);
|
|
47
|
+
tslib_1.__decorate([
|
|
48
|
+
(0, typeorm_1.RelationId)((row) => row.unit),
|
|
49
|
+
tslib_1.__metadata("design:type", String)
|
|
50
|
+
], UnitProgress.prototype, "unitId", void 0);
|
|
51
|
+
tslib_1.__decorate([
|
|
52
|
+
(0, typeorm_1.Column)({ type: 'int', default: 0 }),
|
|
53
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'The seconds the server counted on it.' }),
|
|
54
|
+
tslib_1.__metadata("design:type", Number)
|
|
55
|
+
], UnitProgress.prototype, "learnedSeconds", void 0);
|
|
56
|
+
tslib_1.__decorate([
|
|
57
|
+
(0, typeorm_1.Column)({ type: 'int', default: 0 }),
|
|
58
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'How far the video was watched, in seconds - it cannot be moved past it.' }),
|
|
59
|
+
tslib_1.__metadata("design:type", Number)
|
|
60
|
+
], UnitProgress.prototype, "watchedSeconds", void 0);
|
|
61
|
+
tslib_1.__decorate([
|
|
62
|
+
(0, typeorm_1.Column)({ type: 'int', default: 0 }),
|
|
63
|
+
(0, type_graphql_1.Field)(type => type_graphql_1.Int, { description: 'Where the video was left, in seconds.' }),
|
|
64
|
+
tslib_1.__metadata("design:type", Number)
|
|
65
|
+
], UnitProgress.prototype, "positionSeconds", void 0);
|
|
66
|
+
tslib_1.__decorate([
|
|
67
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
68
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'When the last tick came - the next one is counted from it.' }),
|
|
69
|
+
tslib_1.__metadata("design:type", Date)
|
|
70
|
+
], UnitProgress.prototype, "lastTickAt", void 0);
|
|
71
|
+
tslib_1.__decorate([
|
|
72
|
+
(0, typeorm_1.Column)({ nullable: true }),
|
|
73
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'When it was done.' }),
|
|
74
|
+
tslib_1.__metadata("design:type", Date)
|
|
75
|
+
], UnitProgress.prototype, "doneAt", void 0);
|
|
76
|
+
tslib_1.__decorate([
|
|
77
|
+
(0, typeorm_1.CreateDateColumn)(),
|
|
78
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Creation timestamp.' }),
|
|
79
|
+
tslib_1.__metadata("design:type", Date)
|
|
80
|
+
], UnitProgress.prototype, "createdAt", void 0);
|
|
81
|
+
tslib_1.__decorate([
|
|
82
|
+
(0, typeorm_1.UpdateDateColumn)(),
|
|
83
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Last update timestamp.' }),
|
|
84
|
+
tslib_1.__metadata("design:type", Date)
|
|
85
|
+
], UnitProgress.prototype, "updatedAt", void 0);
|
|
86
|
+
tslib_1.__decorate([
|
|
87
|
+
(0, typeorm_1.DeleteDateColumn)(),
|
|
88
|
+
(0, type_graphql_1.Field)({ nullable: true, description: 'Soft delete timestamp.' }),
|
|
89
|
+
tslib_1.__metadata("design:type", Date)
|
|
90
|
+
], UnitProgress.prototype, "deletedAt", void 0);
|
|
91
|
+
tslib_1.__decorate([
|
|
92
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
93
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'Who made it.' }),
|
|
94
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
95
|
+
], UnitProgress.prototype, "creator", void 0);
|
|
96
|
+
tslib_1.__decorate([
|
|
97
|
+
(0, typeorm_1.RelationId)((row) => row.creator),
|
|
98
|
+
tslib_1.__metadata("design:type", String)
|
|
99
|
+
], UnitProgress.prototype, "creatorId", void 0);
|
|
100
|
+
tslib_1.__decorate([
|
|
101
|
+
(0, typeorm_1.ManyToOne)(type => auth_base_1.User, { nullable: true }),
|
|
102
|
+
(0, type_graphql_1.Field)(type => auth_base_1.User, { nullable: true, description: 'Who changed it last.' }),
|
|
103
|
+
tslib_1.__metadata("design:type", auth_base_1.User)
|
|
104
|
+
], UnitProgress.prototype, "updater", void 0);
|
|
105
|
+
tslib_1.__decorate([
|
|
106
|
+
(0, typeorm_1.RelationId)((row) => row.updater),
|
|
107
|
+
tslib_1.__metadata("design:type", String)
|
|
108
|
+
], UnitProgress.prototype, "updaterId", void 0);
|
|
109
|
+
exports.UnitProgress = UnitProgress = tslib_1.__decorate([
|
|
110
|
+
(0, typeorm_1.Entity)({ name: 'learning_unit_progresses' }),
|
|
111
|
+
(0, typeorm_1.Index)('ix_learning_unit_progress_0', (row) => [row.domain, row.enrollment, row.unit], { unique: true, where: '"deleted_at" IS NULL' }),
|
|
112
|
+
(0, type_graphql_1.ObjectType)({ description: 'How far one enrolment went through one unit - the time the server counted, how far the video was watched, done or not.' })
|
|
113
|
+
], UnitProgress);
|
|
114
|
+
//# sourceMappingURL=unit-progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unit-progress.js","sourceRoot":"","sources":["../../../server/service/enrollment/unit-progress.ts"],"names":[],"mappings":"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","sourcesContent":["import { Field, ID, Int, ObjectType } 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 { CourseUnit } from '../course/course-unit'\nimport { Enrollment } from './enrollment'\n\n/**\n * 진도 (ADR-0194 decision 6) - how far one enrolment went through one unit: the seconds the server counted, how far the\n * video was watched, and whether it is done. Never deleted - another device carries on from it. The time is counted from\n * the gap since the last tick, at most 15 seconds each - never a sum the screen sends.\n */\n@Entity({ name: 'learning_unit_progresses' })\n@Index('ix_learning_unit_progress_0', (row: UnitProgress) => [row.domain, row.enrollment, row.unit], { unique: true, where: '\"deleted_at\" IS NULL' })\n@ObjectType({ description: 'How far one enrolment went through one unit - the time the server counted, how far the video was watched, done or not.' })\nexport class UnitProgress {\n @PrimaryGeneratedColumn('uuid')\n @Field(type => ID, { description: 'Progress 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: UnitProgress) => row.domain)\n domainId?: string\n\n @ManyToOne(type => Enrollment)\n @Field(type => Enrollment, { nullable: true, description: 'The enrolment.' })\n enrollment?: Enrollment\n\n @RelationId((row: UnitProgress) => row.enrollment)\n enrollmentId?: string\n\n @ManyToOne(type => CourseUnit)\n @Field(type => CourseUnit, { nullable: true, description: 'The unit.' })\n unit?: CourseUnit\n\n @RelationId((row: UnitProgress) => row.unit)\n unitId?: string\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'The seconds the server counted on it.' })\n learnedSeconds: number\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'How far the video was watched, in seconds - it cannot be moved past it.' })\n watchedSeconds: number\n\n @Column({ type: 'int', default: 0 })\n @Field(type => Int, { description: 'Where the video was left, in seconds.' })\n positionSeconds: number\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When the last tick came - the next one is counted from it.' })\n lastTickAt?: Date\n\n @Column({ nullable: true })\n @Field({ nullable: true, description: 'When it was done.' })\n doneAt?: Date\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: UnitProgress) => 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: UnitProgress) => row.updater)\n updaterId?: string\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from './course/course';
|
|
2
|
+
export * from './course/course-question';
|
|
3
|
+
export * from './course/course-unit';
|
|
4
|
+
export * from './course/course-version';
|
|
5
|
+
export { COURSE_UNIT_REF_TYPE, publishedVersionOf, unitsOf, questionsOf, addDraftedQuestions } from './course';
|
|
6
|
+
export * from './enrollment/enrollment';
|
|
7
|
+
export * from './enrollment/quiz-attempt';
|
|
8
|
+
export * from './enrollment/unit-progress';
|
|
9
|
+
export * from './enrollment/learning-outcome';
|
|
10
|
+
export { cancelEnrollment, DIRECT_SOURCE_KIND, enroll, learningAddressOf, withdraw, type LearningSource, LEARNING_CERTIFICATE_KIND, LEARNING_REGISTER_KIND } from './enrollment';
|
|
11
|
+
export declare const entities: (typeof import("./course").Course | typeof import("./course").CourseVersion | typeof import("./course").CourseUnit | typeof import("./course").CourseQuestion | typeof import("./enrollment").Enrollment | typeof import("./enrollment").QuizAttempt | typeof import("./enrollment").UnitProgress)[];
|
|
12
|
+
export declare const schema: {
|
|
13
|
+
resolverClasses: (typeof import("./course/course-mutation").CourseMutation | typeof import("./course/course-query").CourseQuery | typeof import("./course/course-query").CourseVersionFields | typeof import("./course/course-query").CourseUnitFields | typeof import("./course/course-query").CourseQuestionFields | typeof import("./enrollment/enrollment-mutation").EnrollmentMutation | typeof import("./enrollment/enrollment-query").EnrollmentQuery)[];
|
|
14
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.schema = exports.entities = exports.LEARNING_REGISTER_KIND = exports.LEARNING_CERTIFICATE_KIND = exports.withdraw = exports.learningAddressOf = exports.enroll = exports.DIRECT_SOURCE_KIND = exports.cancelEnrollment = exports.addDraftedQuestions = exports.questionsOf = exports.unitsOf = exports.publishedVersionOf = exports.COURSE_UNIT_REF_TYPE = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
/* EXPORT ENTITY TYPES */
|
|
6
|
+
tslib_1.__exportStar(require("./course/course"), exports);
|
|
7
|
+
tslib_1.__exportStar(require("./course/course-question"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./course/course-unit"), exports);
|
|
9
|
+
tslib_1.__exportStar(require("./course/course-version"), exports);
|
|
10
|
+
var course_1 = require("./course");
|
|
11
|
+
Object.defineProperty(exports, "COURSE_UNIT_REF_TYPE", { enumerable: true, get: function () { return course_1.COURSE_UNIT_REF_TYPE; } });
|
|
12
|
+
Object.defineProperty(exports, "publishedVersionOf", { enumerable: true, get: function () { return course_1.publishedVersionOf; } });
|
|
13
|
+
Object.defineProperty(exports, "unitsOf", { enumerable: true, get: function () { return course_1.unitsOf; } });
|
|
14
|
+
Object.defineProperty(exports, "questionsOf", { enumerable: true, get: function () { return course_1.questionsOf; } });
|
|
15
|
+
Object.defineProperty(exports, "addDraftedQuestions", { enumerable: true, get: function () { return course_1.addDraftedQuestions; } });
|
|
16
|
+
tslib_1.__exportStar(require("./enrollment/enrollment"), exports);
|
|
17
|
+
tslib_1.__exportStar(require("./enrollment/quiz-attempt"), exports);
|
|
18
|
+
tslib_1.__exportStar(require("./enrollment/unit-progress"), exports);
|
|
19
|
+
tslib_1.__exportStar(require("./enrollment/learning-outcome"), exports);
|
|
20
|
+
var enrollment_1 = require("./enrollment");
|
|
21
|
+
Object.defineProperty(exports, "cancelEnrollment", { enumerable: true, get: function () { return enrollment_1.cancelEnrollment; } });
|
|
22
|
+
Object.defineProperty(exports, "DIRECT_SOURCE_KIND", { enumerable: true, get: function () { return enrollment_1.DIRECT_SOURCE_KIND; } });
|
|
23
|
+
Object.defineProperty(exports, "enroll", { enumerable: true, get: function () { return enrollment_1.enroll; } });
|
|
24
|
+
Object.defineProperty(exports, "learningAddressOf", { enumerable: true, get: function () { return enrollment_1.learningAddressOf; } });
|
|
25
|
+
Object.defineProperty(exports, "withdraw", { enumerable: true, get: function () { return enrollment_1.withdraw; } });
|
|
26
|
+
Object.defineProperty(exports, "LEARNING_CERTIFICATE_KIND", { enumerable: true, get: function () { return enrollment_1.LEARNING_CERTIFICATE_KIND; } });
|
|
27
|
+
Object.defineProperty(exports, "LEARNING_REGISTER_KIND", { enumerable: true, get: function () { return enrollment_1.LEARNING_REGISTER_KIND; } });
|
|
28
|
+
/* IMPORT ENTITIES AND RESOLVERS */
|
|
29
|
+
const course_2 = require("./course");
|
|
30
|
+
const enrollment_2 = require("./enrollment");
|
|
31
|
+
exports.entities = [
|
|
32
|
+
/* ENTITIES */
|
|
33
|
+
...course_2.entities,
|
|
34
|
+
...enrollment_2.entities
|
|
35
|
+
];
|
|
36
|
+
exports.schema = {
|
|
37
|
+
resolverClasses: [
|
|
38
|
+
/* RESOLVER CLASSES */
|
|
39
|
+
...course_2.resolvers,
|
|
40
|
+
...enrollment_2.resolvers
|
|
41
|
+
]
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../server/service/index.ts"],"names":[],"mappings":";;;;AAAA,yBAAyB;AACzB,0DAA+B;AAC/B,mEAAwC;AACxC,+DAAoC;AACpC,kEAAuC;AACvC,mCAA8G;AAArG,8GAAA,oBAAoB,OAAA;AAAE,4GAAA,kBAAkB,OAAA;AAAE,iGAAA,OAAO,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,6GAAA,mBAAmB,OAAA;AAC5F,kEAAuC;AACvC,oEAAyC;AACzC,qEAA0C;AAC1C,wEAA6C;AAC7C,2CAAgL;AAAvK,8GAAA,gBAAgB,OAAA;AAAE,gHAAA,kBAAkB,OAAA;AAAE,oGAAA,MAAM,OAAA;AAAE,+GAAA,iBAAiB,OAAA;AAAE,sGAAA,QAAQ,OAAA;AAAuB,uHAAA,yBAAyB,OAAA;AAAE,oHAAA,sBAAsB,OAAA;AAE1J,mCAAmC;AACnC,qCAAmF;AACnF,6CAA+F;AAElF,QAAA,QAAQ,GAAG;IACtB,cAAc;IACd,GAAG,iBAAc;IACjB,GAAG,qBAAkB;CACtB,CAAA;AAEY,QAAA,MAAM,GAAG;IACpB,eAAe,EAAE;QACf,sBAAsB;QACtB,GAAG,kBAAe;QAClB,GAAG,sBAAmB;KACvB;CACF,CAAA","sourcesContent":["/* EXPORT ENTITY TYPES */\nexport * from './course/course'\nexport * from './course/course-question'\nexport * from './course/course-unit'\nexport * from './course/course-version'\nexport { COURSE_UNIT_REF_TYPE, publishedVersionOf, unitsOf, questionsOf, addDraftedQuestions } from './course'\nexport * from './enrollment/enrollment'\nexport * from './enrollment/quiz-attempt'\nexport * from './enrollment/unit-progress'\nexport * from './enrollment/learning-outcome'\nexport { cancelEnrollment, DIRECT_SOURCE_KIND, enroll, learningAddressOf, withdraw, type LearningSource, LEARNING_CERTIFICATE_KIND, LEARNING_REGISTER_KIND } from './enrollment'\n\n/* IMPORT ENTITIES AND RESOLVERS */\nimport { entities as CourseEntities, resolvers as CourseResolvers } from './course'\nimport { entities as EnrollmentEntities, resolvers as EnrollmentResolvers } from './enrollment'\n\nexport const entities = [\n /* ENTITIES */\n ...CourseEntities,\n ...EnrollmentEntities\n]\n\nexport const schema = {\n resolverClasses: [\n /* RESOLVER CLASSES */\n ...CourseResolvers,\n ...EnrollmentResolvers\n ]\n}\n"]}
|