Skip to content

Commit 0319bab

Browse files
committed
feat: misc
1 parent e766ff8 commit 0319bab

39 files changed

+1147
-413
lines changed

extensions/firestore-send-email.env

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
DEFAULT_FROM=info@examtraining.online
1+
DEFAULT_FROM="examtraining.online <info@examtraining.online>"
22
MAIL_COLLECTION=mail
33
SMTP_CONNECTION_URI=smtps://postmaster%40mg.examtraining.online@smtp.eu.mailgun.org:465
44
SMTP_PASSWORD=projects/${param:PROJECT_NUMBER}/secrets/firestore-send-email-SMTP_PASSWORD/versions/latest

packages/core/src/schema.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type Mail = {
2020
};
2121

2222
export type Answer = {
23+
id: string;
2324
order: number;
2425
description: string;
2526
correct: boolean;
@@ -28,12 +29,9 @@ export type Answer = {
2829
export type Question = {
2930
order: number;
3031
description: string;
31-
answers: CollectionReference<Answer>;
32+
answers: Answer[];
3233
explanation: string;
33-
};
34-
35-
export type QuestionWithAnswers = Omit<Question, "answers"> & {
36-
answers: AddId<Answer>[];
34+
categories?: string[];
3735
};
3836

3937
export type Secret = {
@@ -54,7 +52,7 @@ export type Exam = {
5452

5553
export type ExamWithQuestions = Omit<Exam, "questions" | "secrets"> & {
5654
id: string;
57-
questions: AddId<QuestionWithAnswers>[];
55+
questions: AddId<Question>[];
5856
};
5957

6058
type DocEnum = {

packages/functions/src/createExam.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const EDIT_HASH_LENGTH = 8;
2121
const ACCESS_HASH_LENGTH = 2;
2222

2323
type CreateExamParams = { exam: Partial<Exam>; owner: string };
24-
type CreateExamReturn = {};
24+
type CreateExamReturn = { accessCode?: string; editCode: string };
2525

2626
export const createExam = onCall<CreateExamParams, Promise<CreateExamReturn>>(
2727
{ region: "europe-west1", cors: "*" },
@@ -66,11 +66,12 @@ export const createExam = onCall<CreateExamParams, Promise<CreateExamReturn>>(
6666
await exams.doc(slug).set(exam as Exam);
6767

6868
// Queue mail
69-
await mail.add({
70-
to: owner,
71-
message: {
72-
subject: "Exam created",
73-
html: `Your exam "${exam.title}" has been created.<br/>
69+
if (!process.env.FUNCTIONS_EMULATOR) {
70+
await mail.add({
71+
to: owner,
72+
message: {
73+
subject: "Exam created",
74+
html: `Your exam "${exam.title}" has been created.<br/>
7475
<br/>
7576
${
7677
exam.private
@@ -84,12 +85,16 @@ Access the exam by using this link: <a href="https://examtraining.online/${slug}
8485
In order to make changes to this exam, you need an edit code.<br/>
8586
The edit code is: <code>${editCode}</code><br/>
8687
Edit the exam by using this link: <a href="https://examtraining.online/${slug}/edit?editCode=${editCode}">https://examtraining.online/${slug}/edit?editCode=${editCode}</a>`,
87-
},
88-
});
88+
},
89+
});
90+
}
8991

9092
logger.info({ message: "created exam", data });
9193

92-
return {};
94+
return {
95+
accessCode,
96+
editCode,
97+
};
9398
} catch (error) {
9499
logger.error(error);
95100
throw error;

packages/functions/src/createExamQuestion.ts

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,13 @@ try {
77
import { logger } from "firebase-functions/v2";
88
import { HttpsError, onCall } from "firebase-functions/v2/https";
99
// @ts-ignore
10-
import {
11-
FirestoreCollection,
12-
Question,
13-
QuestionWithAnswers,
14-
} from "@examtraining/core";
15-
import { collectionRef, getDocument } from "./utils";
10+
import { FirestoreCollection, Question } from "@examtraining/core";
11+
import { collectionRef, getDocument, migrateAnswersIfNeeded } from "./utils";
1612

1713
type CreateExamQuestionParams = {
1814
slug: string;
1915
editCode: string;
20-
data: QuestionWithAnswers;
16+
data: Question;
2117
};
2218

2319
type CreateExamQuestionReturn = {};
@@ -56,30 +52,21 @@ export const createExamQuestion = onCall<
5652
);
5753
}
5854

55+
await migrateAnswersIfNeeded(exam);
56+
5957
// Add question
60-
const questionRef = await collectionRef(
58+
await collectionRef(
6159
FirestoreCollection.Exams,
6260
data.slug,
6361
FirestoreCollection.Questions,
6462
).add({
6563
order: data.data.order,
6664
description: data.data.description,
6765
explanation: data.data.explanation,
66+
answers: data.data.answers,
67+
categories: data.data.categories,
6868
} as Question);
6969

70-
// Add answers
71-
const answers = collectionRef(
72-
FirestoreCollection.Exams,
73-
data.slug,
74-
FirestoreCollection.Questions,
75-
questionRef.id,
76-
FirestoreCollection.Answers,
77-
);
78-
79-
for (const answer of data.data.answers) {
80-
await answers.add(answer);
81-
}
82-
8370
logger.info({ message: "created exam question", data });
8471

8572
return {};

packages/functions/src/editExamDetails.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ type EditExamDetailsParams = {
1616
data: Partial<Exam>;
1717
};
1818

19-
type EditExamDetailsReturn = {};
19+
type EditExamDetailsReturn = { accessCode?: string };
2020

2121
export const editExamDetails = onCall<
2222
EditExamDetailsParams,
@@ -58,19 +58,21 @@ export const editExamDetails = onCall<
5858
await docRef(FirestoreCollection.Exams, data.slug).update(data.data);
5959

6060
// Queue mail
61-
if (exam.private === false && data.data.private === true) {
62-
await mail.add({
63-
to: secrets.data()!.owner,
64-
message: {
65-
subject: "Exam made private",
66-
html: `Your exam "${exam.title}" is now private.<br/>
61+
if (!process.env.FUNCTIONS_EMULATOR) {
62+
if (exam.private === false && data.data.private === true) {
63+
await mail.add({
64+
to: secrets.data()!.owner,
65+
message: {
66+
subject: "Exam made private",
67+
html: `Your exam "${exam.title}" is now private.<br/>
6768
<br/>
6869
In order to view it you need an access code.<br/>
6970
The access code is: <code>${secrets.data()!.accessCode}</code><br/>
7071
Access the exam by using this link: <a href="https://examtraining.online/${data.slug}?accessCode=${secrets.data()!.accessCode}">https://examtraining.online/${data.slug}?accessCode=${secrets.data()!.accessCode}</a><br/>
7172
<br/>`,
72-
},
73-
});
73+
},
74+
});
75+
}
7476
}
7577

7678
logger.info({ message: "edited exam details", data });

packages/functions/src/editExamQuestion.ts

Lines changed: 7 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,14 @@ try {
77
import { logger } from "firebase-functions/v2";
88
import { HttpsError, onCall } from "firebase-functions/v2/https";
99
// @ts-ignore
10-
import {
11-
FirestoreCollection,
12-
Question,
13-
QuestionWithAnswers,
14-
} from "@examtraining/core";
15-
import { collectionRef, getDocument } from "./utils";
10+
import { FirestoreCollection, Question } from "@examtraining/core";
11+
import { collectionRef, getDocument, migrateAnswersIfNeeded } from "./utils";
1612

1713
type EditExamQuestionParams = {
1814
slug: string;
1915
editCode: string;
2016
questionId: string;
21-
data: QuestionWithAnswers;
17+
data: Question;
2218
};
2319

2420
type EditExamQuestionReturn = {};
@@ -57,6 +53,8 @@ export const editExamQuestion = onCall<
5753
);
5854
}
5955

56+
await migrateAnswersIfNeeded(exam);
57+
6058
// Update question
6159
const questionRef = collectionRef(
6260
FirestoreCollection.Exams,
@@ -66,25 +64,10 @@ export const editExamQuestion = onCall<
6664
await questionRef.update({
6765
description: data.data.description,
6866
explanation: data.data.explanation,
67+
answers: data.data.answers,
68+
categories: data.data.categories,
6969
} as Question);
7070

71-
// Remove answers collection
72-
const answers = collectionRef(
73-
FirestoreCollection.Exams,
74-
data.slug,
75-
FirestoreCollection.Questions,
76-
questionRef.id,
77-
FirestoreCollection.Answers,
78-
);
79-
for (const doc of (await answers.get()).docs) {
80-
await doc.ref.delete();
81-
}
82-
83-
// Add answers
84-
for (const answer of data.data.answers) {
85-
await answers.add(answer);
86-
}
87-
8871
logger.info({ message: "edited exam question", data });
8972

9073
return {};

packages/functions/src/getExam.ts

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,7 @@ try {
77
import { logger } from "firebase-functions/v2";
88
import { HttpsError, onCall } from "firebase-functions/v2/https";
99
// @ts-ignore
10-
import {
11-
AddId,
12-
Answer,
13-
ExamWithQuestions,
14-
FirestoreCollection,
15-
PlainDoc,
16-
QuestionWithAnswers,
17-
} from "@examtraining/core";
10+
import { ExamWithQuestions, FirestoreCollection } from "@examtraining/core";
1811
import { collectionRef, getDocument, toIdAndRef, toPlainObject } from "./utils";
1912

2013
type GetExamParams = { slug: string; accessCode?: string; editCode?: string };
@@ -76,46 +69,9 @@ export const getExam = onCall<GetExamParams, Promise<GetExamReturn>>(
7669
.get(),
7770
);
7871

79-
const plainQuestions: PlainDoc<AddId<QuestionWithAnswers>>[] = [];
80-
const promises: Promise<void>[] = [];
81-
82-
for (const key in questions) {
83-
const question = questions[key];
84-
const plainQuestion = toPlainObject(
85-
question,
86-
) as AddId<QuestionWithAnswers>;
87-
88-
promises.push(
89-
new Promise(async (resolve) => {
90-
await collectionRef(
91-
FirestoreCollection.Exams,
92-
data.slug,
93-
FirestoreCollection.Questions,
94-
question.id,
95-
FirestoreCollection.Answers,
96-
)
97-
.orderBy("order")
98-
.get()
99-
.then((snapshot) => {
100-
const answers = toIdAndRef(snapshot);
101-
const plainAnswers: PlainDoc<AddId<Answer>>[] = [];
102-
103-
answers.forEach((answer) => {
104-
plainAnswers.push(
105-
toPlainObject(answer) as PlainDoc<AddId<Answer>>,
106-
);
107-
});
108-
109-
plainQuestion.answers = plainAnswers;
110-
plainQuestions[key] = plainQuestion;
111-
})
112-
.then(resolve);
113-
}),
114-
);
115-
}
116-
117-
await Promise.all(promises);
118-
72+
const plainQuestions = questions.map((question) =>
73+
toPlainObject(question),
74+
);
11975
const plainExam = toPlainObject(exam);
12076
const examWithQuestions = {
12177
...plainExam,

packages/functions/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export * from "./editExamDetails";
66
export * from "./editExamQuestion";
77
export * from "./getExam";
88
export * from "./isSlugAvailable";
9+
export * from "./onExamDeleted";
910
export * from "./removeExamQuestion";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import admin from "firebase-admin";
2+
3+
try {
4+
admin.initializeApp();
5+
} catch (error) {}
6+
7+
import { Exam } from "@examtraining/core";
8+
import { QueryDocumentSnapshot } from "firebase-admin/firestore";
9+
import { logger } from "firebase-functions/v2";
10+
import { onDocumentDeleted } from "firebase-functions/v2/firestore";
11+
12+
export const onExamDeleted = onDocumentDeleted(
13+
"exams/{examId}",
14+
async (event) => {
15+
const snap = event.data as QueryDocumentSnapshot<Exam> | undefined;
16+
const exam = snap?.data();
17+
18+
if (!exam) {
19+
throw new Error("could not grab snapshot data.");
20+
}
21+
22+
try {
23+
await exam.secrets.delete();
24+
25+
logger.info({ message: "deleted secret", data: exam });
26+
27+
return {};
28+
} catch (error) {
29+
logger.error(error);
30+
throw error;
31+
}
32+
},
33+
);

packages/functions/src/removeExamQuestion.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import admin, { firestore } from "firebase-admin";
1+
import admin from "firebase-admin";
22

33
try {
44
admin.initializeApp();
@@ -53,12 +53,13 @@ export const removeExamQuestion = onCall<
5353
}
5454

5555
// Remove question
56-
const questionRef = collectionRef(
56+
await collectionRef(
5757
FirestoreCollection.Exams,
5858
data.slug,
5959
FirestoreCollection.Questions,
60-
).doc(data.questionId);
61-
await firestore().recursiveDelete(questionRef);
60+
)
61+
.doc(data.questionId)
62+
.delete();
6263

6364
logger.info({ message: "removed exam question", data });
6465

0 commit comments

Comments
 (0)