@citolab/qti-backend-firebase 0.0.4 → 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 +72 -87
- 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 +521 -412
- 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 +9 -6
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
|
}
|
|
@@ -500,44 +555,39 @@ export const updateItemStatResponseScore = async (db, teacherId, assessmentId, i
|
|
|
500
555
|
export const getPlannedStudents = async (db, teacherId) => {
|
|
501
556
|
const firestore = getFirestore();
|
|
502
557
|
const studentResultCollectionRef = await firestore
|
|
503
|
-
.collection(db.TEACHER.
|
|
558
|
+
.collection(db.TEACHER.SESSIONS.COLLECTION(teacherId))
|
|
504
559
|
.get();
|
|
505
560
|
// StudentResult.
|
|
506
561
|
const studentSessionInfo = studentResultCollectionRef.docs.map((doc) => doc.data());
|
|
507
|
-
const studentsToReturn = [];
|
|
508
562
|
let prevSessionState = "";
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
session.sessionState = "not_started";
|
|
522
|
-
}
|
|
523
|
-
prevSessionState = session.sessionState;
|
|
524
|
-
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";
|
|
525
575
|
}
|
|
526
|
-
|
|
527
|
-
|
|
576
|
+
prevSessionState = session.sessionState;
|
|
577
|
+
sessionsToReturn.push(session);
|
|
528
578
|
}
|
|
529
|
-
return
|
|
579
|
+
return sessionsToReturn;
|
|
530
580
|
};
|
|
531
581
|
export const getPlannedStudent = async (db, teacherId, code) => {
|
|
532
582
|
const firestore = getFirestore();
|
|
533
583
|
const studentResultRef = await firestore
|
|
534
|
-
.doc(db.TEACHER.
|
|
584
|
+
.doc(db.TEACHER.SESSIONS.DOC(teacherId, code))
|
|
535
585
|
.get();
|
|
536
586
|
return studentResultRef.data();
|
|
537
587
|
};
|
|
538
588
|
export const deleteStudent = async (db, teacherId, code) => {
|
|
539
589
|
const firestore = getFirestore();
|
|
540
|
-
const studentRef = await firestore.doc(db.TEACHER.
|
|
590
|
+
const studentRef = await firestore.doc(db.TEACHER.SESSIONS.DOC(teacherId, code));
|
|
541
591
|
const assessments = await firestore
|
|
542
592
|
.collection(db.ASSESSMENT.COLLECTION())
|
|
543
593
|
.get();
|
|
@@ -558,61 +608,52 @@ export const setUser = async (uid, user) => {
|
|
|
558
608
|
const firestore = getFirestore();
|
|
559
609
|
await firestore.doc(new DATABASE().AUTH.DOC(uid)).set(user);
|
|
560
610
|
};
|
|
561
|
-
export const
|
|
611
|
+
export const reopenSession = async (db, teacherId, code) => {
|
|
562
612
|
const firestore = getFirestore();
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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();
|
|
574
633
|
};
|
|
575
634
|
export const getStudentInfoForTeacher = async (db, teacherId, code) => {
|
|
576
635
|
const firestore = getFirestore();
|
|
577
636
|
const studentRef = await firestore
|
|
578
|
-
.doc(db.TEACHER.
|
|
637
|
+
.doc(db.TEACHER.SESSIONS.DOC(teacherId, code))
|
|
579
638
|
.get();
|
|
580
639
|
const student = studentRef.data();
|
|
581
640
|
return student;
|
|
582
641
|
};
|
|
583
|
-
export const
|
|
642
|
+
export const updateStudentIdentification = async (db, teacherId, code, identification) => {
|
|
584
643
|
const firestore = getFirestore();
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
.get();
|
|
588
|
-
const student = studentRef.data();
|
|
589
|
-
const updatedStudent = {
|
|
590
|
-
...student,
|
|
644
|
+
const batch = firestore.batch();
|
|
645
|
+
batch.update(firestore.doc(db.TEACHER.SESSIONS.DOC(teacherId, code)), {
|
|
591
646
|
identification,
|
|
592
|
-
};
|
|
593
|
-
await studentRef.ref.set(updatedStudent);
|
|
594
|
-
};
|
|
595
|
-
export const getAllApplications = async () => {
|
|
596
|
-
const firestore = getFirestore();
|
|
597
|
-
const applicationCollection = await firestore
|
|
598
|
-
.collection(new DATABASE().APPLICATION.COLLECTION)
|
|
599
|
-
.get();
|
|
600
|
-
const applications = applicationCollection.docs.map((doc) => {
|
|
601
|
-
const app = doc.data();
|
|
602
|
-
if (!app.name) {
|
|
603
|
-
app.name = doc.id;
|
|
604
|
-
}
|
|
605
|
-
return app;
|
|
606
647
|
});
|
|
607
|
-
|
|
648
|
+
batch.update(firestore.doc(db.SESSION.DOC(code)), { identification });
|
|
649
|
+
await batch.commit();
|
|
608
650
|
};
|
|
609
651
|
const createStrongPassword = () => {
|
|
610
652
|
const password = Math.random().toString(36).slice(-8);
|
|
611
653
|
return password;
|
|
612
654
|
};
|
|
613
|
-
export const
|
|
655
|
+
export const createUser = async (email) => {
|
|
614
656
|
const auth = getAuth();
|
|
615
|
-
const firestore = getFirestore();
|
|
616
657
|
let password = "";
|
|
617
658
|
let firebaseUser = undefined;
|
|
618
659
|
try {
|
|
@@ -628,13 +669,7 @@ export const createUserForApplication = async (email, applicationId) => {
|
|
|
628
669
|
password,
|
|
629
670
|
});
|
|
630
671
|
}
|
|
631
|
-
|
|
632
|
-
.doc(new DATABASE().AUTH.CHECK_APP_RIGHTS(firebaseUser.uid, applicationId))
|
|
633
|
-
.set({
|
|
634
|
-
email,
|
|
635
|
-
application: applicationId.toLocaleLowerCase(),
|
|
636
|
-
userId: firebaseUser.uid,
|
|
637
|
-
});
|
|
672
|
+
// No longer need to set application-specific permissions
|
|
638
673
|
if (password) {
|
|
639
674
|
return { password, id: firebaseUser.uid };
|
|
640
675
|
}
|
|
@@ -642,61 +677,25 @@ export const createUserForApplication = async (email, applicationId) => {
|
|
|
642
677
|
return { id: firebaseUser.uid };
|
|
643
678
|
}
|
|
644
679
|
};
|
|
645
|
-
export const
|
|
680
|
+
export const deleteUser = async (id) => {
|
|
646
681
|
const auth = getAuth();
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
// otherwise we remove the user from firebase Authentication
|
|
650
|
-
const applicationDocs = await firestore
|
|
651
|
-
.collection(new DATABASE().AUTH.APP_COLLECTION(id))
|
|
652
|
-
.listDocuments();
|
|
653
|
-
const applicationIds = applicationDocs.map((a) => a.id.toLocaleLowerCase());
|
|
654
|
-
if (!applicationIds.includes(applicationId.toLocaleLowerCase())) {
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
await firestore
|
|
658
|
-
.doc(new DATABASE().AUTH.CHECK_APP_RIGHTS(id, applicationId))
|
|
659
|
-
.delete();
|
|
660
|
-
if (applicationIds.length === 1) {
|
|
661
|
-
await auth.deleteUser(id);
|
|
662
|
-
}
|
|
682
|
+
// Simply delete the user from Firebase Auth
|
|
683
|
+
await auth.deleteUser(id);
|
|
663
684
|
return;
|
|
664
685
|
};
|
|
665
|
-
export const
|
|
666
|
-
const
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
.
|
|
670
|
-
.
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
for (const applicationId of applicationsForUser.map((a) => a.id.toLowerCase())) {
|
|
679
|
-
const userData = users.find((u) => u.id === userId);
|
|
680
|
-
const user = {
|
|
681
|
-
id: userId,
|
|
682
|
-
email: userData?.email || "",
|
|
683
|
-
disabled: userData?.disabled || false,
|
|
684
|
-
};
|
|
685
|
-
if (usersPerApplication.has(applicationId)) {
|
|
686
|
-
usersPerApplication.get(applicationId)?.push(user);
|
|
687
|
-
}
|
|
688
|
-
else {
|
|
689
|
-
usersPerApplication.set(applicationId, [user]);
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
const applicationWithUsers = applications.map((a) => {
|
|
694
|
-
return {
|
|
695
|
-
...a,
|
|
696
|
-
users: usersPerApplication.get(a.name?.toLocaleLowerCase()) || [],
|
|
697
|
-
};
|
|
698
|
-
});
|
|
699
|
-
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
|
+
];
|
|
700
699
|
};
|
|
701
700
|
export async function deleteDocumentAndSubcollections(docRef) {
|
|
702
701
|
// Delete the document
|
|
@@ -720,7 +719,7 @@ export async function checkAndDeletePackages(dateTime) {
|
|
|
720
719
|
const packages = packageData.docs.map((doc) => doc.data());
|
|
721
720
|
for (const packageInfo of packages) {
|
|
722
721
|
if (packageInfo.uploaded < dateTime) {
|
|
723
|
-
for (const { assessmentId } of packageInfo.
|
|
722
|
+
for (const { assessmentId } of packageInfo.Assessment) {
|
|
724
723
|
await deleteDocumentAndSubcollections(firestore.doc(db.ASSESSMENT.DOC(assessmentId)));
|
|
725
724
|
}
|
|
726
725
|
await deleteFilesRecursive(storage.bucket(), `packages/${packageInfo.packageId}`);
|
|
@@ -750,11 +749,121 @@ export async function fetchUsersInChunks(ids, chunkSize = 100) {
|
|
|
750
749
|
}
|
|
751
750
|
return results;
|
|
752
751
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
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
|
+
}
|
|
760
869
|
//# sourceMappingURL=logic.js.map
|