@citolab/qti-backend-firebase 0.0.3 → 0.0.5
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/api/app-specific/base/application-specific-base.d.ts +11 -20
- package/dist/api/app-specific/base/application-specific-base.d.ts.map +1 -1
- package/dist/api/app-specific/base/application-specific-base.js +73 -88
- package/dist/api/app-specific/base/application-specific-base.js.map +1 -1
- package/dist/api/app-specific/baseImplementation.d.ts +2 -3
- package/dist/api/app-specific/baseImplementation.d.ts.map +1 -1
- package/dist/api/app-specific/baseImplementation.js +2 -6
- package/dist/api/app-specific/baseImplementation.js.map +1 -1
- package/dist/api/app-specific/index.d.ts +1 -0
- package/dist/api/app-specific/index.d.ts.map +1 -1
- package/dist/api/app-specific/index.js +1 -0
- package/dist/api/app-specific/index.js.map +1 -1
- package/dist/api/app-specific/interface/IApplicationSpecific.d.ts +10 -20
- package/dist/api/app-specific/interface/IApplicationSpecific.d.ts.map +1 -1
- package/dist/api/qti-data.d.ts.map +1 -1
- package/dist/api/qti-data.js +619 -271
- package/dist/api/qti-data.js.map +1 -1
- package/dist/api/qti-resources.d.ts.map +1 -1
- package/dist/api/qti-resources.js +134 -131
- package/dist/api/qti-resources.js.map +1 -1
- package/dist/api/qti-teacher.d.ts.map +1 -1
- package/dist/api/qti-teacher.js +563 -424
- package/dist/api/qti-teacher.js.map +1 -1
- package/dist/api/qti-tools.d.ts.map +1 -1
- package/dist/api/qti-tools.js +213 -41
- package/dist/api/qti-tools.js.map +1 -1
- package/dist/helpers/database.d.ts +35 -24
- package/dist/helpers/database.d.ts.map +1 -1
- package/dist/helpers/database.js +47 -36
- package/dist/helpers/database.js.map +1 -1
- package/dist/helpers/db.d.ts +1 -1
- package/dist/helpers/db.d.ts.map +1 -1
- package/dist/helpers/db.js +3 -7
- package/dist/helpers/db.js.map +1 -1
- package/dist/helpers/endpoint-helpers.d.ts +2 -2
- package/dist/helpers/endpoint-helpers.d.ts.map +1 -1
- package/dist/helpers/endpoint-helpers.js +59 -17
- package/dist/helpers/endpoint-helpers.js.map +1 -1
- package/dist/helpers/excel-helper.d.ts +29 -0
- package/dist/helpers/excel-helper.d.ts.map +1 -0
- package/dist/helpers/excel-helper.js +150 -0
- package/dist/helpers/excel-helper.js.map +1 -0
- package/dist/helpers/general.d.ts +15 -0
- package/dist/helpers/general.d.ts.map +1 -0
- package/dist/helpers/general.js +148 -0
- package/dist/helpers/general.js.map +1 -0
- package/dist/helpers/index.d.ts +2 -0
- package/dist/helpers/index.d.ts.map +1 -1
- package/dist/helpers/index.js +2 -0
- package/dist/helpers/index.js.map +1 -1
- package/dist/helpers/logic.d.ts +100 -41
- package/dist/helpers/logic.d.ts.map +1 -1
- package/dist/helpers/logic.js +523 -413
- package/dist/helpers/logic.js.map +1 -1
- package/dist/helpers/package-upload.d.ts.map +1 -1
- package/dist/helpers/package-upload.js +2 -3
- package/dist/helpers/package-upload.js.map +1 -1
- package/dist/helpers/package.d.ts +3 -4
- package/dist/helpers/package.d.ts.map +1 -1
- package/dist/helpers/package.js +12 -17
- package/dist/helpers/package.js.map +1 -1
- package/dist/helpers/request-headers.d.ts +4 -0
- package/dist/helpers/request-headers.d.ts.map +1 -1
- package/dist/helpers/request-headers.js +2 -2
- package/dist/helpers/request-headers.js.map +1 -1
- package/dist/helpers/resource-cache.d.ts +35 -0
- package/dist/helpers/resource-cache.d.ts.map +1 -0
- package/dist/helpers/resource-cache.js +171 -0
- package/dist/helpers/resource-cache.js.map +1 -0
- package/dist/helpers/storage.d.ts +2 -2
- package/dist/helpers/storage.d.ts.map +1 -1
- package/dist/helpers/storage.js +16 -15
- package/dist/helpers/storage.js.map +1 -1
- package/dist/helpers/utils.d.ts +4 -3
- package/dist/helpers/utils.d.ts.map +1 -1
- package/dist/helpers/utils.js +73 -51
- package/dist/helpers/utils.js.map +1 -1
- package/package.json +12 -9
package/dist/helpers/logic.js
CHANGED
|
@@ -3,8 +3,10 @@ import fs from "fs/promises";
|
|
|
3
3
|
import mime from "mime";
|
|
4
4
|
import { createCode, removeDoubleSlashes } from "./utils";
|
|
5
5
|
import { DATABASE } from "./database";
|
|
6
|
+
import { v4 as uuidv4 } from "uuid";
|
|
6
7
|
import { deleteFilesRecursive } from "./storage";
|
|
7
8
|
import NodeCache from "node-cache";
|
|
9
|
+
import Busboy from "busboy";
|
|
8
10
|
export async function uploadFolderToStorage(folder, storageDestinationPath, uploadFiles, subfolder) {
|
|
9
11
|
const storage = getStorage();
|
|
10
12
|
const bucket = storage.bucket();
|
|
@@ -33,37 +35,71 @@ export async function uploadFolderToStorage(folder, storageDestinationPath, uplo
|
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
|
-
const applicationInfoCache = new NodeCache({
|
|
37
|
-
stdTTL: 60 * 5, // 5 minutes
|
|
38
|
-
});
|
|
39
38
|
const userIdCode = new NodeCache({
|
|
40
39
|
stdTTL: 60 * 20, // 20 minutes
|
|
41
40
|
});
|
|
42
|
-
export const
|
|
41
|
+
export const getTestsetSessions = async (testsetCode, db) => {
|
|
42
|
+
if (!testsetCode || !db)
|
|
43
|
+
return null;
|
|
44
|
+
const firestore = getFirestore();
|
|
45
|
+
const snapshot = await firestore
|
|
46
|
+
.doc(db.TESTSET_SESSIONS.DOC(testsetCode))
|
|
47
|
+
.get();
|
|
48
|
+
return snapshot.exists ? snapshot.data() : null;
|
|
49
|
+
};
|
|
50
|
+
export const getTestsetSessionWithSessions = async (testsetCode, db, userId) => {
|
|
51
|
+
if (!testsetCode || !db)
|
|
52
|
+
return null;
|
|
53
|
+
const firestore = getFirestore();
|
|
54
|
+
// Get the testset session from the testsetsessions collection
|
|
55
|
+
const testsetSessionDoc = await firestore
|
|
56
|
+
.doc(db.TESTSET_SESSIONS.DOC(testsetCode))
|
|
57
|
+
.get();
|
|
58
|
+
if (!testsetSessionDoc.exists) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const testsetSession = testsetSessionDoc.data();
|
|
62
|
+
if (!testsetSession ||
|
|
63
|
+
!testsetSession.sessionIds ||
|
|
64
|
+
testsetSession.sessionIds.length === 0) {
|
|
65
|
+
return { ...testsetSession, sessions: [], userId };
|
|
66
|
+
}
|
|
67
|
+
// Retrieve all test sessions based on the sessionIds field
|
|
68
|
+
const sessionPromises = testsetSession.sessionIds.map((sessionId) => firestore.doc(db.SESSION.DOC(sessionId)).get());
|
|
69
|
+
const sessionDocs = await Promise.all(sessionPromises);
|
|
70
|
+
const sessions = sessionDocs
|
|
71
|
+
.filter((doc) => doc.exists)
|
|
72
|
+
.map((doc) => doc.data());
|
|
73
|
+
// Return the testset session with the populated sessions
|
|
74
|
+
return {
|
|
75
|
+
...testsetSession,
|
|
76
|
+
sessions,
|
|
77
|
+
userId,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
export const getSession = async (code, db, userId) => {
|
|
43
81
|
if (!code || !db)
|
|
44
82
|
return null;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
83
|
+
const assessments = await getAssessments(db);
|
|
84
|
+
const matchAssessmentDemoId = assessments.find((a) => a.demoCode === code);
|
|
85
|
+
if (matchAssessmentDemoId) {
|
|
48
86
|
return {
|
|
49
|
-
appId: db.appId,
|
|
50
87
|
code,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
state: "not_started",
|
|
88
|
+
deliveryId: "<DEMO_DELIVERY_ID>",
|
|
89
|
+
assessmentId: matchAssessmentDemoId.id,
|
|
90
|
+
id: uuidv4(),
|
|
91
|
+
createdAt: new Date().getTime(),
|
|
92
|
+
updatedAt: new Date().getTime(),
|
|
93
|
+
createdBy: userId || null,
|
|
94
|
+
assessmentName: matchAssessmentDemoId.name,
|
|
95
|
+
packageId: matchAssessmentDemoId.packageId,
|
|
96
|
+
sessionState: "not_started",
|
|
61
97
|
isDemo: true,
|
|
62
98
|
teacherId: "demo-teacher",
|
|
63
99
|
};
|
|
64
100
|
}
|
|
65
101
|
const firestore = getFirestore();
|
|
66
|
-
const doc = await firestore.doc(db.
|
|
102
|
+
const doc = await firestore.doc(db.SESSION.DOC(code)).get();
|
|
67
103
|
if (userId && doc.exists) {
|
|
68
104
|
const firestore = getFirestore();
|
|
69
105
|
// store the mapping between the user and the code so on refresh we can get the code by the user
|
|
@@ -71,116 +107,26 @@ export const getStudentSessionInfo = async (code, db, userId) => {
|
|
|
71
107
|
}
|
|
72
108
|
return doc.exists ? doc.data() : null;
|
|
73
109
|
};
|
|
74
|
-
export const
|
|
75
|
-
if (!
|
|
110
|
+
export const getDelivery = async (code, db) => {
|
|
111
|
+
if (!code)
|
|
76
112
|
return null;
|
|
77
113
|
const firestore = getFirestore();
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.doc(db.GROUP_DELIVERY.DOC(groupCode))
|
|
81
|
-
.get();
|
|
82
|
-
if (!groupDeliveryDoc.exists) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
const assessment = groupDeliveryDoc.data();
|
|
86
|
-
const fullAssessment = await getAssessmentTest(db, assessment.assessmentId);
|
|
87
|
-
// Create the mapping between user and code
|
|
88
|
-
await firestore.doc(db.STUDENT_USER_ID_MAPPING.DOC(userId)).set({
|
|
89
|
-
code: groupCode,
|
|
90
|
-
});
|
|
91
|
-
// Create/update student info entry
|
|
92
|
-
const studentDocRef = firestore.doc(db.STUDENT.DOC(userId)); // Assuming nested structure
|
|
93
|
-
const studentInfo = {
|
|
94
|
-
appId: db.appId || "unknown",
|
|
95
|
-
code: groupCode,
|
|
96
|
-
currentAssessmentId: "",
|
|
97
|
-
identification: studentIdentification || "",
|
|
98
|
-
sessions: [],
|
|
99
|
-
isDemo: false,
|
|
100
|
-
teacherId: fullAssessment.teacherId || "unknown_teacher",
|
|
101
|
-
userId: userId,
|
|
102
|
-
};
|
|
103
|
-
// Get assessment details
|
|
104
|
-
const assessments = await getStartableAssessments(db);
|
|
105
|
-
if (assessments && assessments.length > 0) {
|
|
106
|
-
if (!studentInfo.currentAssessmentId) {
|
|
107
|
-
studentInfo.currentAssessmentId = assessment.assessmentId;
|
|
108
|
-
}
|
|
109
|
-
studentInfo.sessions = [
|
|
110
|
-
{
|
|
111
|
-
assessmentId: assessment.assessmentId,
|
|
112
|
-
assessmentName: assessment.name,
|
|
113
|
-
packageId: assessment.packageId,
|
|
114
|
-
sessionState: "not_started",
|
|
115
|
-
},
|
|
116
|
-
];
|
|
117
|
-
}
|
|
118
|
-
await studentDocRef.set(studentInfo);
|
|
119
|
-
return studentInfo;
|
|
114
|
+
const doc = await firestore.doc(db.DELIVERY.DOC(code)).get();
|
|
115
|
+
return doc.exists ? doc.data() : null;
|
|
120
116
|
};
|
|
121
|
-
export const
|
|
117
|
+
export const updateSessionState = async (db, code, state, batch) => {
|
|
122
118
|
if (!code || !db)
|
|
123
119
|
return null;
|
|
124
120
|
const firestore = getFirestore();
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
if (
|
|
128
|
-
const
|
|
129
|
-
...
|
|
130
|
-
|
|
121
|
+
const sessionDocRef = firestore.doc(db.SESSION.DOC(code));
|
|
122
|
+
const session = (await sessionDocRef.get())?.data();
|
|
123
|
+
if (session) {
|
|
124
|
+
const updatedSession = {
|
|
125
|
+
...session,
|
|
126
|
+
sessionState: state,
|
|
131
127
|
};
|
|
132
|
-
batch.set(
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
export const createAssessmentCode = async (db, charCount, assessmentId, application) => {
|
|
136
|
-
let code = createCode(charCount);
|
|
137
|
-
let exists = !!(await codeExists(code, db));
|
|
138
|
-
while (exists) {
|
|
139
|
-
code = createCode(charCount);
|
|
140
|
-
exists =
|
|
141
|
-
(application?.demoCodes || []).includes(code) ||
|
|
142
|
-
!!(await codeExists(code, db));
|
|
128
|
+
batch.set(sessionDocRef, updatedSession);
|
|
143
129
|
}
|
|
144
|
-
const firestore = getFirestore();
|
|
145
|
-
const assessmentData = await firestore
|
|
146
|
-
.doc(db.ASSESSMENT.DOC(assessmentId))
|
|
147
|
-
.get();
|
|
148
|
-
if (assessmentData.exists) {
|
|
149
|
-
const assessmentInfo = assessmentData.data();
|
|
150
|
-
await firestore.doc(db.GROUP_DELIVERY.DOC(code)).set(assessmentInfo);
|
|
151
|
-
return code;
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
export const createDemoCodeForAssessment = async (db, assessmentId, code) => {
|
|
158
|
-
const exists = !!(await codeExists(code, db));
|
|
159
|
-
if (exists) {
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
const firestore = getFirestore();
|
|
163
|
-
const assessmentData = await firestore
|
|
164
|
-
.doc(db.ASSESSMENT.DOC(assessmentId))
|
|
165
|
-
.get();
|
|
166
|
-
const assessmentInfo = assessmentData.data();
|
|
167
|
-
await firestore
|
|
168
|
-
.doc(db.GROUP_DELIVERY.DOC(code))
|
|
169
|
-
.set({ ...assessmentInfo, isDemo: true });
|
|
170
|
-
return code;
|
|
171
|
-
};
|
|
172
|
-
export const checkAssessmentCode = async (db, code) => {
|
|
173
|
-
const firestore = getFirestore();
|
|
174
|
-
if (!code || !db)
|
|
175
|
-
return null;
|
|
176
|
-
const application = await getApplication(db);
|
|
177
|
-
if ((application?.demoCodes || []).includes(code)) {
|
|
178
|
-
return {};
|
|
179
|
-
}
|
|
180
|
-
const assessmentInfo = await firestore.doc(db.GROUP_DELIVERY.DOC(code));
|
|
181
|
-
const doc = await assessmentInfo.get();
|
|
182
|
-
const assessment = doc.data();
|
|
183
|
-
return assessment;
|
|
184
130
|
};
|
|
185
131
|
export const isStartable = (asessment) => {
|
|
186
132
|
if (asessment.startFrom) {
|
|
@@ -197,184 +143,307 @@ export const isStartable = (asessment) => {
|
|
|
197
143
|
}
|
|
198
144
|
return true;
|
|
199
145
|
};
|
|
200
|
-
export const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return
|
|
146
|
+
export const checkDeliveryCode = async (db, deliveryCode) => {
|
|
147
|
+
try {
|
|
148
|
+
const firestore = getFirestore();
|
|
149
|
+
const doc = await firestore.doc(db.DELIVERY.DOC(deliveryCode)).get();
|
|
150
|
+
return doc.exists;
|
|
205
151
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (code) {
|
|
210
|
-
userIdCode.set(key, code);
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error("Error checking code:", error);
|
|
154
|
+
return false;
|
|
211
155
|
}
|
|
212
|
-
return code;
|
|
213
156
|
};
|
|
214
|
-
export const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
return
|
|
157
|
+
export const checkTestsetCode = async (db, testsetCode) => {
|
|
158
|
+
try {
|
|
159
|
+
const firestore = getFirestore();
|
|
160
|
+
const doc = await firestore.doc(db.TESTSET_SESSIONS.DOC(testsetCode)).get();
|
|
161
|
+
return doc.exists;
|
|
219
162
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
? doc.data().teacherId
|
|
224
|
-
: null;
|
|
225
|
-
if (teacherId) {
|
|
226
|
-
userIdCode.set(key, teacherId);
|
|
163
|
+
catch (error) {
|
|
164
|
+
console.error("Error checking code:", error);
|
|
165
|
+
return false;
|
|
227
166
|
}
|
|
228
|
-
return teacherId;
|
|
229
167
|
};
|
|
230
|
-
export const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
168
|
+
export const checkSessionCode = async (db, studentCode) => {
|
|
169
|
+
try {
|
|
170
|
+
const firestore = getFirestore();
|
|
171
|
+
const doc = await firestore.doc(db.SESSION.DOC(studentCode)).get();
|
|
172
|
+
return doc.exists;
|
|
234
173
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const applicationInfo = doc.data();
|
|
239
|
-
applicationInfoCache.set(db.appId || "application", applicationInfo);
|
|
240
|
-
return applicationInfo;
|
|
174
|
+
catch (error) {
|
|
175
|
+
console.error("Error checking code:", error);
|
|
176
|
+
return false;
|
|
241
177
|
}
|
|
242
178
|
};
|
|
243
|
-
export const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
179
|
+
export const deleteDelivery = async (db, deliveryId) => {
|
|
180
|
+
try {
|
|
181
|
+
const firestore = getFirestore();
|
|
182
|
+
const docRef = firestore.doc(db.DELIVERY.DOC(deliveryId));
|
|
183
|
+
await docRef.delete();
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
console.error("Error deleting delivery:", error);
|
|
187
|
+
}
|
|
247
188
|
};
|
|
248
|
-
export const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
189
|
+
export const createDelivery = async (db, userId, assessmentId) => {
|
|
190
|
+
try {
|
|
191
|
+
await getAssessment(db, assessmentId); // Ensure assessment exists
|
|
192
|
+
const firestore = getFirestore();
|
|
193
|
+
const docRef = firestore.collection(db.DELIVERY.COLLECTION()).doc();
|
|
194
|
+
const deliveryCode = await createUniqueCodeForDelivery(db);
|
|
195
|
+
const newDelivery = {
|
|
196
|
+
id: uuidv4(),
|
|
197
|
+
code: deliveryCode,
|
|
198
|
+
createdBy: userId,
|
|
199
|
+
state: "not_started",
|
|
200
|
+
assessmentId,
|
|
201
|
+
createdAt: new Date().getTime(),
|
|
202
|
+
updatedAt: new Date().getTime(),
|
|
256
203
|
};
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
application.assessments = assessments;
|
|
204
|
+
await docRef.set(newDelivery);
|
|
205
|
+
return docRef.id;
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
console.error("Error creating delivery:", error);
|
|
209
|
+
return null;
|
|
264
210
|
}
|
|
265
|
-
return application;
|
|
266
|
-
};
|
|
267
|
-
export const codeExists = async (code, db) => {
|
|
268
|
-
const studentSessions = await getStudentSessionInfo(code, db, undefined);
|
|
269
|
-
return studentSessions;
|
|
270
211
|
};
|
|
271
|
-
export const
|
|
212
|
+
export const createSessionForDelivery = async (db, userId, delivery) => {
|
|
272
213
|
const firestore = getFirestore();
|
|
273
|
-
const
|
|
274
|
-
const
|
|
275
|
-
|
|
214
|
+
const assessment = await getAssessment(db, delivery.assessmentId);
|
|
215
|
+
const uniqueCode = await createUniqueCodeForStudentSession(db);
|
|
216
|
+
if (!uniqueCode) {
|
|
217
|
+
console.error("Failed to create unique code for student session");
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const session = {
|
|
221
|
+
assessmentId: delivery.assessmentId,
|
|
222
|
+
deliveryId: delivery.id,
|
|
223
|
+
createdBy: userId,
|
|
224
|
+
createdAt: new Date().getTime(),
|
|
225
|
+
updatedAt: new Date().getTime(),
|
|
226
|
+
assessmentName: assessment.name,
|
|
227
|
+
code: uniqueCode,
|
|
228
|
+
id: uniqueCode,
|
|
229
|
+
packageId: assessment.packageId,
|
|
230
|
+
sessionState: "not_started",
|
|
231
|
+
assessmentHref: assessment.assessmentHref,
|
|
232
|
+
};
|
|
233
|
+
await firestore.doc(db.SESSION.DOC(uniqueCode)).set(session);
|
|
234
|
+
return session;
|
|
276
235
|
};
|
|
277
|
-
|
|
278
|
-
|
|
236
|
+
export const testsetSessionCodeExists = async (code, db) => {
|
|
237
|
+
return checkTestsetCode(db, code);
|
|
238
|
+
};
|
|
239
|
+
export const sessionCodeExists = async (code, db) => {
|
|
240
|
+
return checkSessionCode(db, code);
|
|
241
|
+
};
|
|
242
|
+
export const deliveryCodeExists = async (code, db) => {
|
|
243
|
+
return checkDeliveryCode(db, code);
|
|
244
|
+
};
|
|
245
|
+
export const createTestsetSessionByIdentifications = async (teacherId, db, testsetCode, identifications,
|
|
246
|
+
// studentTestSessionCodes: { code: string; password?: string }[],
|
|
247
|
+
testsessionState = "not_started", sessionState = "not_started", createPassword = false) => {
|
|
279
248
|
const firestore = getFirestore();
|
|
280
|
-
const application = await getApplication(db);
|
|
281
|
-
const codes = new Array(count).fill(createCode(5));
|
|
282
|
-
const studentAppSessionInfos = [];
|
|
283
249
|
const batch = firestore.batch();
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
250
|
+
const allCreatedSessions = [];
|
|
251
|
+
const allCreatedTestsetSessions = [];
|
|
252
|
+
const assessments = [];
|
|
253
|
+
const testset = await getTestset(testsetCode, db);
|
|
254
|
+
for (const assessmentId of testset.assessmentIds) {
|
|
255
|
+
const assessment = await getAssessment(db, assessmentId);
|
|
256
|
+
if (assessment) {
|
|
257
|
+
assessments.push(assessment);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
for (const identificationCode of identifications) {
|
|
261
|
+
// for (const studentCode of studentTestSessionCodes) {
|
|
262
|
+
const exists = await getTestsetSessions(identificationCode, db);
|
|
263
|
+
if (!exists) {
|
|
264
|
+
const password = createPassword ? createCode(4, "alphabetic") : undefined;
|
|
265
|
+
const testsetSession = {
|
|
266
|
+
id: identificationCode,
|
|
267
|
+
teacherId,
|
|
268
|
+
code: identificationCode,
|
|
269
|
+
createdAt: new Date().getTime(),
|
|
270
|
+
createdBy: teacherId,
|
|
271
|
+
updatedAt: new Date().getTime(),
|
|
272
|
+
state: testsessionState,
|
|
273
|
+
sessionIds: [],
|
|
274
|
+
password,
|
|
275
|
+
testsetId: testsetCode,
|
|
276
|
+
identification: "",
|
|
277
|
+
};
|
|
278
|
+
for (const assessmentId of testset.assessmentIds) {
|
|
279
|
+
const uniqueCode = await createUniqueCodeForStudentSession(db);
|
|
280
|
+
if (!uniqueCode) {
|
|
281
|
+
console.error("Failed to create unique code for student session");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const assessment = assessments.find((a) => a.id === assessmentId);
|
|
285
|
+
const session = {
|
|
286
|
+
id: uniqueCode,
|
|
287
|
+
assessmentId,
|
|
288
|
+
deliveryId: teacherId,
|
|
289
|
+
createdBy: teacherId,
|
|
290
|
+
createdAt: new Date().getTime(),
|
|
291
|
+
updatedAt: new Date().getTime(),
|
|
292
|
+
assessmentName: assessment?.name || "",
|
|
293
|
+
packageId: assessment?.packageId || "",
|
|
294
|
+
teacherId,
|
|
295
|
+
assessmentHref: assessment?.assessmentHref,
|
|
296
|
+
testsetSessionId: identificationCode,
|
|
297
|
+
sessionState,
|
|
298
|
+
code: uniqueCode,
|
|
299
|
+
};
|
|
300
|
+
if (!testsetSession.sessionIds) {
|
|
301
|
+
testsetSession.sessionIds = [];
|
|
302
|
+
}
|
|
303
|
+
testsetSession.sessionIds.push(session.id);
|
|
304
|
+
batch.set(firestore.doc(db.SESSION.DOC(session.id)), session);
|
|
305
|
+
// now add them to the teacher view model
|
|
306
|
+
const teacherDocPath = db.TEACHER.SESSIONS.DOC(teacherId, session.code);
|
|
307
|
+
batch.set(firestore.doc(teacherDocPath), session);
|
|
308
|
+
allCreatedSessions.push(session);
|
|
309
|
+
}
|
|
310
|
+
batch.set(firestore.doc(db.TESTSET_SESSIONS.DOC(testsetSession.id)), testsetSession);
|
|
311
|
+
allCreatedTestsetSessions.push(testsetSession);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
await batch.commit();
|
|
315
|
+
return {
|
|
316
|
+
session: allCreatedSessions,
|
|
317
|
+
testsetSessions: allCreatedTestsetSessions,
|
|
318
|
+
};
|
|
319
|
+
};
|
|
320
|
+
export const createUniqueCodeForDelivery = async (db) => {
|
|
321
|
+
// Create a unique delivery code if must be unique among students and deliveries
|
|
322
|
+
// each student session gets a unique code to be able to restart.
|
|
323
|
+
try {
|
|
324
|
+
let code = createCode(4);
|
|
325
|
+
let exists = await deliveryCodeExists(code, db);
|
|
326
|
+
while (exists) {
|
|
327
|
+
code = createCode(4);
|
|
328
|
+
exists = await deliveryCodeExists(code, db);
|
|
329
|
+
}
|
|
330
|
+
return code;
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
console.error("Error creating activity:", error);
|
|
334
|
+
throw error;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
export const createUniqueCodeForStudentSession = async (db) => {
|
|
338
|
+
// Create a unique delivery code if must be unique among students and deliveries
|
|
339
|
+
// each student session gets a unique code to be able to restart.
|
|
340
|
+
try {
|
|
341
|
+
let code = createCode(5);
|
|
342
|
+
let exists = await sessionCodeExists(code, db);
|
|
288
343
|
while (exists) {
|
|
289
344
|
code = createCode(5);
|
|
290
|
-
exists =
|
|
291
|
-
(await codeExists(code, db)));
|
|
345
|
+
exists = await sessionCodeExists(code, db);
|
|
292
346
|
}
|
|
293
|
-
|
|
294
|
-
teacherId,
|
|
295
|
-
code,
|
|
296
|
-
appId: db.appId || "application",
|
|
297
|
-
sessions: assessments.map((assessment) => ({
|
|
298
|
-
assessmentId: assessment.assessmentId,
|
|
299
|
-
assessmentName: assessment.name,
|
|
300
|
-
packageId: assessment.packageId,
|
|
301
|
-
sessionState: assessment.assessmentId.startsWith("practice")
|
|
302
|
-
? "not_generated"
|
|
303
|
-
: "not_available",
|
|
304
|
-
})),
|
|
305
|
-
isDemo: false,
|
|
306
|
-
};
|
|
307
|
-
studentAppSessionInfos.push(studentSessionInfo);
|
|
308
|
-
const teacherResult = {
|
|
309
|
-
code,
|
|
310
|
-
sessions: studentSessionInfo.sessions.map((session) => ({
|
|
311
|
-
code,
|
|
312
|
-
assessmentId: session.assessmentId,
|
|
313
|
-
sessionState: session.sessionState,
|
|
314
|
-
testScore: 0,
|
|
315
|
-
})),
|
|
316
|
-
};
|
|
317
|
-
batch.set(firestore.doc(db.STUDENT.DOC(code)), studentSessionInfo);
|
|
318
|
-
// now add them to the teacher view model
|
|
319
|
-
const teacherDocPath = db.TEACHER.STUDENTS.DOC(teacherId, teacherResult.code);
|
|
320
|
-
batch.set(firestore.doc(teacherDocPath), teacherResult);
|
|
347
|
+
return code;
|
|
321
348
|
}
|
|
322
|
-
|
|
323
|
-
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error("Error creating activity:", error);
|
|
351
|
+
throw error;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
export const createUniqueCodeForStudentTestsetSession = async (db) => {
|
|
355
|
+
// Create a unique delivery code if must be unique among students and deliveries
|
|
356
|
+
// each student session gets a unique code to be able to restart.
|
|
357
|
+
try {
|
|
358
|
+
let code = createCode(5);
|
|
359
|
+
let exists = await testsetSessionCodeExists(code, db);
|
|
360
|
+
while (exists) {
|
|
361
|
+
code = createCode(5);
|
|
362
|
+
exists = await testsetSessionCodeExists(code, db);
|
|
363
|
+
}
|
|
364
|
+
return code;
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
console.error("Error creating activity:", error);
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
export const hasAccess = async (uid, appId) => {
|
|
372
|
+
// Always return true since we removed applicationId-based access control
|
|
373
|
+
return true;
|
|
324
374
|
};
|
|
325
|
-
export const
|
|
375
|
+
export const createSessions = async (teacherId, deliveryCode, db, count, deliveryIds) => {
|
|
326
376
|
const firestore = getFirestore();
|
|
327
|
-
const studentAppSessionInfos = [];
|
|
328
377
|
const batch = firestore.batch();
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
378
|
+
const sessionList = [];
|
|
379
|
+
const assessments = [];
|
|
380
|
+
for (const deliveryId of deliveryIds) {
|
|
381
|
+
const delivery = await getDelivery(deliveryCode, db);
|
|
382
|
+
if (!delivery) {
|
|
383
|
+
console.error(`Delivery with ID ${deliveryId} not found`);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
let assessment = assessments.find((a) => a.id === delivery.assessmentId);
|
|
387
|
+
if (!assessment) {
|
|
388
|
+
assessment = await getAssessment(db, delivery.assessmentId);
|
|
389
|
+
if (assessment) {
|
|
390
|
+
assessments.push(assessment);
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
console.error(`Assessment with ID ${delivery.assessmentId} not found`);
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
const codes = new Array(count).fill(createCode(5));
|
|
398
|
+
for (let code of codes) {
|
|
399
|
+
// check if code does not exists twice in codes array
|
|
400
|
+
const isGeneratedTwice = codes.filter((c) => c === code).length > 1;
|
|
401
|
+
let exists = (await deliveryCodeExists(code, db)) || isGeneratedTwice;
|
|
402
|
+
while (exists) {
|
|
403
|
+
code = createCode(5);
|
|
404
|
+
exists = !!(await deliveryCodeExists(code, db));
|
|
405
|
+
// TODO, we must check the assessment demo code as well.
|
|
406
|
+
// but for now these codes can be generated with at least one char that are not used in createCode,
|
|
407
|
+
// like AEOIU1
|
|
408
|
+
}
|
|
409
|
+
const id = uuidv4();
|
|
410
|
+
const plannedSessions = {
|
|
411
|
+
id,
|
|
412
|
+
code,
|
|
413
|
+
deliveryId,
|
|
333
414
|
teacherId,
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
415
|
+
createdAt: new Date().getTime(),
|
|
416
|
+
updatedAt: new Date().getTime(),
|
|
417
|
+
createdBy: teacherId || "",
|
|
418
|
+
assessmentId: assessment.id,
|
|
419
|
+
assessmentName: assessment.name,
|
|
420
|
+
packageId: assessment.packageId,
|
|
421
|
+
sessionState: assessment.id.startsWith("practice")
|
|
422
|
+
? "not_generated"
|
|
423
|
+
: "not_available",
|
|
342
424
|
isDemo: false,
|
|
343
425
|
};
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
code: identification.identifier,
|
|
350
|
-
sessions: studentSessionInfo.sessions.map((session) => ({
|
|
351
|
-
code: identification.identifier,
|
|
352
|
-
assessmentId: session.assessmentId,
|
|
353
|
-
sessionState: session.sessionState,
|
|
354
|
-
testScore: 0,
|
|
355
|
-
})),
|
|
356
|
-
};
|
|
357
|
-
batch.set(firestore.doc(db.STUDENT.DOC(identification.identifier)), studentSessionInfo);
|
|
358
|
-
// now add them to the teacher view model
|
|
359
|
-
const teacherDocPath = db.TEACHER.STUDENTS.DOC(teacherId, teacherResult.code);
|
|
360
|
-
batch.set(firestore.doc(teacherDocPath), teacherResult);
|
|
426
|
+
sessionList.push(plannedSessions);
|
|
427
|
+
}
|
|
428
|
+
for (const session of sessionList) {
|
|
429
|
+
batch.set(firestore.doc(db.SESSION.DOC(session.code)), session);
|
|
430
|
+
batch.set(firestore.doc(db.TEACHER.SESSIONS.DOC(teacherId, session.code)), session);
|
|
361
431
|
}
|
|
362
432
|
}
|
|
363
433
|
await batch.commit();
|
|
364
|
-
return
|
|
434
|
+
return sessionList;
|
|
365
435
|
};
|
|
366
436
|
export const getAllStudents = async (db) => {
|
|
367
437
|
const firestore = getFirestore();
|
|
368
438
|
const docs = await firestore
|
|
369
|
-
.collection(db.
|
|
439
|
+
.collection(db.SESSION.COLLECTION())
|
|
370
440
|
.listDocuments();
|
|
371
|
-
const
|
|
441
|
+
const studentSessions = [];
|
|
372
442
|
for (const doc of docs) {
|
|
373
443
|
const studentInfo = await doc.get();
|
|
374
|
-
|
|
375
|
-
{ code: doc.id });
|
|
444
|
+
studentSessions.push(studentInfo.data() || { code: doc.id });
|
|
376
445
|
}
|
|
377
|
-
return
|
|
446
|
+
return studentSessions;
|
|
378
447
|
};
|
|
379
448
|
export const getAssessments = async (db) => {
|
|
380
449
|
const firestore = getFirestore();
|
|
@@ -384,29 +453,7 @@ export const getAssessments = async (db) => {
|
|
|
384
453
|
const assessments = assessmentData.docs.map((doc) => doc.data());
|
|
385
454
|
return assessments;
|
|
386
455
|
};
|
|
387
|
-
export const
|
|
388
|
-
const application = await getApplication(db);
|
|
389
|
-
if (!application) {
|
|
390
|
-
return [];
|
|
391
|
-
}
|
|
392
|
-
const startableAssessments = application.assessments.filter((a) => {
|
|
393
|
-
if (a.startFrom) {
|
|
394
|
-
const startFrom = new Date(a.startFrom);
|
|
395
|
-
if (startFrom > new Date()) {
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
if (a.endAt) {
|
|
400
|
-
const startUntil = new Date(a.endAt);
|
|
401
|
-
if (startUntil < new Date()) {
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
return a.canStart !== false;
|
|
406
|
-
});
|
|
407
|
-
return startableAssessments;
|
|
408
|
-
};
|
|
409
|
-
export const getAssessmentTest = async (db, assessmentId) => {
|
|
456
|
+
export const getAssessment = async (db, assessmentId) => {
|
|
410
457
|
const firestore = getFirestore();
|
|
411
458
|
const assessmentDoc = await firestore
|
|
412
459
|
.doc(db.ASSESSMENT.DOC(assessmentId))
|
|
@@ -420,6 +467,14 @@ export const getAssessmentTest = async (db, assessmentId) => {
|
|
|
420
467
|
}
|
|
421
468
|
return assessmentData;
|
|
422
469
|
};
|
|
470
|
+
export const getTestset = async (testsetId, db) => {
|
|
471
|
+
const firestore = getFirestore();
|
|
472
|
+
const testsetDoc = await firestore.doc(db.TESTSET.DOC(testsetId)).get();
|
|
473
|
+
if (!testsetDoc.exists) {
|
|
474
|
+
throw new Error(`Testset with ID ${testsetId} does not exist.`);
|
|
475
|
+
}
|
|
476
|
+
return testsetDoc.data();
|
|
477
|
+
};
|
|
423
478
|
export const getAssessmentItemRefsFromPackage = async ($assessmentTest) => {
|
|
424
479
|
//let regularItemCount = 0;
|
|
425
480
|
let itemCount = 0;
|
|
@@ -441,7 +496,7 @@ export const getAssessmentItemRefsFromPackage = async ($assessmentTest) => {
|
|
|
441
496
|
};
|
|
442
497
|
export const getItemsStats = async (db, teacherId, assessmentId, target) => {
|
|
443
498
|
const firestore = getFirestore();
|
|
444
|
-
const assessmentDoc = await
|
|
499
|
+
const assessmentDoc = await getAssessment(db, assessmentId);
|
|
445
500
|
if (!assessmentDoc) {
|
|
446
501
|
throw new Error(`Assessment with ID ${assessmentId} does not exist.`);
|
|
447
502
|
}
|
|
@@ -467,6 +522,7 @@ export const getItemsStats = async (db, teacherId, assessmentId, target) => {
|
|
|
467
522
|
}
|
|
468
523
|
else {
|
|
469
524
|
const data = d.data();
|
|
525
|
+
const id = d.id;
|
|
470
526
|
if (data) {
|
|
471
527
|
let itemStats = stats.find((s) => s.itemId === itemIdentifier);
|
|
472
528
|
if (!itemStats) {
|
|
@@ -481,7 +537,7 @@ export const getItemsStats = async (db, teacherId, assessmentId, target) => {
|
|
|
481
537
|
itemStats.count += data.count;
|
|
482
538
|
itemStats.numberCorrect +=
|
|
483
539
|
!data.score || data.score?.toString() === "0" ? 0 : 1;
|
|
484
|
-
itemStats.responses.push(data);
|
|
540
|
+
itemStats.responses.push({ ...data, id });
|
|
485
541
|
}
|
|
486
542
|
}
|
|
487
543
|
}
|
|
@@ -499,44 +555,39 @@ export const updateItemStatResponseScore = async (db, teacherId, assessmentId, i
|
|
|
499
555
|
export const getPlannedStudents = async (db, teacherId) => {
|
|
500
556
|
const firestore = getFirestore();
|
|
501
557
|
const studentResultCollectionRef = await firestore
|
|
502
|
-
.collection(db.TEACHER.
|
|
558
|
+
.collection(db.TEACHER.SESSIONS.COLLECTION(teacherId))
|
|
503
559
|
.get();
|
|
504
560
|
// StudentResult.
|
|
505
561
|
const studentSessionInfo = studentResultCollectionRef.docs.map((doc) => doc.data());
|
|
506
|
-
const studentsToReturn = [];
|
|
507
562
|
let prevSessionState = "";
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
session.sessionState = "not_started";
|
|
521
|
-
}
|
|
522
|
-
prevSessionState = session.sessionState;
|
|
523
|
-
sessionsToReturn.push(session);
|
|
563
|
+
const sessionsToReturn = [];
|
|
564
|
+
for (const session of studentSessionInfo) {
|
|
565
|
+
if (session.sessionState === "not_generated" &&
|
|
566
|
+
session.assessmentId === "practice" &&
|
|
567
|
+
prevSessionState === "scored") {
|
|
568
|
+
session.sessionState = "not_available";
|
|
569
|
+
}
|
|
570
|
+
else if (session.assessmentId === "practice-2" &&
|
|
571
|
+
(session.sessionState === "not_generated" ||
|
|
572
|
+
session.sessionState === "not_available") &&
|
|
573
|
+
prevSessionState === "scored") {
|
|
574
|
+
session.sessionState = "not_started";
|
|
524
575
|
}
|
|
525
|
-
|
|
526
|
-
|
|
576
|
+
prevSessionState = session.sessionState;
|
|
577
|
+
sessionsToReturn.push(session);
|
|
527
578
|
}
|
|
528
|
-
return
|
|
579
|
+
return sessionsToReturn;
|
|
529
580
|
};
|
|
530
581
|
export const getPlannedStudent = async (db, teacherId, code) => {
|
|
531
582
|
const firestore = getFirestore();
|
|
532
583
|
const studentResultRef = await firestore
|
|
533
|
-
.doc(db.TEACHER.
|
|
584
|
+
.doc(db.TEACHER.SESSIONS.DOC(teacherId, code))
|
|
534
585
|
.get();
|
|
535
586
|
return studentResultRef.data();
|
|
536
587
|
};
|
|
537
588
|
export const deleteStudent = async (db, teacherId, code) => {
|
|
538
589
|
const firestore = getFirestore();
|
|
539
|
-
const studentRef = await firestore.doc(db.TEACHER.
|
|
590
|
+
const studentRef = await firestore.doc(db.TEACHER.SESSIONS.DOC(teacherId, code));
|
|
540
591
|
const assessments = await firestore
|
|
541
592
|
.collection(db.ASSESSMENT.COLLECTION())
|
|
542
593
|
.get();
|
|
@@ -557,61 +608,52 @@ export const setUser = async (uid, user) => {
|
|
|
557
608
|
const firestore = getFirestore();
|
|
558
609
|
await firestore.doc(new DATABASE().AUTH.DOC(uid)).set(user);
|
|
559
610
|
};
|
|
560
|
-
export const
|
|
611
|
+
export const reopenSession = async (db, teacherId, code) => {
|
|
561
612
|
const firestore = getFirestore();
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
613
|
+
const batch = firestore.batch();
|
|
614
|
+
batch.update(firestore.doc(db.TEACHER.SESSIONS.DOC(teacherId, code)), {
|
|
615
|
+
sessionState: "not_started",
|
|
616
|
+
});
|
|
617
|
+
batch.update(firestore.doc(db.SESSION.DOC(code)), {
|
|
618
|
+
sessionState: "not_started",
|
|
619
|
+
});
|
|
620
|
+
await batch.commit();
|
|
621
|
+
};
|
|
622
|
+
export const resetSession = async (db, teacherId, code) => {
|
|
623
|
+
const firestore = getFirestore();
|
|
624
|
+
const batch = firestore.batch();
|
|
625
|
+
batch.update(firestore.doc(db.TEACHER.SESSIONS.DOC(teacherId, code)), {
|
|
626
|
+
sessionState: "not_started",
|
|
627
|
+
});
|
|
628
|
+
batch.update(firestore.doc(db.SESSION.DOC(code)), {
|
|
629
|
+
sessionState: "not_started",
|
|
630
|
+
});
|
|
631
|
+
batch.delete(firestore.doc(db.SESSION.DOC(code)));
|
|
632
|
+
await batch.commit();
|
|
573
633
|
};
|
|
574
634
|
export const getStudentInfoForTeacher = async (db, teacherId, code) => {
|
|
575
635
|
const firestore = getFirestore();
|
|
576
636
|
const studentRef = await firestore
|
|
577
|
-
.doc(db.TEACHER.
|
|
637
|
+
.doc(db.TEACHER.SESSIONS.DOC(teacherId, code))
|
|
578
638
|
.get();
|
|
579
639
|
const student = studentRef.data();
|
|
580
640
|
return student;
|
|
581
641
|
};
|
|
582
|
-
export const
|
|
642
|
+
export const updateStudentIdentification = async (db, teacherId, code, identification) => {
|
|
583
643
|
const firestore = getFirestore();
|
|
584
|
-
const
|
|
585
|
-
|
|
586
|
-
.get();
|
|
587
|
-
const student = studentRef.data();
|
|
588
|
-
const updatedStudent = {
|
|
589
|
-
...student,
|
|
644
|
+
const batch = firestore.batch();
|
|
645
|
+
batch.update(firestore.doc(db.TEACHER.SESSIONS.DOC(teacherId, code)), {
|
|
590
646
|
identification,
|
|
591
|
-
};
|
|
592
|
-
await studentRef.ref.set(updatedStudent);
|
|
593
|
-
};
|
|
594
|
-
export const getAllApplications = async () => {
|
|
595
|
-
const firestore = getFirestore();
|
|
596
|
-
const applicationCollection = await firestore
|
|
597
|
-
.collection(new DATABASE().APPLICATION.COLLECTION)
|
|
598
|
-
.get();
|
|
599
|
-
const applications = applicationCollection.docs.map((doc) => {
|
|
600
|
-
const app = doc.data();
|
|
601
|
-
if (!app.name) {
|
|
602
|
-
app.name = doc.id;
|
|
603
|
-
}
|
|
604
|
-
return app;
|
|
605
647
|
});
|
|
606
|
-
|
|
648
|
+
batch.update(firestore.doc(db.SESSION.DOC(code)), { identification });
|
|
649
|
+
await batch.commit();
|
|
607
650
|
};
|
|
608
651
|
const createStrongPassword = () => {
|
|
609
652
|
const password = Math.random().toString(36).slice(-8);
|
|
610
653
|
return password;
|
|
611
654
|
};
|
|
612
|
-
export const
|
|
655
|
+
export const createUser = async (email) => {
|
|
613
656
|
const auth = getAuth();
|
|
614
|
-
const firestore = getFirestore();
|
|
615
657
|
let password = "";
|
|
616
658
|
let firebaseUser = undefined;
|
|
617
659
|
try {
|
|
@@ -627,13 +669,7 @@ export const createUserForApplication = async (email, applicationId) => {
|
|
|
627
669
|
password,
|
|
628
670
|
});
|
|
629
671
|
}
|
|
630
|
-
|
|
631
|
-
.doc(new DATABASE().AUTH.CHECK_APP_RIGHTS(firebaseUser.uid, applicationId))
|
|
632
|
-
.set({
|
|
633
|
-
email,
|
|
634
|
-
application: applicationId.toLocaleLowerCase(),
|
|
635
|
-
userId: firebaseUser.uid,
|
|
636
|
-
});
|
|
672
|
+
// No longer need to set application-specific permissions
|
|
637
673
|
if (password) {
|
|
638
674
|
return { password, id: firebaseUser.uid };
|
|
639
675
|
}
|
|
@@ -641,61 +677,25 @@ export const createUserForApplication = async (email, applicationId) => {
|
|
|
641
677
|
return { id: firebaseUser.uid };
|
|
642
678
|
}
|
|
643
679
|
};
|
|
644
|
-
export const
|
|
680
|
+
export const deleteUser = async (id) => {
|
|
645
681
|
const auth = getAuth();
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
// otherwise we remove the user from firebase Authentication
|
|
649
|
-
const applicationDocs = await firestore
|
|
650
|
-
.collection(new DATABASE().AUTH.APP_COLLECTION(id))
|
|
651
|
-
.listDocuments();
|
|
652
|
-
const applicationIds = applicationDocs.map((a) => a.id.toLocaleLowerCase());
|
|
653
|
-
if (!applicationIds.includes(applicationId.toLocaleLowerCase())) {
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
await firestore
|
|
657
|
-
.doc(new DATABASE().AUTH.CHECK_APP_RIGHTS(id, applicationId))
|
|
658
|
-
.delete();
|
|
659
|
-
if (applicationIds.length === 1) {
|
|
660
|
-
await auth.deleteUser(id);
|
|
661
|
-
}
|
|
682
|
+
// Simply delete the user from Firebase Auth
|
|
683
|
+
await auth.deleteUser(id);
|
|
662
684
|
return;
|
|
663
685
|
};
|
|
664
|
-
export const
|
|
665
|
-
const
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
.
|
|
669
|
-
.
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
for (const applicationId of applicationsForUser.map((a) => a.id.toLowerCase())) {
|
|
678
|
-
const userData = users.find((u) => u.id === userId);
|
|
679
|
-
const user = {
|
|
680
|
-
id: userId,
|
|
681
|
-
email: userData?.email || "",
|
|
682
|
-
disabled: userData?.disabled || false,
|
|
683
|
-
};
|
|
684
|
-
if (usersPerApplication.has(applicationId)) {
|
|
685
|
-
usersPerApplication.get(applicationId)?.push(user);
|
|
686
|
-
}
|
|
687
|
-
else {
|
|
688
|
-
usersPerApplication.set(applicationId, [user]);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
const applicationWithUsers = applications.map((a) => {
|
|
693
|
-
return {
|
|
694
|
-
...a,
|
|
695
|
-
users: usersPerApplication.get(a.name?.toLocaleLowerCase()) || [],
|
|
696
|
-
};
|
|
697
|
-
});
|
|
698
|
-
return applicationWithUsers;
|
|
686
|
+
export const getAllUsers = async () => {
|
|
687
|
+
const userDocs = await getAuth().listUsers();
|
|
688
|
+
const users = userDocs.users.map((user) => ({
|
|
689
|
+
id: user.uid,
|
|
690
|
+
email: user.email || "",
|
|
691
|
+
disabled: user.disabled || false,
|
|
692
|
+
}));
|
|
693
|
+
return [
|
|
694
|
+
{
|
|
695
|
+
name: "default",
|
|
696
|
+
users: users,
|
|
697
|
+
},
|
|
698
|
+
];
|
|
699
699
|
};
|
|
700
700
|
export async function deleteDocumentAndSubcollections(docRef) {
|
|
701
701
|
// Delete the document
|
|
@@ -719,7 +719,7 @@ export async function checkAndDeletePackages(dateTime) {
|
|
|
719
719
|
const packages = packageData.docs.map((doc) => doc.data());
|
|
720
720
|
for (const packageInfo of packages) {
|
|
721
721
|
if (packageInfo.uploaded < dateTime) {
|
|
722
|
-
for (const { assessmentId } of packageInfo.
|
|
722
|
+
for (const { assessmentId } of packageInfo.Assessment) {
|
|
723
723
|
await deleteDocumentAndSubcollections(firestore.doc(db.ASSESSMENT.DOC(assessmentId)));
|
|
724
724
|
}
|
|
725
725
|
await deleteFilesRecursive(storage.bucket(), `packages/${packageInfo.packageId}`);
|
|
@@ -749,11 +749,121 @@ export async function fetchUsersInChunks(ids, chunkSize = 100) {
|
|
|
749
749
|
}
|
|
750
750
|
return results;
|
|
751
751
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
752
|
+
/**
|
|
753
|
+
* Processes feedback submission with optional screenshot
|
|
754
|
+
* @param req - Express request object
|
|
755
|
+
* @param userId - User ID who is submitting feedback
|
|
756
|
+
* @param userType - Type of user ('teacher' or 'student')
|
|
757
|
+
* @returns Promise<{data: FeedbackSubmissionData, fileBuffer: Buffer | null, fileOriginalname: string | null}>
|
|
758
|
+
*/
|
|
759
|
+
export function processFeedbackSubmission(req) {
|
|
760
|
+
return new Promise((resolve, reject) => {
|
|
761
|
+
const busboy = Busboy({ headers: req.headers });
|
|
762
|
+
const fields = {};
|
|
763
|
+
let fileBuffer = null;
|
|
764
|
+
let fileOriginalname = null;
|
|
765
|
+
busboy.on("field", (fieldname, val) => {
|
|
766
|
+
fields[fieldname] = val;
|
|
767
|
+
});
|
|
768
|
+
busboy.on("file", (fieldname, file, filename) => {
|
|
769
|
+
if (fieldname === "screenshot") {
|
|
770
|
+
const chunks = [];
|
|
771
|
+
fileOriginalname = filename.filename || filename;
|
|
772
|
+
file.on("data", (data) => {
|
|
773
|
+
chunks.push(data);
|
|
774
|
+
});
|
|
775
|
+
file.on("end", () => {
|
|
776
|
+
if (chunks.length) {
|
|
777
|
+
fileBuffer = Buffer.concat(chunks);
|
|
778
|
+
}
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
// Discard other files
|
|
783
|
+
file.resume();
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
busboy.on("finish", () => {
|
|
787
|
+
const { type, description, feedbackId, email, pageUrl } = fields;
|
|
788
|
+
resolve({
|
|
789
|
+
data: { type, description, feedbackId, email, pageUrl },
|
|
790
|
+
fileBuffer,
|
|
791
|
+
fileOriginalname,
|
|
792
|
+
});
|
|
793
|
+
});
|
|
794
|
+
busboy.on("error", (error) => {
|
|
795
|
+
reject(error);
|
|
796
|
+
});
|
|
797
|
+
// Handle the request
|
|
798
|
+
if (req.rawBody) {
|
|
799
|
+
// For Firebase Functions
|
|
800
|
+
busboy.end(req.rawBody);
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
// For regular Express
|
|
804
|
+
req.pipe(busboy);
|
|
805
|
+
}
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Validates feedback submission data
|
|
810
|
+
* @param data - Feedback data to validate
|
|
811
|
+
* @returns Validation error message or null if valid
|
|
812
|
+
*/
|
|
813
|
+
export function validateFeedbackData(data) {
|
|
814
|
+
const { type, description, feedbackId } = data;
|
|
815
|
+
if (!type || !description || !feedbackId) {
|
|
816
|
+
return "Missing required fields: type, description, and feedbackId are required";
|
|
817
|
+
}
|
|
818
|
+
if (type.length < 1 || type.length > 50) {
|
|
819
|
+
return "Type must be between 1 and 50 characters";
|
|
820
|
+
}
|
|
821
|
+
if (description.length < 1 || description.length > 2000) {
|
|
822
|
+
return "Description must be between 1 and 2000 characters";
|
|
823
|
+
}
|
|
824
|
+
if (feedbackId.length < 1 || feedbackId.length > 100) {
|
|
825
|
+
return "Feedback ID must be between 1 and 100 characters";
|
|
826
|
+
}
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Saves feedback to Firestore and optionally uploads screenshot to Storage
|
|
831
|
+
* @param data - Feedback submission data
|
|
832
|
+
* @param userId - User ID who submitted the feedback
|
|
833
|
+
* @param userType - Type of user ('teacher' or 'student')
|
|
834
|
+
* @param fileBuffer - Screenshot file buffer (optional)
|
|
835
|
+
* @returns Promise<void>
|
|
836
|
+
*/
|
|
837
|
+
export async function saveFeedback(data, userId, userType, fileBuffer) {
|
|
838
|
+
const { type, description, feedbackId, email, pageUrl } = data;
|
|
839
|
+
// Create feedback document in Firestore
|
|
840
|
+
const firestore = getFirestore();
|
|
841
|
+
const db = new DATABASE();
|
|
842
|
+
const firebaseDocPath = db.FEEDBACK.DOC(`${feedbackId}_${userId}`);
|
|
843
|
+
const feedbackRef = firestore.doc(firebaseDocPath);
|
|
844
|
+
await feedbackRef.set({
|
|
845
|
+
type,
|
|
846
|
+
description,
|
|
847
|
+
userId,
|
|
848
|
+
userType,
|
|
849
|
+
email: email || null,
|
|
850
|
+
pageUrl: pageUrl || null,
|
|
851
|
+
hasScreenshot: !!fileBuffer,
|
|
852
|
+
createdAt: new Date().toISOString(),
|
|
853
|
+
});
|
|
854
|
+
// If there's a screenshot, upload it to Firebase Storage
|
|
855
|
+
if (fileBuffer) {
|
|
856
|
+
const storage = getStorage();
|
|
857
|
+
const bucket = storage.bucket();
|
|
858
|
+
// storage path = firebaseDocPath but without the first /
|
|
859
|
+
const storagePath = firebaseDocPath.replace(/^\//, "");
|
|
860
|
+
const file = bucket.file(`${storagePath}.png`);
|
|
861
|
+
// Upload the file to Firebase Storage
|
|
862
|
+
await file.save(fileBuffer, {
|
|
863
|
+
metadata: {
|
|
864
|
+
contentType: "image/png",
|
|
865
|
+
},
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
}
|
|
759
869
|
//# sourceMappingURL=logic.js.map
|