@openstax/ts-utils 1.30.2 → 1.31.0
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/cjs/services/apiGateway/index.d.ts +1 -1
- package/dist/cjs/services/documentStore/fileSystemAssert.d.ts +1 -0
- package/dist/cjs/services/documentStore/fileSystemAssert.js +14 -0
- package/dist/cjs/services/documentStore/unversioned/file-system.js +15 -2
- package/dist/cjs/services/documentStore/versioned/file-system.js +5 -0
- package/dist/cjs/services/fileServer/index.d.ts +0 -11
- package/dist/cjs/services/fileServer/localFileServer.js +0 -47
- package/dist/cjs/services/fileServer/s3FileServer.js +0 -70
- package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +13 -4
- package/dist/cjs/services/lrsGateway/xapiUtils.js +19 -4
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/services/apiGateway/index.d.ts +1 -1
- package/dist/esm/services/documentStore/fileSystemAssert.d.ts +1 -0
- package/dist/esm/services/documentStore/fileSystemAssert.js +10 -0
- package/dist/esm/services/documentStore/unversioned/file-system.js +15 -2
- package/dist/esm/services/documentStore/versioned/file-system.js +5 -0
- package/dist/esm/services/fileServer/index.d.ts +0 -11
- package/dist/esm/services/fileServer/localFileServer.js +0 -47
- package/dist/esm/services/fileServer/s3FileServer.js +1 -68
- package/dist/esm/services/lrsGateway/xapiUtils.d.ts +13 -4
- package/dist/esm/services/lrsGateway/xapiUtils.js +19 -4
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +5 -6
- package/script/bin/.init-params-script.bash.swp +0 -0
|
@@ -38,7 +38,7 @@ export type ApiClientResponse<Ro> = Ro extends any ? {
|
|
|
38
38
|
} : never;
|
|
39
39
|
export type ExpandRoute<T> = T extends ((...args: infer A) => infer R) & {
|
|
40
40
|
renderUrl: (...args: infer Ar) => Promise<string>;
|
|
41
|
-
} ? (
|
|
41
|
+
} ? (...args: A) => R & {
|
|
42
42
|
renderUrl: (...args: Ar) => Promise<string>;
|
|
43
43
|
} : never;
|
|
44
44
|
export type MapRoutesToClient<Ru> = [Ru] extends [AnyRoute<Ru>] ? {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const assertNoUndefined: (obj: any, path?: string[]) => void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertNoUndefined = void 0;
|
|
4
|
+
const assertNoUndefined = (obj, path = []) => {
|
|
5
|
+
if (obj === undefined) {
|
|
6
|
+
throw new Error(`unknown attribute type ${typeof obj} with value ${obj} at ${path.join('.') || 'root'}.`);
|
|
7
|
+
}
|
|
8
|
+
if (obj && typeof obj === 'object') {
|
|
9
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
10
|
+
(0, exports.assertNoUndefined)(value, [...path, key]);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
exports.assertNoUndefined = assertNoUndefined;
|
|
@@ -43,6 +43,7 @@ const __1 = require("../../..");
|
|
|
43
43
|
const config_1 = require("../../../config");
|
|
44
44
|
const errors_1 = require("../../../errors");
|
|
45
45
|
const guards_1 = require("../../../guards");
|
|
46
|
+
const fileSystemAssert_1 = require("../fileSystemAssert");
|
|
46
47
|
const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, options) => {
|
|
47
48
|
const tableName = (0, config_1.resolveConfigValue)(configProvider[initializer.configSpace || 'fileSystem'].tableName);
|
|
48
49
|
const tablePath = tableName.then((table) => path_1.default.join(initializer.dataDir, table));
|
|
@@ -81,6 +82,7 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
|
|
|
81
82
|
return {
|
|
82
83
|
loadAllDocumentsTheBadWay,
|
|
83
84
|
getItemsByField: async (key, value, pageKey) => {
|
|
85
|
+
(0, fileSystemAssert_1.assertNoUndefined)(value, [key]);
|
|
84
86
|
const pageSize = 10;
|
|
85
87
|
const items = await loadAllDocumentsTheBadWay();
|
|
86
88
|
const filteredItems = items.filter((item) => item[key] === value);
|
|
@@ -92,11 +94,18 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
|
|
|
92
94
|
return { items: paginatedItems, nextPageToken };
|
|
93
95
|
},
|
|
94
96
|
batchGetItem: async (ids) => {
|
|
95
|
-
const items = await Promise.all(ids.map((id) =>
|
|
97
|
+
const items = await Promise.all(ids.map((id) => {
|
|
98
|
+
(0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
|
|
99
|
+
return load(hashFilename(id));
|
|
100
|
+
}));
|
|
96
101
|
return items.filter(guards_1.isDefined);
|
|
97
102
|
},
|
|
98
|
-
getItem: (id) =>
|
|
103
|
+
getItem: (id) => {
|
|
104
|
+
(0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
|
|
105
|
+
return load(hashFilename(id));
|
|
106
|
+
},
|
|
99
107
|
incrementItemAttribute: async (id, attribute) => {
|
|
108
|
+
(0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
|
|
100
109
|
const filename = hashFilename(id);
|
|
101
110
|
const path = await filePath(filename);
|
|
102
111
|
await mkTableDir;
|
|
@@ -127,11 +136,13 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
|
|
|
127
136
|
throw new errors_1.NotFoundError(`Item with ${hashKey.toString()} "${id}" does not exist`);
|
|
128
137
|
}
|
|
129
138
|
const newItem = { ...data, ...item };
|
|
139
|
+
(0, fileSystemAssert_1.assertNoUndefined)(newItem);
|
|
130
140
|
return new Promise((resolve, reject) => {
|
|
131
141
|
writeFile(path, JSON.stringify(newItem, null, 2), (err) => err ? reject(err) : resolve(newItem));
|
|
132
142
|
});
|
|
133
143
|
},
|
|
134
144
|
putItem: async (item) => {
|
|
145
|
+
(0, fileSystemAssert_1.assertNoUndefined)(item);
|
|
135
146
|
const path = await filePath(hashFilename(item[hashKey]));
|
|
136
147
|
await mkTableDir;
|
|
137
148
|
return new Promise((resolve, reject) => {
|
|
@@ -139,6 +150,7 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
|
|
|
139
150
|
});
|
|
140
151
|
},
|
|
141
152
|
createItem: async (item) => {
|
|
153
|
+
(0, fileSystemAssert_1.assertNoUndefined)(item);
|
|
142
154
|
const hashed = hashFilename(item[hashKey]);
|
|
143
155
|
const existingItem = await load(hashed);
|
|
144
156
|
if (existingItem) {
|
|
@@ -159,6 +171,7 @@ const fileSystemUnversionedDocumentStore = (initializer) => () => (configProvide
|
|
|
159
171
|
// Process items sequentially to ensure consistent conflict detection
|
|
160
172
|
// Note: concurrency parameter is ignored for filesystem to avoid race conditions
|
|
161
173
|
for (const item of items) {
|
|
174
|
+
(0, fileSystemAssert_1.assertNoUndefined)(item);
|
|
162
175
|
try {
|
|
163
176
|
const hashed = hashFilename(item[hashKey]);
|
|
164
177
|
const existingItem = await load(hashed);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.fileSystemVersionedDocumentStore = void 0;
|
|
4
|
+
const fileSystemAssert_1 = require("../fileSystemAssert");
|
|
4
5
|
const file_system_1 = require("../unversioned/file-system");
|
|
5
6
|
const PAGE_LIMIT = 5;
|
|
6
7
|
const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider) => (_, hashKey, options) => {
|
|
@@ -12,6 +13,7 @@ const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider)
|
|
|
12
13
|
}));
|
|
13
14
|
},
|
|
14
15
|
getVersions: async (id, startVersion) => {
|
|
16
|
+
(0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
|
|
15
17
|
const item = await unversionedDocuments.getItem(id);
|
|
16
18
|
const versions = item === null || item === void 0 ? void 0 : item.items.reverse();
|
|
17
19
|
if (!versions) {
|
|
@@ -26,6 +28,7 @@ const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider)
|
|
|
26
28
|
};
|
|
27
29
|
},
|
|
28
30
|
getItem: async (id, timestamp) => {
|
|
31
|
+
(0, fileSystemAssert_1.assertNoUndefined)(id, ['id']);
|
|
29
32
|
const item = await unversionedDocuments.getItem(id);
|
|
30
33
|
if (timestamp) {
|
|
31
34
|
return item === null || item === void 0 ? void 0 : item.items.find(version => version.timestamp === timestamp);
|
|
@@ -41,6 +44,7 @@ const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider)
|
|
|
41
44
|
save: async (changes) => {
|
|
42
45
|
var _a;
|
|
43
46
|
const document = { ...item, ...changes, timestamp, author };
|
|
47
|
+
(0, fileSystemAssert_1.assertNoUndefined)(document);
|
|
44
48
|
const container = (_a = await unversionedDocuments.getItem(document[hashKey])) !== null && _a !== void 0 ? _a : { id: document[hashKey], items: [] };
|
|
45
49
|
const updated = { ...container, items: [...container.items, document] };
|
|
46
50
|
await unversionedDocuments.putItem(updated);
|
|
@@ -52,6 +56,7 @@ const fileSystemVersionedDocumentStore = (initializer) => () => (configProvider)
|
|
|
52
56
|
var _a;
|
|
53
57
|
const author = (options === null || options === void 0 ? void 0 : options.getAuthor) ? await options.getAuthor(...authorArgs) : authorArgs[0];
|
|
54
58
|
const document = { ...item, timestamp: new Date().getTime(), author };
|
|
59
|
+
(0, fileSystemAssert_1.assertNoUndefined)(document);
|
|
55
60
|
const container = (_a = await unversionedDocuments.getItem(document[hashKey])) !== null && _a !== void 0 ? _a : { id: document[hashKey], items: [] };
|
|
56
61
|
const updated = { ...container, items: [...container.items, document] };
|
|
57
62
|
await unversionedDocuments.putItem(updated);
|
|
@@ -14,16 +14,5 @@ export interface FileServerAdapter {
|
|
|
14
14
|
putFileContent: (source: FileValue, content: string) => Promise<FileValue>;
|
|
15
15
|
getSignedViewerUrl: (source: FileValue) => Promise<string>;
|
|
16
16
|
getFileContent: (source: FileValue) => Promise<Buffer>;
|
|
17
|
-
getSignedFileUploadConfig: () => Promise<{
|
|
18
|
-
url: string;
|
|
19
|
-
payload: {
|
|
20
|
-
[key: string]: string;
|
|
21
|
-
};
|
|
22
|
-
}>;
|
|
23
|
-
copyFileTo: (source: FileValue, destinationPath: string) => Promise<FileValue>;
|
|
24
|
-
copyFileToDirectory: (source: FileValue, destinationDirectory: string) => Promise<FileValue>;
|
|
25
|
-
isTemporaryUpload: (source: FileValue) => boolean;
|
|
26
|
-
getFileChecksum: (source: FileValue) => Promise<string>;
|
|
27
|
-
filesEqual: (sourceA: FileValue, sourceB: FileValue) => Promise<boolean>;
|
|
28
17
|
}
|
|
29
18
|
export declare const isFileOrFolder: (thing: any) => thing is FileValue | FolderValue;
|
|
@@ -5,14 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.localFileServer = void 0;
|
|
7
7
|
/* cspell:ignore originalname */
|
|
8
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
9
8
|
const fs_1 = __importDefault(require("fs"));
|
|
10
9
|
const https_1 = __importDefault(require("https"));
|
|
11
10
|
const path_1 = __importDefault(require("path"));
|
|
12
11
|
const cors_1 = __importDefault(require("cors"));
|
|
13
12
|
const express_1 = __importDefault(require("express"));
|
|
14
13
|
const multer_1 = __importDefault(require("multer"));
|
|
15
|
-
const uuid_1 = require("uuid");
|
|
16
14
|
const assertions_1 = require("../../assertions");
|
|
17
15
|
const config_1 = require("../../config");
|
|
18
16
|
const guards_1 = require("../../guards");
|
|
@@ -74,55 +72,10 @@ const localFileServer = (initializer) => (configProvider) => {
|
|
|
74
72
|
await fs_1.default.promises.writeFile(filePath, content);
|
|
75
73
|
return source;
|
|
76
74
|
};
|
|
77
|
-
const getSignedFileUploadConfig = async () => {
|
|
78
|
-
const prefix = 'uploads/' + (0, uuid_1.v4)();
|
|
79
|
-
return {
|
|
80
|
-
url: `https://${await host}:${await port}/`,
|
|
81
|
-
payload: {
|
|
82
|
-
key: prefix + '/${filename}',
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
};
|
|
86
|
-
const copyFileTo = async (source, destinationPath) => {
|
|
87
|
-
const sourcePath = path_1.default.join(await fileDir, source.path);
|
|
88
|
-
const destPath = path_1.default.join(await fileDir, destinationPath);
|
|
89
|
-
const destDirectory = path_1.default.dirname(destPath);
|
|
90
|
-
await fs_1.default.promises.mkdir(destDirectory, { recursive: true });
|
|
91
|
-
await fs_1.default.promises.copyFile(sourcePath, destPath);
|
|
92
|
-
return {
|
|
93
|
-
...source,
|
|
94
|
-
path: destinationPath
|
|
95
|
-
};
|
|
96
|
-
};
|
|
97
|
-
const copyFileToDirectory = async (source, destination) => {
|
|
98
|
-
const destinationPath = path_1.default.join(destination, source.label);
|
|
99
|
-
return copyFileTo(source, destinationPath);
|
|
100
|
-
};
|
|
101
|
-
const isTemporaryUpload = (source) => {
|
|
102
|
-
return source.path.indexOf('uploads/') === 0;
|
|
103
|
-
};
|
|
104
|
-
const getFileChecksum = async (source) => {
|
|
105
|
-
const filePath = path_1.default.join(await fileDir, source.path);
|
|
106
|
-
const fileContent = await fs_1.default.promises.readFile(filePath);
|
|
107
|
-
return crypto_1.default.createHash('md5').update(fileContent).digest('hex');
|
|
108
|
-
};
|
|
109
|
-
const filesEqual = async (sourceA, sourceB) => {
|
|
110
|
-
const [aSum, bSum] = await Promise.all([
|
|
111
|
-
getFileChecksum(sourceA),
|
|
112
|
-
getFileChecksum(sourceB)
|
|
113
|
-
]);
|
|
114
|
-
return aSum === bSum;
|
|
115
|
-
};
|
|
116
75
|
return {
|
|
117
76
|
getSignedViewerUrl,
|
|
118
77
|
getFileContent,
|
|
119
78
|
putFileContent,
|
|
120
|
-
getSignedFileUploadConfig,
|
|
121
|
-
copyFileTo,
|
|
122
|
-
copyFileToDirectory,
|
|
123
|
-
isTemporaryUpload,
|
|
124
|
-
getFileChecksum,
|
|
125
|
-
filesEqual,
|
|
126
79
|
};
|
|
127
80
|
};
|
|
128
81
|
exports.localFileServer = localFileServer;
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.s3FileServer = void 0;
|
|
7
4
|
/* cspell:ignore presigner */
|
|
8
5
|
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
9
|
-
const s3_presigned_post_1 = require("@aws-sdk/s3-presigned-post");
|
|
10
6
|
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
11
|
-
const path_1 = __importDefault(require("path"));
|
|
12
|
-
const uuid_1 = require("uuid");
|
|
13
7
|
const __1 = require("../..");
|
|
14
8
|
const assertions_1 = require("../../assertions");
|
|
15
9
|
const config_1 = require("../../config");
|
|
@@ -50,74 +44,10 @@ const s3FileServer = (initializer) => (configProvider) => {
|
|
|
50
44
|
await (await s3Service()).send(command);
|
|
51
45
|
return source;
|
|
52
46
|
};
|
|
53
|
-
/*
|
|
54
|
-
* https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/modules/_aws_sdk_s3_presigned_post.html
|
|
55
|
-
* https://docs.aws.amazon.com/AmazonS3/latest/userguide/HTTPPOSTExamples.html
|
|
56
|
-
* https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-post-example.html
|
|
57
|
-
*/
|
|
58
|
-
const getSignedFileUploadConfig = async () => {
|
|
59
|
-
const prefix = 'uploads/' + (0, uuid_1.v4)();
|
|
60
|
-
const bucket = (await bucketName());
|
|
61
|
-
const Conditions = [
|
|
62
|
-
{ acl: 'private' },
|
|
63
|
-
{ bucket },
|
|
64
|
-
['starts-with', '$key', prefix]
|
|
65
|
-
];
|
|
66
|
-
const defaultFields = {
|
|
67
|
-
acl: 'private',
|
|
68
|
-
};
|
|
69
|
-
const { url, fields } = await (0, s3_presigned_post_1.createPresignedPost)(await s3Service(), {
|
|
70
|
-
Bucket: bucket,
|
|
71
|
-
Key: prefix + '/${filename}',
|
|
72
|
-
Conditions,
|
|
73
|
-
Fields: defaultFields,
|
|
74
|
-
Expires: 3600, // 1 hour
|
|
75
|
-
});
|
|
76
|
-
return {
|
|
77
|
-
url, payload: fields
|
|
78
|
-
};
|
|
79
|
-
};
|
|
80
|
-
const copyFileTo = async (source, destinationPath) => {
|
|
81
|
-
const bucket = (await bucketName());
|
|
82
|
-
const destinationPathWithoutLeadingSlash = destinationPath.replace(/^\//, '');
|
|
83
|
-
const command = new client_s3_1.CopyObjectCommand({
|
|
84
|
-
Bucket: bucket,
|
|
85
|
-
Key: destinationPathWithoutLeadingSlash,
|
|
86
|
-
CopySource: path_1.default.join(bucket, source.path),
|
|
87
|
-
});
|
|
88
|
-
await (await s3Service()).send(command);
|
|
89
|
-
return {
|
|
90
|
-
...source,
|
|
91
|
-
path: destinationPathWithoutLeadingSlash
|
|
92
|
-
};
|
|
93
|
-
};
|
|
94
|
-
const copyFileToDirectory = async (source, destination) => {
|
|
95
|
-
const destinationPath = path_1.default.join(destination, source.label);
|
|
96
|
-
return copyFileTo(source, destinationPath);
|
|
97
|
-
};
|
|
98
|
-
const isTemporaryUpload = (source) => {
|
|
99
|
-
return source.path.indexOf('uploads/') === 0;
|
|
100
|
-
};
|
|
101
|
-
const getFileChecksum = async (source) => {
|
|
102
|
-
const bucket = (await bucketName());
|
|
103
|
-
const command = new client_s3_1.HeadObjectCommand({ Bucket: bucket, Key: source.path });
|
|
104
|
-
const response = await (await s3Service()).send(command);
|
|
105
|
-
return (0, assertions_1.assertDefined)(response.ETag);
|
|
106
|
-
};
|
|
107
|
-
const filesEqual = async (sourceA, sourceB) => {
|
|
108
|
-
const [aSum, bSum] = await Promise.all([getFileChecksum(sourceA), getFileChecksum(sourceB)]);
|
|
109
|
-
return aSum === bSum;
|
|
110
|
-
};
|
|
111
47
|
return {
|
|
112
48
|
getFileContent,
|
|
113
49
|
putFileContent,
|
|
114
50
|
getSignedViewerUrl,
|
|
115
|
-
getSignedFileUploadConfig,
|
|
116
|
-
copyFileTo,
|
|
117
|
-
copyFileToDirectory,
|
|
118
|
-
isTemporaryUpload,
|
|
119
|
-
getFileChecksum,
|
|
120
|
-
filesEqual,
|
|
121
51
|
};
|
|
122
52
|
};
|
|
123
53
|
exports.s3FileServer = s3FileServer;
|
|
@@ -3,11 +3,15 @@ import { AuthProvider } from '../authProvider';
|
|
|
3
3
|
import { ActivityState } from './attempt-utils';
|
|
4
4
|
import { LrsGateway } from '.';
|
|
5
5
|
export interface Grade {
|
|
6
|
-
scoreGiven: number;
|
|
7
|
-
scoreMaximum: number;
|
|
8
|
-
comment?: string;
|
|
9
6
|
activityProgress: 'Initialized' | 'Started' | 'inProgress' | 'Submitted' | 'Completed';
|
|
7
|
+
comment?: string;
|
|
10
8
|
gradingProgress: 'FullyGraded' | 'Pending' | 'PendingManual' | 'Failed' | 'NotReady';
|
|
9
|
+
scoreGiven: number;
|
|
10
|
+
scoreMaximum: number;
|
|
11
|
+
submission?: {
|
|
12
|
+
startedAt?: string;
|
|
13
|
+
submittedAt?: string;
|
|
14
|
+
};
|
|
11
15
|
userId?: string;
|
|
12
16
|
}
|
|
13
17
|
export declare const getRegistrationAttemptInfo: (lrs: LrsGateway, registration: string, options?: {
|
|
@@ -24,7 +28,12 @@ export declare const getScoreGrade: (score: {
|
|
|
24
28
|
raw?: number;
|
|
25
29
|
min?: number;
|
|
26
30
|
max?: number;
|
|
27
|
-
},
|
|
31
|
+
}, options: {
|
|
32
|
+
maxScore?: number;
|
|
33
|
+
startedAt?: string;
|
|
34
|
+
submittedAt?: string;
|
|
35
|
+
userId?: string;
|
|
36
|
+
}) => Grade;
|
|
28
37
|
export type Progress = {
|
|
29
38
|
scaled: number;
|
|
30
39
|
max?: number;
|
|
@@ -29,30 +29,45 @@ exports.getRegistrationAttemptInfo = getRegistrationAttemptInfo;
|
|
|
29
29
|
// generates a payload that can be sent to the LMS, documentation here:
|
|
30
30
|
// lti: http://www.imsglobal.org/spec/lti-ags/v2p0#score-publish-service
|
|
31
31
|
// ltijs: https://cvmcosta.me/ltijs/#/grading
|
|
32
|
-
|
|
32
|
+
// Note: "min" is currently completely ignored
|
|
33
|
+
const getScoreGrade = (score, options) => {
|
|
33
34
|
const { raw, scaled, max } = score;
|
|
35
|
+
const { maxScore, startedAt, submittedAt, userId } = options;
|
|
34
36
|
const scoreMaximum = maxScore !== null && maxScore !== void 0 ? maxScore : 100;
|
|
35
37
|
const scoreGiven = raw && max
|
|
36
38
|
? scoreMaximum / max * raw
|
|
37
39
|
: scaled
|
|
38
40
|
? scaled * scoreMaximum
|
|
39
41
|
: 0;
|
|
42
|
+
const submission = {};
|
|
43
|
+
if (startedAt) {
|
|
44
|
+
submission.startedAt = startedAt;
|
|
45
|
+
}
|
|
46
|
+
if (submittedAt) {
|
|
47
|
+
submission.submittedAt = submittedAt;
|
|
48
|
+
}
|
|
40
49
|
return {
|
|
41
50
|
userId,
|
|
42
|
-
activityProgress:
|
|
51
|
+
activityProgress: submittedAt ? 'Completed' : 'Started',
|
|
43
52
|
// canvas assumes that anything that isn't 'FullyGraded' requires manual grading and displays a "needs grading" icon.
|
|
44
53
|
// if you warp your mind you can consider the portion of the assignment which is completed to be fully graded.
|
|
45
54
|
gradingProgress: 'FullyGraded',
|
|
46
55
|
scoreMaximum,
|
|
47
56
|
scoreGiven: (0, __1.roundToPrecision)(scoreGiven, -2),
|
|
57
|
+
submission,
|
|
48
58
|
};
|
|
49
59
|
};
|
|
50
60
|
exports.getScoreGrade = getScoreGrade;
|
|
51
61
|
// These methods assign 0's to incomplete activities
|
|
52
62
|
const getCompletedActivityStateGradeAndProgress = ({ name, scoreMaximum, state, userId }) => {
|
|
53
|
-
var _a, _b;
|
|
63
|
+
var _a, _b, _c, _d;
|
|
54
64
|
return ({
|
|
55
|
-
grade: (0, exports.getScoreGrade)(((_b = (_a = state.currentAttemptCompleted) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.score) || {},
|
|
65
|
+
grade: (0, exports.getScoreGrade)(((_b = (_a = state.currentAttemptCompleted) === null || _a === void 0 ? void 0 : _a.result) === null || _b === void 0 ? void 0 : _b.score) || {}, {
|
|
66
|
+
maxScore: scoreMaximum,
|
|
67
|
+
startedAt: (_c = state.currentAttempt) === null || _c === void 0 ? void 0 : _c.timestamp,
|
|
68
|
+
submittedAt: (_d = state.currentAttemptCompleted) === null || _d === void 0 ? void 0 : _d.timestamp,
|
|
69
|
+
userId,
|
|
70
|
+
}),
|
|
56
71
|
progress: {
|
|
57
72
|
scaled: state.currentAttemptCompleted ? 1 : 0,
|
|
58
73
|
},
|