dce-expresskit 4.0.0 → 4.2.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/lib/constants/LOG_REVIEW_PAGE_SIZE.d.ts +6 -0
- package/lib/constants/LOG_REVIEW_PAGE_SIZE.js +9 -0
- package/lib/constants/LOG_REVIEW_PAGE_SIZE.js.map +1 -0
- package/lib/helpers/dataSigner.js +7 -5
- package/lib/helpers/dataSigner.js.map +1 -1
- package/lib/helpers/genRouteHandler.js +45 -20
- package/lib/helpers/genRouteHandler.js.map +1 -1
- package/lib/helpers/getLogReviewerLogs.d.ts +27 -0
- package/lib/helpers/getLogReviewerLogs.js +238 -0
- package/lib/helpers/getLogReviewerLogs.js.map +1 -0
- package/lib/helpers/initExpressKitCollections.d.ts +33 -0
- package/lib/helpers/initExpressKitCollections.js +180 -0
- package/lib/helpers/initExpressKitCollections.js.map +1 -0
- package/lib/helpers/initServer.d.ts +0 -33
- package/lib/helpers/initServer.js +31 -75
- package/lib/helpers/initServer.js.map +1 -1
- package/lib/index.d.ts +2 -3
- package/lib/index.js +3 -5
- package/lib/index.js.map +1 -1
- package/lib/types/ExpressKitErrorCode.d.ts +2 -1
- package/lib/types/ExpressKitErrorCode.js +1 -0
- package/lib/types/ExpressKitErrorCode.js.map +1 -1
- package/lib/types/LogReviewerAdmin.d.ts +10 -0
- package/lib/types/LogReviewerAdmin.js +3 -0
- package/lib/types/LogReviewerAdmin.js.map +1 -0
- package/lib/types/SelectAdmin.d.ts +10 -0
- package/lib/types/SelectAdmin.js +3 -0
- package/lib/types/SelectAdmin.js.map +1 -0
- package/package.json +2 -2
- package/src/constants/LOG_REVIEW_PAGE_SIZE.ts +7 -0
- package/src/helpers/dataSigner.ts +2 -2
- package/src/helpers/genRouteHandler.ts +28 -4
- package/src/helpers/getLogReviewerLogs.ts +260 -0
- package/src/helpers/initExpressKitCollections.ts +144 -0
- package/src/helpers/initServer.ts +43 -84
- package/src/index.ts +2 -4
- package/src/types/ExpressKitErrorCode.ts +1 -0
- package/src/types/LogReviewerAdmin.ts +14 -0
- package/src/types/SelectAdmin.ts +14 -0
- package/src/helpers/initCrossServerCredentialCollection.ts +0 -19
- package/src/helpers/initLogCollection.ts +0 -30
|
@@ -19,9 +19,7 @@ import {
|
|
|
19
19
|
import { getLaunchInfo } from 'caccl/server';
|
|
20
20
|
|
|
21
21
|
// Import caccl functions
|
|
22
|
-
import {
|
|
23
|
-
internalGetLogCollection,
|
|
24
|
-
} from './initServer';
|
|
22
|
+
import initExpressKitCollections, { internalGetLogCollection, internalGetSelectAdminCollection } from './initExpressKitCollections';
|
|
25
23
|
|
|
26
24
|
// Import shared types
|
|
27
25
|
import ExpressKitErrorCode from '../types/ExpressKitErrorCode';
|
|
@@ -563,6 +561,32 @@ const genRouteHandler = (
|
|
|
563
561
|
);
|
|
564
562
|
}
|
|
565
563
|
|
|
564
|
+
// Add Select Admin endpoint security
|
|
565
|
+
if (
|
|
566
|
+
// This is a select admin endpoint
|
|
567
|
+
req.path.startsWith('/api/admin/select')
|
|
568
|
+
) {
|
|
569
|
+
// Get select admin collection
|
|
570
|
+
const selectAdminCollection = await internalGetSelectAdminCollection();
|
|
571
|
+
const id = output.userId;
|
|
572
|
+
|
|
573
|
+
// Find match if exists in select admin collection
|
|
574
|
+
const [match] = await selectAdminCollection.find({ id });
|
|
575
|
+
|
|
576
|
+
// Check that user exists in select admin collection
|
|
577
|
+
if (!match) {
|
|
578
|
+
// User does not have access
|
|
579
|
+
return handleError(
|
|
580
|
+
res,
|
|
581
|
+
{
|
|
582
|
+
message: 'This action is only allowed for select Canvas admins. Please go back to Canvas, log in as a select admin, and try again.',
|
|
583
|
+
code: ExpressKitErrorCode.NotSelectAdmin,
|
|
584
|
+
status: 401,
|
|
585
|
+
},
|
|
586
|
+
);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
566
590
|
/*----------------------------------------*/
|
|
567
591
|
/* ------------- Log Handler ------------ */
|
|
568
592
|
/*----------------------------------------*/
|
|
@@ -675,7 +699,7 @@ const genRouteHandler = (
|
|
|
675
699
|
};
|
|
676
700
|
|
|
677
701
|
// Either print to console or save to db
|
|
678
|
-
const logCollection = internalGetLogCollection();
|
|
702
|
+
const logCollection = await internalGetLogCollection();
|
|
679
703
|
if (logCollection) {
|
|
680
704
|
// Store to the log collection
|
|
681
705
|
await logCollection.insert(log);
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// Import dce-mango
|
|
2
|
+
import { Collection } from 'dce-mango';
|
|
3
|
+
|
|
4
|
+
// Import dce-reactkit
|
|
5
|
+
import {
|
|
6
|
+
DAY_IN_MS,
|
|
7
|
+
Log,
|
|
8
|
+
LogReviewerFilterState,
|
|
9
|
+
LogType
|
|
10
|
+
} from 'dce-reactkit';
|
|
11
|
+
|
|
12
|
+
// Import shared types
|
|
13
|
+
import LOG_REVIEW_PAGE_SIZE from '../constants/LOG_REVIEW_PAGE_SIZE';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get logs for the log reviewer interface
|
|
17
|
+
* @author Yuen Ler Chow
|
|
18
|
+
* @param opts object containing all arguments
|
|
19
|
+
* @param opts.pageNumber the page number to retrieve (1-indexed)
|
|
20
|
+
* @param opts.filters filter criteria for logs
|
|
21
|
+
* @param opts.countDocuments if true, count number of documents matching
|
|
22
|
+
* filters and return num pages (not always required because if changing pages,
|
|
23
|
+
* we don't need to recount documents)
|
|
24
|
+
* @param opts.logCollection MongoDB collection containing logs
|
|
25
|
+
* @returns object with logs for the requested page and optionally total number of pages
|
|
26
|
+
*/
|
|
27
|
+
const getLogReviewerLogs = async (
|
|
28
|
+
opts: {
|
|
29
|
+
pageNumber: number,
|
|
30
|
+
filters: LogReviewerFilterState,
|
|
31
|
+
countDocuments: boolean,
|
|
32
|
+
logCollection: Collection<Log>,
|
|
33
|
+
},
|
|
34
|
+
) => {
|
|
35
|
+
// Destructure opts
|
|
36
|
+
const {
|
|
37
|
+
pageNumber,
|
|
38
|
+
filters,
|
|
39
|
+
countDocuments,
|
|
40
|
+
logCollection,
|
|
41
|
+
} = opts;
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
// Destructure filters
|
|
45
|
+
const {
|
|
46
|
+
dateFilterState,
|
|
47
|
+
contextFilterState,
|
|
48
|
+
tagFilterState,
|
|
49
|
+
actionErrorFilterState,
|
|
50
|
+
advancedFilterState,
|
|
51
|
+
} = filters as LogReviewerFilterState;
|
|
52
|
+
|
|
53
|
+
// Build MongoDB query based on filters
|
|
54
|
+
const query: { [k: string]: any } = {};
|
|
55
|
+
|
|
56
|
+
/* -------------- Date Filter ------------- */
|
|
57
|
+
|
|
58
|
+
// Convert start and end dates from the dateFilterState into timestamps
|
|
59
|
+
const { startDate, endDate } = dateFilterState;
|
|
60
|
+
const startTimestamp = new Date(
|
|
61
|
+
`${startDate.month}/${startDate.day}/${startDate.year}`,
|
|
62
|
+
).getTime();
|
|
63
|
+
const endTimestamp = (
|
|
64
|
+
(new Date(`${endDate.month}/${endDate.day}/${endDate.year}`)).getTime()
|
|
65
|
+
+ DAY_IN_MS
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Add a date range condition to the query
|
|
69
|
+
query.timestamp = {
|
|
70
|
+
$gte: startTimestamp,
|
|
71
|
+
$lt: endTimestamp,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/* ------------ Context Filter ------------ */
|
|
75
|
+
|
|
76
|
+
// Process context filters to include selected contexts and subcontexts
|
|
77
|
+
const contextConditions: { [k: string]: any }[] = [];
|
|
78
|
+
Object.keys(contextFilterState).forEach((context) => {
|
|
79
|
+
const value = contextFilterState[context];
|
|
80
|
+
if (typeof value === 'boolean') {
|
|
81
|
+
if (value) {
|
|
82
|
+
// The entire context is selected
|
|
83
|
+
contextConditions.push({ context });
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
// The context has subcontexts
|
|
87
|
+
const subcontexts = Object.keys(value).filter((subcontext) => {
|
|
88
|
+
return value[subcontext];
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (subcontexts.length > 0) {
|
|
92
|
+
contextConditions.push({
|
|
93
|
+
context,
|
|
94
|
+
subcontext: { $in: subcontexts },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
if (contextConditions.length > 0) {
|
|
100
|
+
query.$or = contextConditions;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* -------------- Tag Filter -------------- */
|
|
104
|
+
|
|
105
|
+
const selectedTags = Object.keys(tagFilterState).filter((tag) => { return tagFilterState[tag]; });
|
|
106
|
+
if (selectedTags.length > 0) {
|
|
107
|
+
query.tags = { $in: selectedTags };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* --------- Action/Error Filter ---------- */
|
|
111
|
+
|
|
112
|
+
if (actionErrorFilterState.type) {
|
|
113
|
+
query.type = actionErrorFilterState.type;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (actionErrorFilterState.type === LogType.Error) {
|
|
117
|
+
if (actionErrorFilterState.errorMessage) {
|
|
118
|
+
// Add error message to the query.
|
|
119
|
+
// $i is used for case-insensitive search, and $regex is used for partial matching
|
|
120
|
+
query.errorMessage = {
|
|
121
|
+
$regex: actionErrorFilterState.errorMessage,
|
|
122
|
+
$options: 'i',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (actionErrorFilterState.errorCode) {
|
|
127
|
+
query.errorCode = {
|
|
128
|
+
$regex: actionErrorFilterState.errorCode,
|
|
129
|
+
$options: 'i',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (actionErrorFilterState.type === LogType.Action) {
|
|
135
|
+
const selectedTargets = (
|
|
136
|
+
Object.keys(actionErrorFilterState.target)
|
|
137
|
+
.filter((target) => {
|
|
138
|
+
return actionErrorFilterState.target[target];
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
const selectedActions = (
|
|
142
|
+
Object.keys(actionErrorFilterState.action)
|
|
143
|
+
.filter((action) => {
|
|
144
|
+
return actionErrorFilterState.action[action];
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
if (selectedTargets.length > 0) {
|
|
148
|
+
query.target = { $in: selectedTargets };
|
|
149
|
+
}
|
|
150
|
+
if (selectedActions.length > 0) {
|
|
151
|
+
query.action = { $in: selectedActions };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/* ------------ Advanced Filter ----------- */
|
|
156
|
+
|
|
157
|
+
if (advancedFilterState.userFirstName) {
|
|
158
|
+
query.userFirstName = {
|
|
159
|
+
$regex: advancedFilterState.userFirstName,
|
|
160
|
+
$options: 'i',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (advancedFilterState.userLastName) {
|
|
165
|
+
query.userLastName = {
|
|
166
|
+
$regex: advancedFilterState.userLastName,
|
|
167
|
+
$options: 'i',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (advancedFilterState.userEmail) {
|
|
172
|
+
query.userEmail = {
|
|
173
|
+
$regex: advancedFilterState.userEmail,
|
|
174
|
+
$options: 'i',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (advancedFilterState.userId) {
|
|
179
|
+
query.userId = Number.parseInt(advancedFilterState.userId, 10);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const roles: {
|
|
183
|
+
isLearner?: boolean,
|
|
184
|
+
isTTM?: boolean,
|
|
185
|
+
isAdmin?: boolean,
|
|
186
|
+
}[] = [];
|
|
187
|
+
if (advancedFilterState.includeLearners) {
|
|
188
|
+
roles.push({ isLearner: true });
|
|
189
|
+
}
|
|
190
|
+
if (advancedFilterState.includeTTMs) {
|
|
191
|
+
roles.push({ isTTM: true });
|
|
192
|
+
}
|
|
193
|
+
if (advancedFilterState.includeAdmins) {
|
|
194
|
+
roles.push({ isAdmin: true });
|
|
195
|
+
}
|
|
196
|
+
// If any roles are selected, add them to the query
|
|
197
|
+
if (roles.length > 0) {
|
|
198
|
+
// The $or operator is used to match any of the roles
|
|
199
|
+
// The $and operator is to ensure that other conditions in the query are met
|
|
200
|
+
query.$and = [{ $or: roles }];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (advancedFilterState.courseId) {
|
|
204
|
+
query.courseId = Number.parseInt(advancedFilterState.courseId, 10);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (advancedFilterState.courseName) {
|
|
208
|
+
query.courseName = {
|
|
209
|
+
$regex: advancedFilterState.courseName,
|
|
210
|
+
$options: 'i',
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (advancedFilterState.isMobile !== undefined) {
|
|
215
|
+
query['device.isMobile'] = Boolean(advancedFilterState.isMobile);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (advancedFilterState.source) {
|
|
219
|
+
query.source = advancedFilterState.source;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (advancedFilterState.routePath) {
|
|
223
|
+
query.routePath = {
|
|
224
|
+
$regex: advancedFilterState.routePath,
|
|
225
|
+
$options: 'i',
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (advancedFilterState.routeTemplate) {
|
|
230
|
+
query.routeTemplate = {
|
|
231
|
+
$regex: advancedFilterState.routeTemplate,
|
|
232
|
+
$options: 'i',
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Query for logs
|
|
237
|
+
const response = await logCollection.findPaged({
|
|
238
|
+
query,
|
|
239
|
+
perPage: LOG_REVIEW_PAGE_SIZE,
|
|
240
|
+
pageNumber,
|
|
241
|
+
sortDescending: true,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Count documents if requested
|
|
245
|
+
if (countDocuments) {
|
|
246
|
+
const numPages = Math.ceil(
|
|
247
|
+
await logCollection.count(query)
|
|
248
|
+
/ LOG_REVIEW_PAGE_SIZE
|
|
249
|
+
);
|
|
250
|
+
return {
|
|
251
|
+
...response,
|
|
252
|
+
numPages,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Return response
|
|
257
|
+
return response;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
export default getLogReviewerLogs;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Import dce-mango
|
|
2
|
+
import { Collection as MangoCollection } from 'dce-mango';
|
|
3
|
+
|
|
4
|
+
// Import dce-reactkit
|
|
5
|
+
import { Log } from 'dce-reactkit';
|
|
6
|
+
|
|
7
|
+
// Import shared types
|
|
8
|
+
import CrossServerCredential from '../types/CrossServerCredential';
|
|
9
|
+
import SelectAdmin from '../types/SelectAdmin';
|
|
10
|
+
import LogReviewerAdmin from '../types/LogReviewerAdmin';
|
|
11
|
+
|
|
12
|
+
/*------------------------------------------------------------------------*/
|
|
13
|
+
/* ------------------------- Collection Storage ------------------------- */
|
|
14
|
+
/*------------------------------------------------------------------------*/
|
|
15
|
+
|
|
16
|
+
// Variables to store collections
|
|
17
|
+
let logCollection: MangoCollection<Log>;
|
|
18
|
+
let crossServerCredentialCollection: MangoCollection<CrossServerCredential>;
|
|
19
|
+
let selectAdminCollection: MangoCollection<SelectAdmin>;
|
|
20
|
+
let logReviewerAdminCollection: MangoCollection<LogReviewerAdmin>;
|
|
21
|
+
|
|
22
|
+
// Promise that resolves when all collections are initialized
|
|
23
|
+
let collectionsInitializedResolve: (v?: unknown) => void;
|
|
24
|
+
let collectionsInitializedReject: (error: Error) => void;
|
|
25
|
+
const collectionsInitialized = new Promise((resolve, reject) => {
|
|
26
|
+
collectionsInitializedResolve = resolve;
|
|
27
|
+
collectionsInitializedReject = reject;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/*------------------------------------------------------------------------*/
|
|
31
|
+
/* ------------------------- Collection Getters ------------------------- */
|
|
32
|
+
/*------------------------------------------------------------------------*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the log collection after initialization
|
|
36
|
+
* @author Gardenia Liu
|
|
37
|
+
*/
|
|
38
|
+
export const internalGetLogCollection = async () => {
|
|
39
|
+
// Wait for collections to be initialized
|
|
40
|
+
await collectionsInitialized;
|
|
41
|
+
|
|
42
|
+
// Return the log collection
|
|
43
|
+
return logCollection;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get the cross server credential collection after initialization
|
|
48
|
+
* @author Gardenia Liu
|
|
49
|
+
*/
|
|
50
|
+
export const internalGetCrossServerCredentialCollection = async () => {
|
|
51
|
+
// Wait for collections to be initialized
|
|
52
|
+
await collectionsInitialized;
|
|
53
|
+
|
|
54
|
+
// Return the cross server credential collection
|
|
55
|
+
return crossServerCredentialCollection;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Get the select admin collection after initialization
|
|
60
|
+
* @author Gardenia Liu
|
|
61
|
+
*/
|
|
62
|
+
export const internalGetSelectAdminCollection = async () => {
|
|
63
|
+
// Wait for collections to be initialized
|
|
64
|
+
await collectionsInitialized;
|
|
65
|
+
|
|
66
|
+
// Return the cross server credential collection
|
|
67
|
+
return selectAdminCollection;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get the log reviewer admin collection after initialization
|
|
72
|
+
* @author Yuen Ler Chow
|
|
73
|
+
*/
|
|
74
|
+
export const internalGetLogReviewerAdminCollection = async () => {
|
|
75
|
+
// Wait for collections to be initialized
|
|
76
|
+
await collectionsInitialized;
|
|
77
|
+
|
|
78
|
+
// Return the log reviewer admin collection
|
|
79
|
+
return logReviewerAdminCollection;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/*------------------------------------------------------------------------*/
|
|
83
|
+
/* -------------------------------- Main -------------------------------- */
|
|
84
|
+
/*------------------------------------------------------------------------*/
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Initialize all collections required for expresskit
|
|
88
|
+
* @author Gardenia Liu
|
|
89
|
+
* @author Gabe Abrams
|
|
90
|
+
* @param Collection the Collection class from dce-mango
|
|
91
|
+
*/
|
|
92
|
+
const initExpressKitCollections = (Collection: typeof MangoCollection) => {
|
|
93
|
+
try {
|
|
94
|
+
// Create and store log collection
|
|
95
|
+
logCollection = new Collection<Log>(
|
|
96
|
+
'Log',
|
|
97
|
+
{
|
|
98
|
+
uniqueIndexKey: 'id',
|
|
99
|
+
indexKeys: [
|
|
100
|
+
'courseId',
|
|
101
|
+
'context',
|
|
102
|
+
'subcontext',
|
|
103
|
+
'tags',
|
|
104
|
+
'year',
|
|
105
|
+
'month',
|
|
106
|
+
'day',
|
|
107
|
+
'hour',
|
|
108
|
+
'type',
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Create and store cross server credential collection
|
|
114
|
+
crossServerCredentialCollection = new Collection<CrossServerCredential>(
|
|
115
|
+
'CrossServerCredential',
|
|
116
|
+
{
|
|
117
|
+
uniqueIndexKey: 'key',
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Create and store select admin collection
|
|
122
|
+
selectAdminCollection = new Collection<SelectAdmin>(
|
|
123
|
+
'SelectAdmin',
|
|
124
|
+
{
|
|
125
|
+
uniqueIndexKey: 'id',
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Create and store log reviewer admin collection
|
|
130
|
+
logReviewerAdminCollection = new Collection<LogReviewerAdmin>(
|
|
131
|
+
'LogReviewerAdmin',
|
|
132
|
+
{
|
|
133
|
+
uniqueIndexKey: 'id',
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// Finished! Resolve the promise
|
|
138
|
+
collectionsInitializedResolve();
|
|
139
|
+
} catch (err) {
|
|
140
|
+
return collectionsInitializedReject(err as Error);
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default initExpressKitCollections;
|
|
@@ -6,51 +6,26 @@ import { Collection } from 'dce-mango';
|
|
|
6
6
|
|
|
7
7
|
// Import dce-reactkit
|
|
8
8
|
import {
|
|
9
|
-
ErrorWithCode,
|
|
10
9
|
ParamType,
|
|
11
10
|
LogFunction,
|
|
12
|
-
LOG_REVIEW_ROUTE_PATH_PREFIX,
|
|
13
11
|
LOG_ROUTE_PATH,
|
|
14
12
|
LOG_REVIEW_STATUS_ROUTE,
|
|
15
|
-
|
|
13
|
+
LOG_REVIEW_GET_LOGS_ROUTE,
|
|
14
|
+
ErrorWithCode,
|
|
16
15
|
} from 'dce-reactkit';
|
|
17
16
|
|
|
18
17
|
// Import shared helpers
|
|
19
18
|
import genRouteHandler from './genRouteHandler';
|
|
19
|
+
import getLogReviewerLogs from './getLogReviewerLogs';
|
|
20
20
|
|
|
21
21
|
// Import shared types
|
|
22
22
|
import ExpressKitErrorCode from '../types/ExpressKitErrorCode';
|
|
23
|
-
import CrossServerCredential from '../types/CrossServerCredential';
|
|
24
|
-
|
|
25
|
-
// Stored copy of dce-mango log collection
|
|
26
|
-
let _logCollection: Collection<Log>;
|
|
27
|
-
|
|
28
|
-
// Stored copy of dce-mango cross-server credential collection
|
|
29
|
-
let _crossServerCredentialCollection: Collection<CrossServerCredential>;
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* Get log collection
|
|
37
|
-
* @author Gabe Abrams
|
|
38
|
-
* @returns log collection if one was included during launch or null if we don't
|
|
39
|
-
* have a log collection (yet)
|
|
40
|
-
*/
|
|
41
|
-
export const internalGetLogCollection = () => {
|
|
42
|
-
return _logCollection ?? null;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get cross-server credential collection
|
|
47
|
-
* @author Gabe Abrams
|
|
48
|
-
* @return cross-server credential collection if one was included during launch or null
|
|
49
|
-
* if we don't have a cross-server credential collection (yet)
|
|
50
|
-
*/
|
|
51
|
-
export const internalGetCrossServerCredentialCollection = () => {
|
|
52
|
-
return _crossServerCredentialCollection ?? null;
|
|
53
|
-
};
|
|
24
|
+
// Import shared helpers
|
|
25
|
+
import {
|
|
26
|
+
internalGetLogCollection,
|
|
27
|
+
internalGetLogReviewerAdminCollection,
|
|
28
|
+
} from './initExpressKitCollections';
|
|
54
29
|
|
|
55
30
|
/*------------------------------------------------------------------------*/
|
|
56
31
|
/* Main */
|
|
@@ -62,31 +37,12 @@ export const internalGetCrossServerCredentialCollection = () => {
|
|
|
62
37
|
* @param opts object containing all arguments
|
|
63
38
|
* @param opts.app express app from inside of the postprocessor function that
|
|
64
39
|
* we will add routes to
|
|
65
|
-
* @param opts.getLaunchInfo CACCL LTI's get launch info function
|
|
66
|
-
* @param [opts.logCollection] mongo collection from dce-mango to use for
|
|
67
|
-
* storing logs. If none is included, logs are written to the console
|
|
68
|
-
* @param [opts.logReviewAdmins=all] info on which admins can review
|
|
69
|
-
* logs from the client. If not included, all Canvas admins are allowed to
|
|
70
|
-
* review logs. If null, no Canvas admins are allowed to review logs.
|
|
71
|
-
* If an array of Canvas userIds (numbers), only Canvas admins with those
|
|
72
|
-
* userIds are allowed to review logs. If a dce-mango collection, only
|
|
73
|
-
* Canvas admins with entries in that collection ({ userId, ...}) are allowed
|
|
74
|
-
* to review logs
|
|
75
|
-
* @param [opts.crossServerCredentialCollection] mongo collection from dce-mango to use for
|
|
76
|
-
* storing cross-server credentials. If none is included, cross-server credentials
|
|
77
|
-
* are not supported
|
|
78
40
|
*/
|
|
79
41
|
const initServer = (
|
|
80
42
|
opts: {
|
|
81
43
|
app: express.Application,
|
|
82
|
-
logReviewAdmins?: (number[] | Collection<any>),
|
|
83
|
-
logCollection?: Collection<Log>,
|
|
84
|
-
crossServerCredentialCollection?: Collection<CrossServerCredential>,
|
|
85
44
|
},
|
|
86
45
|
) => {
|
|
87
|
-
_logCollection = opts.logCollection;
|
|
88
|
-
_crossServerCredentialCollection = opts.crossServerCredentialCollection;
|
|
89
|
-
|
|
90
46
|
/*----------------------------------------*/
|
|
91
47
|
/* Logging */
|
|
92
48
|
/*----------------------------------------*/
|
|
@@ -189,22 +145,15 @@ const initServer = (
|
|
|
189
145
|
return false;
|
|
190
146
|
}
|
|
191
147
|
|
|
192
|
-
|
|
193
|
-
if (!opts.logReviewAdmins) {
|
|
194
|
-
return true;
|
|
195
|
-
}
|
|
148
|
+
/* ------- Look Up Credential ------- */
|
|
196
149
|
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
// Array of userIds
|
|
200
|
-
if (Array.isArray(opts.logReviewAdmins)) {
|
|
201
|
-
return opts.logReviewAdmins.some((allowedId) => {
|
|
202
|
-
return (userId === allowedId);
|
|
203
|
-
});
|
|
204
|
-
}
|
|
150
|
+
// Get the log reviewer admin collection
|
|
151
|
+
const logReviewerAdminCollection = await internalGetLogReviewerAdminCollection();
|
|
205
152
|
|
|
206
|
-
|
|
207
|
-
|
|
153
|
+
// Check if the user is in the log reviewer admin collection
|
|
154
|
+
try {
|
|
155
|
+
// Must be in the collection
|
|
156
|
+
const matches = await logReviewerAdminCollection.find({ id: userId });
|
|
208
157
|
|
|
209
158
|
// Make sure at least one entry matches
|
|
210
159
|
return matches.length > 0;
|
|
@@ -223,36 +172,45 @@ const initServer = (
|
|
|
223
172
|
LOG_REVIEW_STATUS_ROUTE,
|
|
224
173
|
genRouteHandler({
|
|
225
174
|
handler: async ({ params }) => {
|
|
226
|
-
|
|
175
|
+
// Destructure params
|
|
176
|
+
const {
|
|
177
|
+
userId,
|
|
178
|
+
isAdmin,
|
|
179
|
+
} = params;
|
|
180
|
+
|
|
181
|
+
// Check if user can review logs
|
|
227
182
|
const canReview = await canReviewLogs(userId, isAdmin);
|
|
183
|
+
|
|
184
|
+
// Return result
|
|
228
185
|
return canReview;
|
|
229
186
|
},
|
|
230
187
|
}),
|
|
231
188
|
);
|
|
232
189
|
|
|
233
190
|
/**
|
|
234
|
-
* Get
|
|
191
|
+
* Get filtered logs based on provided filters
|
|
235
192
|
* @author Gabe Abrams
|
|
236
|
-
* @
|
|
237
|
-
* @param
|
|
238
|
-
* @
|
|
193
|
+
* @author Yuen Ler Chow
|
|
194
|
+
* @param pageNumber the page number to get
|
|
195
|
+
* @param filters the filters to apply to the logs
|
|
196
|
+
* @returns {Log[]} list of logs that match the filters
|
|
239
197
|
*/
|
|
240
198
|
opts.app.get(
|
|
241
|
-
|
|
199
|
+
LOG_REVIEW_GET_LOGS_ROUTE,
|
|
242
200
|
genRouteHandler({
|
|
243
201
|
paramTypes: {
|
|
244
|
-
year: ParamType.Int,
|
|
245
|
-
month: ParamType.Int,
|
|
246
202
|
pageNumber: ParamType.Int,
|
|
203
|
+
filters: ParamType.JSON,
|
|
204
|
+
countDocuments: ParamType.Boolean,
|
|
247
205
|
},
|
|
248
206
|
handler: async ({ params }) => {
|
|
249
|
-
//
|
|
207
|
+
// Destructure params
|
|
250
208
|
const {
|
|
251
|
-
year,
|
|
252
|
-
month,
|
|
253
209
|
pageNumber,
|
|
254
210
|
userId,
|
|
255
211
|
isAdmin,
|
|
212
|
+
filters,
|
|
213
|
+
countDocuments,
|
|
256
214
|
} = params;
|
|
257
215
|
|
|
258
216
|
// Validate user
|
|
@@ -264,14 +222,15 @@ const initServer = (
|
|
|
264
222
|
);
|
|
265
223
|
}
|
|
266
224
|
|
|
267
|
-
//
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
},
|
|
273
|
-
perPage: 1000,
|
|
225
|
+
// Get log collection
|
|
226
|
+
const logCollection = await internalGetLogCollection();
|
|
227
|
+
|
|
228
|
+
// Get logs
|
|
229
|
+
const response = await getLogReviewerLogs({
|
|
274
230
|
pageNumber,
|
|
231
|
+
filters,
|
|
232
|
+
countDocuments,
|
|
233
|
+
logCollection,
|
|
275
234
|
});
|
|
276
235
|
|
|
277
236
|
// Return response
|
package/src/index.ts
CHANGED
|
@@ -48,14 +48,13 @@ import {
|
|
|
48
48
|
} from 'dce-reactkit';
|
|
49
49
|
|
|
50
50
|
// Import helpers
|
|
51
|
-
import initCrossServerCredentialCollection from './helpers/initCrossServerCredentialCollection';
|
|
52
|
-
import initLogCollection from './helpers/initLogCollection';
|
|
53
51
|
import initServer from './helpers/initServer';
|
|
54
52
|
import genRouteHandler from './helpers/genRouteHandler';
|
|
55
53
|
import handleError from './helpers/handleError';
|
|
56
54
|
import handleSuccess from './helpers/handleSuccess';
|
|
57
55
|
import addDBEditorEndpoints from './helpers/addDBEditorEndpoints';
|
|
58
56
|
import visitEndpointOnAnotherServer from './helpers/visitEndpointOnAnotherServer';
|
|
57
|
+
import initExpressKitCollections from './helpers/initExpressKitCollections';
|
|
59
58
|
|
|
60
59
|
// Import types
|
|
61
60
|
import CrossServerCredential from './types/CrossServerCredential';
|
|
@@ -106,8 +105,7 @@ export {
|
|
|
106
105
|
genRouteHandler,
|
|
107
106
|
handleError,
|
|
108
107
|
handleSuccess,
|
|
109
|
-
|
|
110
|
-
initCrossServerCredentialCollection,
|
|
108
|
+
initExpressKitCollections,
|
|
111
109
|
addDBEditorEndpoints,
|
|
112
110
|
visitEndpointOnAnotherServer,
|
|
113
111
|
// Types
|