@olinfo/quizms-firebase 4.0.1 → 4.1.1

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.
Files changed (68) hide show
  1. package/dist/functions.d.ts +2 -11
  2. package/dist/functions.js +2 -2
  3. package/dist/package.json.js +1 -1
  4. package/dist/src/cli/files/functions-index.js +4 -4
  5. package/dist/src/cli/files/functions-index.js.map +1 -1
  6. package/dist/src/cli/import.js +85 -96
  7. package/dist/src/cli/import.js.map +1 -1
  8. package/dist/src/cli/init.js +8 -8
  9. package/dist/src/cli/init.js.map +1 -1
  10. package/dist/src/cli/update-scores.js +21 -32
  11. package/dist/src/cli/update-scores.js.map +1 -1
  12. package/dist/src/cli/utils/collection.js +20 -18
  13. package/dist/src/cli/utils/collection.js.map +1 -1
  14. package/dist/src/cli/utils/converters-admin.js +31 -32
  15. package/dist/src/cli/utils/converters-admin.js.map +1 -1
  16. package/dist/src/cli/utils/region-finder.js +6 -5
  17. package/dist/src/cli/utils/region-finder.js.map +1 -1
  18. package/dist/src/cli/utils/storage.js +26 -35
  19. package/dist/src/cli/utils/storage.js.map +1 -1
  20. package/dist/src/cli/utils/users.js +13 -12
  21. package/dist/src/cli/utils/users.js.map +1 -1
  22. package/dist/src/functions/api/admin.js +25 -10
  23. package/dist/src/functions/api/admin.js.map +1 -1
  24. package/dist/src/functions/api/index.js +26 -37
  25. package/dist/src/functions/api/index.js.map +1 -1
  26. package/dist/src/functions/api/queries.js +24 -25
  27. package/dist/src/functions/api/queries.js.map +1 -1
  28. package/dist/src/functions/api/student.js +62 -72
  29. package/dist/src/functions/api/student.js.map +1 -1
  30. package/dist/src/functions/api/teacher.js +104 -111
  31. package/dist/src/functions/api/teacher.js.map +1 -1
  32. package/dist/src/functions/api/trpc.js +18 -0
  33. package/dist/src/functions/api/trpc.js.map +1 -0
  34. package/dist/src/functions/submissions.js +1 -1
  35. package/dist/src/functions/submissions.js.map +1 -1
  36. package/dist/src/models/user.js +2 -2
  37. package/dist/src/models/user.js.map +1 -1
  38. package/dist/src/models/website.js +1 -1
  39. package/dist/src/models/website.js.map +1 -1
  40. package/dist/src/vite/preset.js +3 -4
  41. package/dist/src/vite/preset.js.map +1 -1
  42. package/dist/src/web/admin/announcements.js +9 -10
  43. package/dist/src/web/admin/announcements.js.map +1 -1
  44. package/dist/src/web/admin/school-table.js +7 -8
  45. package/dist/src/web/admin/school-table.js.map +1 -1
  46. package/dist/src/web/common/api.js +32 -35
  47. package/dist/src/web/common/api.js.map +1 -1
  48. package/dist/src/web/common/converters.js +35 -36
  49. package/dist/src/web/common/converters.js.map +1 -1
  50. package/dist/src/web/common/token-login.js +11 -11
  51. package/dist/src/web/common/token-login.js.map +1 -1
  52. package/dist/src/web/entry/admin.js +5 -7
  53. package/dist/src/web/entry/admin.js.map +1 -1
  54. package/dist/src/web/entry/student-restoring.js +18 -20
  55. package/dist/src/web/entry/student-restoring.js.map +1 -1
  56. package/dist/src/web/entry/student.js +70 -76
  57. package/dist/src/web/entry/student.js.map +1 -1
  58. package/dist/src/web/entry/teacher.js +68 -68
  59. package/dist/src/web/entry/teacher.js.map +1 -1
  60. package/dist/src/web/hooks/collection.js +29 -30
  61. package/dist/src/web/hooks/collection.js.map +1 -1
  62. package/dist/src/web/hooks/document.js +35 -36
  63. package/dist/src/web/hooks/document.js.map +1 -1
  64. package/dist/src/web/hooks/subscription.js +26 -22
  65. package/dist/src/web/hooks/subscription.js.map +1 -1
  66. package/package.json +29 -25
  67. package/dist/src/functions/api/schema.js +0 -42
  68. package/dist/src/functions/api/schema.js.map +0 -1
@@ -1,37 +1,36 @@
1
- import i from "node:crypto";
2
- import { Rng as c } from "@olinfo/quizms/utils";
3
- import { studentConverter as o, userConverter as p, participationConverter as a, contestConverter as h, variantConverter as d } from "../../cli/utils/converters-admin.js";
1
+ import { Rng as i } from "@olinfo/quizms/utils";
2
+ import { userConverter as c, participationConverter as o, contestConverter as p, studentConverter as a, variantConverter as h } from "../../cli/utils/converters-admin.js";
4
3
  import { db as e } from "../common.js";
5
- const u = new c(i.randomUUID());
6
- async function l(t) {
7
- return (await e.doc(`contests/${t}`).withConverter(h).get()).data();
4
+ const u = new i();
5
+ async function v(t) {
6
+ return (await e.doc(`contests/${t}`).withConverter(p).get()).data();
8
7
  }
9
- async function m(t) {
10
- return (await e.doc(`participations/${t}`).withConverter(a).get()).data();
8
+ async function C(t) {
9
+ return (await e.doc(`participations/${t}`).withConverter(o).get()).data();
11
10
  }
12
- async function f(t, n) {
13
- return (await e.collection(`participations/${t}/students`).where("token", "==", n).withConverter(o).get()).docs.map((s) => s.data());
11
+ async function l(t, n) {
12
+ return (await e.collection(`participations/${t}/students`).where("token", "==", n).withConverter(a).get()).docs.map((r) => r.data());
14
13
  }
15
- async function y(t) {
16
- return (await e.collection("participations").withConverter(a).where("token", "==", t).get()).docs[0]?.data();
14
+ async function f(t) {
15
+ return (await e.collection("participations").withConverter(o).where("token", "==", t).get()).docs[0]?.data();
17
16
  }
18
- async function $(t) {
19
- return (await e.collection("users").where("username", "==", t).withConverter(p).get()).docs[0]?.data();
17
+ async function m(t) {
18
+ return (await e.collection("users").where("username", "==", t).withConverter(c).get()).docs[0]?.data();
20
19
  }
21
- async function k(t) {
22
- const n = await e.collection("variants").withConverter(d).where("contestId", "==", t).where("isOnline", "==", !0).get();
20
+ async function y(t) {
21
+ const n = await e.collection("variants").withConverter(h).where("contestId", "==", t).where("isOnline", "==", !0).get();
23
22
  return u.choice(n.docs).data();
24
23
  }
25
- async function P(t, n) {
26
- return (await e.collection(`participations/${t}/students`).withConverter(o).where("userDataHash", "==", n).where("disabled", "!=", !0).get()).docs[0]?.data();
24
+ async function $(t, n) {
25
+ return (await e.collection(`participations/${t}/students`).withConverter(a).where("userDataHash", "==", n).where("disabled", "!=", !0).get()).docs[0]?.data();
27
26
  }
28
27
  export {
29
- l as getContest,
30
- m as getParticipation,
31
- y as getParticipationByToken,
32
- f as getParticipationStudents,
33
- k as getRandomVariant,
34
- P as getStudentByHash,
35
- $ as getUser
28
+ v as getContest,
29
+ C as getParticipation,
30
+ f as getParticipationByToken,
31
+ l as getParticipationStudents,
32
+ y as getRandomVariant,
33
+ $ as getStudentByHash,
34
+ m as getUser
36
35
  };
37
36
  //# sourceMappingURL=queries.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"queries.js","sources":["../../../../src/functions/api/queries.ts"],"sourcesContent":["import crypto from \"node:crypto\";\n\nimport { Rng } from \"@olinfo/quizms/utils\";\n\nimport {\n contestConverter,\n participationConverter,\n studentConverter,\n userConverter,\n variantConverter,\n} from \"~/cli/utils/converters-admin\";\nimport { db } from \"~/functions/common\";\n\nconst rng = new Rng(crypto.randomUUID());\n\nexport async function getContest(contestId: string) {\n const snapshot = await db.doc(`contests/${contestId}`).withConverter(contestConverter).get();\n return snapshot.data()!;\n}\n\nexport async function getParticipation(participationId: string) {\n const snapshot = await db\n .doc(`participations/${participationId}`)\n .withConverter(participationConverter)\n .get();\n return snapshot.data();\n}\n\nexport async function getParticipationStudents(participationId: string, token: string) {\n const snapshot = await db\n .collection(`participations/${participationId}/students`)\n .where(\"token\", \"==\", token)\n .withConverter(studentConverter)\n .get();\n return snapshot.docs.map((doc) => doc.data());\n}\n\nexport async function getParticipationByToken(token: string) {\n const snapshot = await db\n .collection(\"participations\")\n .withConverter(participationConverter)\n .where(\"token\", \"==\", token)\n .get();\n return snapshot.docs[0]?.data();\n}\n\nexport async function getUser(username: string) {\n const snapshot = await db\n .collection(\"users\")\n .where(\"username\", \"==\", username)\n .withConverter(userConverter)\n .get();\n return snapshot.docs[0]?.data();\n}\n\nexport async function getRandomVariant(contestId: string) {\n const snapshot = await db\n .collection(\"variants\")\n .withConverter(variantConverter)\n .where(\"contestId\", \"==\", contestId)\n .where(\"isOnline\", \"==\", true)\n .get();\n return rng.choice(snapshot.docs).data();\n}\nexport async function getStudentByHash(participationId: string, userDataHash: string) {\n const snapshot = await db\n .collection(`participations/${participationId}/students`)\n .withConverter(studentConverter)\n .where(\"userDataHash\", \"==\", userDataHash)\n .where(\"disabled\", \"!=\", true)\n .get();\n return snapshot.docs[0]?.data();\n}\n"],"names":["rng","Rng","crypto","getContest","contestId","db","contestConverter","getParticipation","participationId","participationConverter","getParticipationStudents","token","studentConverter","doc","getParticipationByToken","getUser","username","userConverter","getRandomVariant","snapshot","variantConverter","getStudentByHash","userDataHash"],"mappings":";;;;AAaA,MAAMA,IAAM,IAAIC,EAAIC,EAAO,YAAY;AAEvC,eAAsBC,EAAWC,GAAmB;AAElD,UADiB,MAAMC,EAAG,IAAI,YAAYD,CAAS,EAAE,EAAE,cAAcE,CAAgB,EAAE,IAAA,GACvE,KAAA;AAClB;AAEA,eAAsBC,EAAiBC,GAAyB;AAK9D,UAJiB,MAAMH,EACpB,IAAI,kBAAkBG,CAAe,EAAE,EACvC,cAAcC,CAAsB,EACpC,IAAA,GACa,KAAA;AAClB;AAEA,eAAsBC,EAAyBF,GAAyBG,GAAe;AAMrF,UALiB,MAAMN,EACpB,WAAW,kBAAkBG,CAAe,WAAW,EACvD,MAAM,SAAS,MAAMG,CAAK,EAC1B,cAAcC,CAAgB,EAC9B,IAAA,GACa,KAAK,IAAI,CAACC,MAAQA,EAAI,MAAM;AAC9C;AAEA,eAAsBC,EAAwBH,GAAe;AAM3D,UALiB,MAAMN,EACpB,WAAW,gBAAgB,EAC3B,cAAcI,CAAsB,EACpC,MAAM,SAAS,MAAME,CAAK,EAC1B,IAAA,GACa,KAAK,CAAC,GAAG,KAAA;AAC3B;AAEA,eAAsBI,EAAQC,GAAkB;AAM9C,UALiB,MAAMX,EACpB,WAAW,OAAO,EAClB,MAAM,YAAY,MAAMW,CAAQ,EAChC,cAAcC,CAAa,EAC3B,IAAA,GACa,KAAK,CAAC,GAAG,KAAA;AAC3B;AAEA,eAAsBC,EAAiBd,GAAmB;AACxD,QAAMe,IAAW,MAAMd,EACpB,WAAW,UAAU,EACrB,cAAce,CAAgB,EAC9B,MAAM,aAAa,MAAMhB,CAAS,EAClC,MAAM,YAAY,MAAM,EAAI,EAC5B,IAAA;AACH,SAAOJ,EAAI,OAAOmB,EAAS,IAAI,EAAE,KAAA;AACnC;AACA,eAAsBE,EAAiBb,GAAyBc,GAAsB;AAOpF,UANiB,MAAMjB,EACpB,WAAW,kBAAkBG,CAAe,WAAW,EACvD,cAAcI,CAAgB,EAC9B,MAAM,gBAAgB,MAAMU,CAAY,EACxC,MAAM,YAAY,MAAM,EAAI,EAC5B,IAAA,GACa,KAAK,CAAC,GAAG,KAAA;AAC3B;"}
1
+ {"version":3,"file":"queries.js","sources":["../../../../src/functions/api/queries.ts"],"sourcesContent":["import { Rng } from \"@olinfo/quizms/utils\";\n\nimport {\n contestConverter,\n participationConverter,\n studentConverter,\n userConverter,\n variantConverter,\n} from \"~/cli/utils/converters-admin\";\nimport { db } from \"~/functions/common\";\n\nconst rng = new Rng();\n\nexport async function getContest(contestId: string) {\n const snapshot = await db.doc(`contests/${contestId}`).withConverter(contestConverter).get();\n return snapshot.data()!;\n}\n\nexport async function getParticipation(participationId: string) {\n const snapshot = await db\n .doc(`participations/${participationId}`)\n .withConverter(participationConverter)\n .get();\n return snapshot.data();\n}\n\nexport async function getParticipationStudents(participationId: string, token: string) {\n const snapshot = await db\n .collection(`participations/${participationId}/students`)\n .where(\"token\", \"==\", token)\n .withConverter(studentConverter)\n .get();\n return snapshot.docs.map((doc) => doc.data());\n}\n\nexport async function getParticipationByToken(token: string) {\n const snapshot = await db\n .collection(\"participations\")\n .withConverter(participationConverter)\n .where(\"token\", \"==\", token)\n .get();\n return snapshot.docs[0]?.data();\n}\n\nexport async function getUser(username: string) {\n const snapshot = await db\n .collection(\"users\")\n .where(\"username\", \"==\", username)\n .withConverter(userConverter)\n .get();\n return snapshot.docs[0]?.data();\n}\n\nexport async function getRandomVariant(contestId: string) {\n const snapshot = await db\n .collection(\"variants\")\n .withConverter(variantConverter)\n .where(\"contestId\", \"==\", contestId)\n .where(\"isOnline\", \"==\", true)\n .get();\n return rng.choice(snapshot.docs).data();\n}\nexport async function getStudentByHash(participationId: string, userDataHash: string) {\n const snapshot = await db\n .collection(`participations/${participationId}/students`)\n .withConverter(studentConverter)\n .where(\"userDataHash\", \"==\", userDataHash)\n .where(\"disabled\", \"!=\", true)\n .get();\n return snapshot.docs[0]?.data();\n}\n"],"names":["rng","Rng","getContest","contestId","db","contestConverter","getParticipation","participationId","participationConverter","getParticipationStudents","token","studentConverter","doc","getParticipationByToken","getUser","username","userConverter","getRandomVariant","snapshot","variantConverter","getStudentByHash","userDataHash"],"mappings":";;;AAWA,MAAMA,IAAM,IAAIC,EAAA;AAEhB,eAAsBC,EAAWC,GAAmB;AAElD,UADiB,MAAMC,EAAG,IAAI,YAAYD,CAAS,EAAE,EAAE,cAAcE,CAAgB,EAAE,IAAA,GACvE,KAAA;AAClB;AAEA,eAAsBC,EAAiBC,GAAyB;AAK9D,UAJiB,MAAMH,EACpB,IAAI,kBAAkBG,CAAe,EAAE,EACvC,cAAcC,CAAsB,EACpC,IAAA,GACa,KAAA;AAClB;AAEA,eAAsBC,EAAyBF,GAAyBG,GAAe;AAMrF,UALiB,MAAMN,EACpB,WAAW,kBAAkBG,CAAe,WAAW,EACvD,MAAM,SAAS,MAAMG,CAAK,EAC1B,cAAcC,CAAgB,EAC9B,IAAA,GACa,KAAK,IAAI,CAACC,MAAQA,EAAI,MAAM;AAC9C;AAEA,eAAsBC,EAAwBH,GAAe;AAM3D,UALiB,MAAMN,EACpB,WAAW,gBAAgB,EAC3B,cAAcI,CAAsB,EACpC,MAAM,SAAS,MAAME,CAAK,EAC1B,IAAA,GACa,KAAK,CAAC,GAAG,KAAA;AAC3B;AAEA,eAAsBI,EAAQC,GAAkB;AAM9C,UALiB,MAAMX,EACpB,WAAW,OAAO,EAClB,MAAM,YAAY,MAAMW,CAAQ,EAChC,cAAcC,CAAa,EAC3B,IAAA,GACa,KAAK,CAAC,GAAG,KAAA;AAC3B;AAEA,eAAsBC,EAAiBd,GAAmB;AACxD,QAAMe,IAAW,MAAMd,EACpB,WAAW,UAAU,EACrB,cAAce,CAAgB,EAC9B,MAAM,aAAa,MAAMhB,CAAS,EAClC,MAAM,YAAY,MAAM,EAAI,EAC5B,IAAA;AACH,SAAOH,EAAI,OAAOkB,EAAS,IAAI,EAAE,KAAA;AACnC;AACA,eAAsBE,EAAiBb,GAAyBc,GAAsB;AAOpF,UANiB,MAAMjB,EACpB,WAAW,kBAAkBG,CAAe,WAAW,EACvD,cAAcI,CAAgB,EAC9B,MAAM,gBAAgB,MAAMU,CAAY,EACxC,MAAM,YAAY,MAAM,EAAI,EAC5B,IAAA,GACa,KAAK,CAAC,GAAG,KAAA;AAC3B;"}
@@ -1,88 +1,78 @@
1
- import g from "node:crypto";
2
- import { sha256 as k } from "@noble/hashes/sha2";
3
- import { parseUserData as w, getNormalizedUserData as T } from "@olinfo/quizms/models";
4
- import { Timestamp as s } from "firebase-admin/firestore";
5
- import * as I from "firebase-functions/logger";
6
- import { db as f, auth as i } from "../common.js";
7
- import { getParticipationByToken as v, getContest as D, getStudentByHash as h, getRandomVariant as A } from "./queries.js";
8
- function C(d, t) {
9
- const e = k(T(d, { userData: t }));
10
- return Buffer.from(e).toString("base64url");
1
+ import D, { hash as I } from "node:crypto";
2
+ import { studentSchema as p, getNormalizedUserData as w } from "@olinfo/quizms/models";
3
+ import { TRPCError as i } from "@trpc/server";
4
+ import { Timestamp as r } from "firebase-admin/firestore";
5
+ import * as h from "firebase-functions/logger";
6
+ import s from "zod";
7
+ import { db as u, auth as m } from "../common.js";
8
+ import { getParticipationByToken as l, getContest as k, getStudentByHash as v, getRandomVariant as T } from "./queries.js";
9
+ import { publicProcedure as C } from "./trpc.js";
10
+ function R(o, t) {
11
+ return I("sha256", w(o, { userData: t }));
11
12
  }
12
- const b = async (d, t) => {
13
- I.info("Student login request received", { data: t });
14
- const e = await v(t.token);
15
- if (!e)
16
- return { success: !1, errorCode: "INVALID_TOKEN", error: "Codice prova non valido" };
17
- if (e.contestId !== t.contestId)
18
- return {
19
- success: !1,
20
- errorCode: "TOKEN_CONTEST_MISMATCH",
21
- error: "Il token proviene da una gara diversa"
22
- };
23
- const c = await D(e.contestId);
24
- let r;
25
- try {
26
- r = Object.fromEntries(
27
- c.userData.map((n) => {
28
- const m = w(t.userData[n.name], n);
29
- if (m == null) throw new Error("Missing field");
30
- return [n.name, m];
31
- })
32
- );
33
- } catch (n) {
34
- return I.error("Invalid user data", { error: n }), {
35
- success: !1,
36
- errorCode: "INVALID_USER_DATA",
37
- error: "Dati non validi"
38
- };
39
- }
40
- const o = g.randomUUID(), u = C(c, r), a = await h(e.id, u);
41
- if (a)
42
- return a.token !== t.token ? {
43
- success: !1,
44
- errorCode: "MULTIPLE_PARTICIPATION",
45
- error: "Hai già partecipato a questa gara"
46
- } : (await f.doc(`studentRestores/${o}`).set({
47
- studentId: a.id,
48
- participationId: a.participationId,
13
+ const U = C.input(
14
+ s.strictObject({
15
+ contestId: s.string(),
16
+ token: s.string(),
17
+ userData: p.shape.userData.unwrap().required(),
18
+ extraData: p.shape.extraData.unwrap()
19
+ })
20
+ ).mutation(async (o) => {
21
+ const { input: t } = o;
22
+ h.info("Student login request received", { data: t });
23
+ const a = await l(t.token);
24
+ if (!a)
25
+ throw new i({ code: "BAD_REQUEST", message: "Codice prova non valido" });
26
+ if (a.contestId !== t.contestId)
27
+ throw new i({ code: "FORBIDDEN", message: "Codice prova non valido" });
28
+ const f = await k(a.contestId), n = D.randomUUID(), d = R(f, t.userData), e = await v(a.id, d);
29
+ if (e) {
30
+ if (e.token !== t.token)
31
+ throw new i({
32
+ code: "FORBIDDEN",
33
+ message: "Hai già partecipato a questa gara"
34
+ });
35
+ return await u.doc(`studentRestores/${n}`).set({
36
+ studentId: e.id,
37
+ participationId: e.participationId,
49
38
  token: t.token,
50
- name: r.name,
51
- surname: r.surname,
39
+ name: t.userData.name,
40
+ surname: t.userData.surname,
52
41
  approvalCode: Math.round(Math.random() * 1e3),
53
42
  status: "pending",
54
- createdAt: s.now()
55
- }), { success: !0, data: { token: await i.createCustomToken(o, {
43
+ createdAt: r.now()
44
+ }), m.createCustomToken(n, {
56
45
  role: "student",
57
- studentId: a.id,
58
- participationId: a.participationId,
59
- variant: a.variant
60
- }) } });
61
- const p = await A(t.contestId), l = await f.collection(`participations/${e.id}/students`).add({
62
- uid: o,
63
- userData: r,
64
- userDataHash: u,
46
+ studentId: e.id,
47
+ participationId: e.participationId,
48
+ variant: e.variant
49
+ });
50
+ }
51
+ const c = await T(t.contestId), g = await u.collection(`participations/${a.id}/students`).add({
52
+ uid: n,
53
+ userData: t.userData,
54
+ userDataHash: d,
65
55
  absent: !1,
66
56
  disabled: !1,
67
- participationId: e.id,
57
+ participationId: a.id,
68
58
  contestId: t.contestId,
69
59
  token: t.token,
70
- startedAt: e.startingTime,
71
- finishedAt: e.endingTime,
72
- variant: p.id,
60
+ startedAt: a.startingTime,
61
+ finishedAt: a.endingTime,
62
+ variant: c.id,
73
63
  answers: {},
74
64
  extraData: t.extraData,
75
- createdAt: s.now(),
76
- updatedAt: s.now()
65
+ createdAt: r.now(),
66
+ updatedAt: r.now()
77
67
  });
78
- return await i.createUser({ uid: o, displayName: `${r.name} ${r.surname}` }), { success: !0, data: { token: await i.createCustomToken(o, {
68
+ return m.createCustomToken(n, {
79
69
  role: "student",
80
- studentId: l.id,
81
- participationId: e.id,
82
- variant: p
83
- }) } };
84
- };
70
+ studentId: g.id,
71
+ participationId: a.id,
72
+ variant: c
73
+ });
74
+ });
85
75
  export {
86
- b as studentLogin
76
+ U as studentLogin
87
77
  };
88
78
  //# sourceMappingURL=student.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"student.js","sources":["../../../../src/functions/api/student.ts"],"sourcesContent":["import crypto from \"node:crypto\";\n\nimport { sha256 } from \"@noble/hashes/sha2\";\nimport {\n type Contest,\n getNormalizedUserData,\n parseUserData,\n type Student,\n} from \"@olinfo/quizms/models\";\nimport { Timestamp } from \"firebase-admin/firestore\";\nimport * as logger from \"firebase-functions/logger\";\n\nimport { auth, db, type Endpoint } from \"../common\";\nimport { getContest, getParticipationByToken, getRandomVariant, getStudentByHash } from \"./queries\";\nimport type { StudentLogin } from \"./schema\";\n\nfunction studentHash(contest: Contest, userData: NonNullable<Student[\"userData\"]>) {\n const hash = sha256(getNormalizedUserData(contest, { userData }));\n return Buffer.from(hash).toString(\"base64url\");\n}\n\nexport const studentLogin: Endpoint<StudentLogin> = async (_request, data) => {\n logger.info(\"Student login request received\", { data });\n\n const participation = await getParticipationByToken(data.token);\n if (!participation) {\n return { success: false, errorCode: \"INVALID_TOKEN\", error: \"Codice prova non valido\" };\n }\n if (participation.contestId !== data.contestId) {\n return {\n success: false,\n errorCode: \"TOKEN_CONTEST_MISMATCH\",\n error: \"Il token proviene da una gara diversa\",\n };\n }\n\n const contest = await getContest(participation.contestId);\n\n let userData: NonNullable<Student[\"userData\"]>;\n try {\n userData = Object.fromEntries(\n contest.userData.map((field) => {\n const value = parseUserData(data.userData[field.name], field);\n if (value == null) throw new Error(\"Missing field\");\n return [field.name, value];\n }),\n );\n } catch (error) {\n logger.error(\"Invalid user data\", { error });\n return {\n success: false,\n errorCode: \"INVALID_USER_DATA\",\n error: \"Dati non validi\",\n };\n }\n\n const uid = crypto.randomUUID();\n const userDataHash = studentHash(contest, userData);\n\n const duplicated = await getStudentByHash(participation.id, userDataHash);\n if (duplicated) {\n if (duplicated.token !== data.token) {\n return {\n success: false,\n errorCode: \"MULTIPLE_PARTICIPATION\",\n error: \"Hai già partecipato a questa gara\",\n };\n }\n\n await db.doc(`studentRestores/${uid}`).set({\n studentId: duplicated.id,\n participationId: duplicated.participationId,\n token: data.token,\n name: userData.name,\n surname: userData.surname,\n approvalCode: Math.round(Math.random() * 1000),\n status: \"pending\",\n createdAt: Timestamp.now(),\n });\n\n const token = await auth.createCustomToken(uid, {\n role: \"student\",\n studentId: duplicated.id,\n participationId: duplicated.participationId,\n variant: duplicated.variant,\n });\n return { success: true, data: { token } };\n }\n\n const variant = await getRandomVariant(data.contestId);\n\n const studentRef = await db.collection(`participations/${participation.id}/students`).add({\n uid,\n userData,\n userDataHash,\n absent: false,\n disabled: false,\n participationId: participation.id,\n contestId: data.contestId,\n token: data.token,\n startedAt: participation.startingTime,\n finishedAt: participation.endingTime,\n variant: variant.id,\n answers: {},\n extraData: data.extraData,\n createdAt: Timestamp.now(),\n updatedAt: Timestamp.now(),\n });\n\n await auth.createUser({ uid, displayName: `${userData.name} ${userData.surname}` });\n const token = await auth.createCustomToken(uid, {\n role: \"student\",\n studentId: studentRef.id,\n participationId: participation.id,\n variant,\n });\n return { success: true, data: { token } };\n};\n"],"names":["studentHash","contest","userData","hash","sha256","getNormalizedUserData","studentLogin","_request","data","logger","participation","getParticipationByToken","getContest","field","value","parseUserData","error","uid","crypto","userDataHash","duplicated","getStudentByHash","db","Timestamp","auth","variant","getRandomVariant","studentRef"],"mappings":"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"}
1
+ {"version":3,"file":"student.js","sources":["../../../../src/functions/api/student.ts"],"sourcesContent":["import crypto, { hash } from \"node:crypto\";\n\nimport {\n type Contest,\n getNormalizedUserData,\n type Student,\n studentSchema,\n} from \"@olinfo/quizms/models\";\nimport { TRPCError } from \"@trpc/server\";\nimport { Timestamp } from \"firebase-admin/firestore\";\nimport * as logger from \"firebase-functions/logger\";\nimport z from \"zod\";\n\nimport { auth, db } from \"../common\";\nimport { getContest, getParticipationByToken, getRandomVariant, getStudentByHash } from \"./queries\";\nimport { publicProcedure } from \"./trpc\";\n\nfunction studentHash(contest: Contest, userData: NonNullable<Student[\"userData\"]>) {\n return hash(\"sha256\", getNormalizedUserData(contest, { userData }));\n}\n\nexport const studentLogin = publicProcedure\n .input(\n z.strictObject({\n contestId: z.string(),\n token: z.string(),\n userData: studentSchema.shape.userData.unwrap().required(),\n extraData: studentSchema.shape.extraData.unwrap(),\n }),\n )\n .mutation(async (opts) => {\n const { input: data } = opts;\n logger.info(\"Student login request received\", { data });\n\n const participation = await getParticipationByToken(data.token);\n if (!participation) {\n throw new TRPCError({ code: \"BAD_REQUEST\", message: \"Codice prova non valido\" });\n }\n if (participation.contestId !== data.contestId) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Codice prova non valido\" });\n }\n\n const contest = await getContest(participation.contestId);\n\n const uid = crypto.randomUUID();\n const userDataHash = studentHash(contest, data.userData);\n\n const duplicated = await getStudentByHash(participation.id, userDataHash);\n if (duplicated) {\n if (duplicated.token !== data.token) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message: \"Hai già partecipato a questa gara\",\n });\n }\n\n await db.doc(`studentRestores/${uid}`).set({\n studentId: duplicated.id,\n participationId: duplicated.participationId,\n token: data.token,\n name: data.userData.name,\n surname: data.userData.surname,\n approvalCode: Math.round(Math.random() * 1000),\n status: \"pending\",\n createdAt: Timestamp.now(),\n });\n\n return auth.createCustomToken(uid, {\n role: \"student\",\n studentId: duplicated.id,\n participationId: duplicated.participationId,\n variant: duplicated.variant,\n });\n }\n\n const variant = await getRandomVariant(data.contestId);\n\n const studentRef = await db.collection(`participations/${participation.id}/students`).add({\n uid,\n userData: data.userData,\n userDataHash,\n absent: false,\n disabled: false,\n participationId: participation.id,\n contestId: data.contestId,\n token: data.token,\n startedAt: participation.startingTime,\n finishedAt: participation.endingTime,\n variant: variant.id,\n answers: {},\n extraData: data.extraData,\n createdAt: Timestamp.now(),\n updatedAt: Timestamp.now(),\n });\n\n return auth.createCustomToken(uid, {\n role: \"student\",\n studentId: studentRef.id,\n participationId: participation.id,\n variant,\n });\n });\n"],"names":["studentHash","contest","userData","hash","getNormalizedUserData","studentLogin","publicProcedure","z","studentSchema","opts","data","logger","participation","getParticipationByToken","TRPCError","getContest","uid","crypto","userDataHash","duplicated","getStudentByHash","db","Timestamp","auth","variant","getRandomVariant","studentRef"],"mappings":";;;;;;;;;AAiBA,SAASA,EAAYC,GAAkBC,GAA4C;AACjF,SAAOC,EAAK,UAAUC,EAAsBH,GAAS,EAAE,UAAAC,EAAA,CAAU,CAAC;AACpE;AAEO,MAAMG,IAAeC,EACzB;AAAA,EACCC,EAAE,aAAa;AAAA,IACb,WAAWA,EAAE,OAAA;AAAA,IACb,OAAOA,EAAE,OAAA;AAAA,IACT,UAAUC,EAAc,MAAM,SAAS,OAAA,EAAS,SAAA;AAAA,IAChD,WAAWA,EAAc,MAAM,UAAU,OAAA;AAAA,EAAO,CACjD;AACH,EACC,SAAS,OAAOC,MAAS;AACxB,QAAM,EAAE,OAAOC,EAAA,IAASD;AACxB,EAAAE,EAAO,KAAK,kCAAkC,EAAE,MAAAD,EAAA,CAAM;AAEtD,QAAME,IAAgB,MAAMC,EAAwBH,EAAK,KAAK;AAC9D,MAAI,CAACE;AACH,UAAM,IAAIE,EAAU,EAAE,MAAM,eAAe,SAAS,2BAA2B;AAEjF,MAAIF,EAAc,cAAcF,EAAK;AACnC,UAAM,IAAII,EAAU,EAAE,MAAM,aAAa,SAAS,2BAA2B;AAG/E,QAAMb,IAAU,MAAMc,EAAWH,EAAc,SAAS,GAElDI,IAAMC,EAAO,WAAA,GACbC,IAAelB,EAAYC,GAASS,EAAK,QAAQ,GAEjDS,IAAa,MAAMC,EAAiBR,EAAc,IAAIM,CAAY;AACxE,MAAIC,GAAY;AACd,QAAIA,EAAW,UAAUT,EAAK;AAC5B,YAAM,IAAII,EAAU;AAAA,QAClB,MAAM;AAAA,QACN,SAAS;AAAA,MAAA,CACV;AAGH,iBAAMO,EAAG,IAAI,mBAAmBL,CAAG,EAAE,EAAE,IAAI;AAAA,MACzC,WAAWG,EAAW;AAAA,MACtB,iBAAiBA,EAAW;AAAA,MAC5B,OAAOT,EAAK;AAAA,MACZ,MAAMA,EAAK,SAAS;AAAA,MACpB,SAASA,EAAK,SAAS;AAAA,MACvB,cAAc,KAAK,MAAM,KAAK,OAAA,IAAW,GAAI;AAAA,MAC7C,QAAQ;AAAA,MACR,WAAWY,EAAU,IAAA;AAAA,IAAI,CAC1B,GAEMC,EAAK,kBAAkBP,GAAK;AAAA,MACjC,MAAM;AAAA,MACN,WAAWG,EAAW;AAAA,MACtB,iBAAiBA,EAAW;AAAA,MAC5B,SAASA,EAAW;AAAA,IAAA,CACrB;AAAA,EACH;AAEA,QAAMK,IAAU,MAAMC,EAAiBf,EAAK,SAAS,GAE/CgB,IAAa,MAAML,EAAG,WAAW,kBAAkBT,EAAc,EAAE,WAAW,EAAE,IAAI;AAAA,IACxF,KAAAI;AAAA,IACA,UAAUN,EAAK;AAAA,IACf,cAAAQ;AAAA,IACA,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,iBAAiBN,EAAc;AAAA,IAC/B,WAAWF,EAAK;AAAA,IAChB,OAAOA,EAAK;AAAA,IACZ,WAAWE,EAAc;AAAA,IACzB,YAAYA,EAAc;AAAA,IAC1B,SAASY,EAAQ;AAAA,IACjB,SAAS,CAAA;AAAA,IACT,WAAWd,EAAK;AAAA,IAChB,WAAWY,EAAU,IAAA;AAAA,IACrB,WAAWA,EAAU,IAAA;AAAA,EAAI,CAC1B;AAED,SAAOC,EAAK,kBAAkBP,GAAK;AAAA,IACjC,MAAM;AAAA,IACN,WAAWU,EAAW;AAAA,IACtB,iBAAiBd,EAAc;AAAA,IAC/B,SAAAY;AAAA,EAAA,CACD;AACH,CAAC;"}
@@ -1,136 +1,129 @@
1
- import I from "node:crypto";
2
- import { randomToken as g } from "@olinfo/quizms/utils";
3
- import { isPast as l, isFuture as T, roundToNearestMinutes as k, addSeconds as p, addMinutes as C } from "date-fns";
4
- import { getFunctions as O } from "firebase-admin/functions";
5
- import * as s from "firebase-functions/logger";
6
- import { defineString as N } from "firebase-functions/params";
7
- import { chunk as A } from "lodash-es";
8
- import { db as n, auth as m } from "../common.js";
9
- import { getParticipationStudents as w, getUser as P, getParticipation as S, getContest as _, getParticipationByToken as D } from "./queries.js";
10
- const E = N("QUIZMS_REGION"), b = async (t, a) => {
11
- s.info("Teacher login request received", { data: a });
12
- const r = await P(a.username);
13
- return r ? r.role !== "teacher" ? { success: !1, errorCode: "INVALID_ROLE", error: "Utenza non abilitata" } : r.password !== a.password ? { success: !1, errorCode: "INVALID_PASSWORD", error: "Password non corretta" } : { success: !0, data: { token: await m.createCustomToken(I.randomUUID(), {
1
+ import D from "node:crypto";
2
+ import { randomToken as E } from "@olinfo/quizms/utils";
3
+ import { TRPCError as o } from "@trpc/server";
4
+ import { isFuture as g, roundToNearestMinutes as O, addSeconds as l, addMinutes as R, isPast as h } from "date-fns";
5
+ import { getFunctions as B } from "firebase-admin/functions";
6
+ import * as d from "firebase-functions/logger";
7
+ import { defineString as F } from "firebase-functions/params";
8
+ import { chunk as P } from "lodash-es";
9
+ import r from "zod";
10
+ import { auth as T, db as c } from "../common.js";
11
+ import { getUser as S, getParticipationStudents as I, getParticipation as y, getContest as b, getParticipationByToken as v } from "./queries.js";
12
+ import { publicProcedure as w } from "./trpc.js";
13
+ const N = F("QUIZMS_REGION"), W = w.input(
14
+ r.strictObject({
15
+ username: r.string(),
16
+ password: r.string()
17
+ })
18
+ ).mutation(async (a) => {
19
+ const { input: i } = a;
20
+ d.info("Teacher login request received", { data: i });
21
+ const e = await S(i.username);
22
+ if (!e)
23
+ throw new o({ code: "BAD_REQUEST", message: "Username non esistente" });
24
+ if (e.password !== i.password)
25
+ throw new o({ code: "BAD_REQUEST", message: "Password non corretta" });
26
+ if (e.role !== "teacher")
27
+ throw new o({ code: "FORBIDDEN", message: "Utenza non abilitata" });
28
+ return T.createCustomToken(D.randomUUID(), {
14
29
  role: "teacher",
15
- schoolId: r.id
16
- }) } } : { success: !1, errorCode: "USER_NOT_FOUND", error: "Username non esistente" };
17
- };
18
- async function f(t, a) {
19
- const r = await S(t.data.participationId);
20
- if (!r)
21
- return [{ success: !1, errorCode: "PARTICIPATION_NOT_FOUND", error: "Gara non valida" }];
22
- if (r.schoolId !== t.auth?.token.schoolId)
23
- return [
24
- {
25
- success: !1,
26
- errorCode: "FORBIDDEN",
27
- error: "Gara non valida"
28
- }
29
- ];
30
- const e = await _(r.contestId);
31
- if (a) {
32
- if (!e.hasOnline)
33
- return [
34
- {
35
- success: !1,
36
- errorCode: "CONTEST_OFFLINE",
37
- error: "Gara non abilitata per prove online"
38
- }
39
- ];
40
- if (T(e.contestWindowStart))
41
- return [{ success: !1, errorCode: "CONTEST_NOT_STARTED", error: "Gara non iniziata" }];
42
- if (l(e.contestWindowEnd))
43
- return [{ success: !1, errorCode: "CONTEST_ENDED", error: "Gara già terminata" }];
30
+ schoolId: e.id
31
+ });
32
+ });
33
+ async function f(a, i, e) {
34
+ const t = await y(a);
35
+ if (!t)
36
+ throw new o({ code: "BAD_REQUEST", message: "Gara non valida" });
37
+ if (t.schoolId !== i?.schoolId)
38
+ throw new o({ code: "FORBIDDEN", message: "Gara non valida" });
39
+ const n = await b(t.contestId);
40
+ if (e) {
41
+ if (!n.hasOnline)
42
+ throw new o({ code: "FORBIDDEN", message: "Gara non abilitata per prove online" });
43
+ if (g(n.contestWindowStart))
44
+ throw new o({ code: "FORBIDDEN", message: "Gara non iniziata" });
45
+ if (h(n.contestWindowEnd))
46
+ throw new o({ code: "FORBIDDEN", message: "Gara già terminata" });
44
47
  }
45
- return [null, r, e];
48
+ return [t, n];
46
49
  }
47
- async function R() {
50
+ async function z() {
48
51
  for (; ; ) {
49
- const t = await g();
50
- if (!await D(t))
51
- return t;
52
+ const a = await E();
53
+ if (!await v(a))
54
+ return a;
52
55
  }
53
56
  }
54
- const Q = async (t, a) => {
55
- s.info("Teacher start participation received", { data: a });
56
- const [r, e, i] = await f(t, !0);
57
- if (r)
58
- return r;
59
- if (e.endingTime != null && i.hasOnline && (T(e.endingTime) || !i.allowRestart))
60
- return {
61
- success: !1,
62
- errorCode: "PARTICIPATION_ALREADY_STARTED",
63
- error: "Gara già iniziata"
64
- };
65
- if (e.token) {
66
- const d = await w(e.id, e.token);
67
- await h(d);
57
+ const L = w.input(r.strictObject({ participationId: r.string() })).mutation(async (a) => {
58
+ const { input: i, ctx: e } = a;
59
+ d.info("Teacher start participation received", { data: i });
60
+ const [t, n] = await f(
61
+ i.participationId,
62
+ e.claims,
63
+ !0
64
+ );
65
+ if (t.endingTime != null && n.hasOnline && (g(t.endingTime) || !n.allowRestart))
66
+ throw new o({ code: "FORBIDDEN", message: "Gara già iniziata" });
67
+ if (t.token) {
68
+ const m = await I(t.id, t.token);
69
+ await k(m);
68
70
  }
69
- const c = k(p(Date.now(), 3.5 * 60)), o = C(c, i.hasOnline ? i.duration : 0), u = await R();
70
- await n.doc(`participations/${e.id}`).update({
71
+ const p = O(l(Date.now(), 3.5 * 60)), s = R(p, n.hasOnline ? n.duration : 0), u = await z();
72
+ await c.doc(`participations/${t.id}`).update({
71
73
  token: u,
72
- startingTime: c,
73
- endingTime: o
74
+ startingTime: p,
75
+ endingTime: s
74
76
  });
75
77
  try {
76
- await O().taskQueue(
77
- `locations/${E.value()}/functions/updateScores`
78
+ await B().taskQueue(
79
+ `locations/${N.value()}/functions/updateScores`
78
80
  ).enqueue(
79
- { participationId: e.id, token: u },
80
- { scheduleTime: p(o, 5) }
81
+ { participationId: t.id, token: u },
82
+ { scheduleTime: l(s, 5) }
81
83
  );
82
- } catch (d) {
83
- s.error("Failed to queue updateScores task", { error: d });
84
+ } catch (m) {
85
+ d.error("Failed to queue updateScores task", { error: m });
84
86
  }
85
- return { success: !0, data: {} };
86
- }, q = async (t, a) => {
87
- s.info("Teacher stop participation received", { data: a });
88
- const [r, e] = await f(t, !0);
89
- if (r)
90
- return r;
91
- if (e.startingTime == null)
92
- return {
93
- success: !1,
94
- errorCode: "PARTICIPATION_NOT_STARTED",
95
- error: "Gara non iniziata"
96
- };
97
- if (l(e.startingTime))
98
- return {
99
- success: !1,
100
- errorCode: "PARTICIPATION_ALREADY_STARTED",
101
- error: "Gara già iniziata"
102
- };
103
- const i = await w(e.id, e.token);
104
- await h(i);
105
- for (const c of A(i, 300)) {
106
- const o = n.batch();
107
- for (const u of c)
108
- o.delete(n.doc(`participations/${e.id}/students/${u.id}`));
109
- await o.commit();
87
+ }), Z = w.input(r.strictObject({ participationId: r.string() })).mutation(async (a) => {
88
+ const { input: i, ctx: e } = a;
89
+ d.info("Teacher stop participation received", { data: i });
90
+ const [t] = await f(i.participationId, e.claims, !0);
91
+ if (t.startingTime == null)
92
+ throw new o({ code: "FORBIDDEN", message: "Gara non iniziata" });
93
+ if (h(t.startingTime))
94
+ throw new o({ code: "FORBIDDEN", message: "Gara già iniziata" });
95
+ const n = await I(t.id, t.token);
96
+ await k(n);
97
+ for (const p of P(n, 300)) {
98
+ const s = c.batch();
99
+ for (const u of p)
100
+ s.delete(c.doc(`participations/${t.id}/students/${u.id}`));
101
+ await s.commit();
110
102
  }
111
- return await n.doc(`participations/${e.id}`).update({
103
+ await c.doc(`participations/${t.id}`).update({
112
104
  token: null,
113
105
  startingTime: null,
114
106
  endingTime: null
115
- }), { success: !0, data: {} };
116
- };
117
- async function h(t) {
107
+ });
108
+ });
109
+ async function k(a) {
118
110
  await Promise.all(
119
- t.map(async (a) => {
120
- a.uid && await m.revokeRefreshTokens(a.uid).catch(() => {
111
+ a.map(async (i) => {
112
+ i.uid && await T.revokeRefreshTokens(i.uid).catch(() => {
121
113
  });
122
114
  })
123
115
  );
124
116
  }
125
- const M = async (t, a) => {
126
- s.info("Teacher finalize participation received", { data: a });
127
- const [r, e] = await f(t, !1);
128
- return r || (await n.doc(`participations/${e.id}`).update({ finalized: !0 }), { success: !0, data: {} });
129
- };
117
+ const H = w.input(r.strictObject({ participationId: r.string() })).mutation(async (a) => {
118
+ const { input: i, ctx: e } = a;
119
+ d.info("Teacher finalize participation received", { data: i });
120
+ const [t] = await f(i.participationId, e.claims, !1);
121
+ await c.doc(`participations/${t.id}`).update({ finalized: !0 });
122
+ });
130
123
  export {
131
- M as teacherFinalizeParticipation,
132
- b as teacherLogin,
133
- Q as teacherStartParticipation,
134
- q as teacherStopParticipation
124
+ H as teacherFinalizeParticipation,
125
+ W as teacherLogin,
126
+ L as teacherStartParticipation,
127
+ Z as teacherStopParticipation
135
128
  };
136
129
  //# sourceMappingURL=teacher.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"teacher.js","sources":["../../../../src/functions/api/teacher.ts"],"sourcesContent":["import crypto from \"node:crypto\";\n\nimport type { Contest, Participation, Student } from \"@olinfo/quizms/models\";\nimport { randomToken } from \"@olinfo/quizms/utils\";\nimport { addMinutes, addSeconds, isFuture, isPast, roundToNearestMinutes } from \"date-fns\";\nimport { getFunctions } from \"firebase-admin/functions\";\nimport type { CallableRequest } from \"firebase-functions/https\";\nimport * as logger from \"firebase-functions/logger\";\nimport { defineString } from \"firebase-functions/params\";\nimport { chunk } from \"lodash-es\";\n\nimport { auth, db, type Endpoint, type EndpointError } from \"../common\";\nimport {\n getContest,\n getParticipation,\n getParticipationByToken,\n getParticipationStudents,\n getUser,\n} from \"./queries\";\nimport type {\n TeacherFinalizeParticipation,\n TeacherLogin,\n TeacherStartParticipation,\n TeacherStopParticipation,\n} from \"./schema\";\n\nconst region = defineString(\"QUIZMS_REGION\");\n\nexport const teacherLogin: Endpoint<TeacherLogin> = async (_request, data) => {\n logger.info(\"Teacher login request received\", { data });\n\n const user = await getUser(data.username);\n if (!user) {\n return { success: false, errorCode: \"USER_NOT_FOUND\", error: \"Username non esistente\" };\n }\n\n if (user.role !== \"teacher\") {\n return { success: false, errorCode: \"INVALID_ROLE\", error: \"Utenza non abilitata\" };\n }\n if (user.password !== data.password) {\n return { success: false, errorCode: \"INVALID_PASSWORD\", error: \"Password non corretta\" };\n }\n const token = await auth.createCustomToken(crypto.randomUUID(), {\n role: \"teacher\",\n schoolId: user.id,\n });\n return { success: true, data: { token } };\n};\n\nasync function checkParticipation(\n request: CallableRequest<{ participationId: string }>,\n onlineOnly: boolean,\n): Promise<[EndpointError] | [null, Participation, Contest]> {\n const participation = await getParticipation(request.data.participationId);\n if (!participation) {\n return [{ success: false, errorCode: \"PARTICIPATION_NOT_FOUND\", error: \"Gara non valida\" }];\n }\n\n if (participation.schoolId !== request.auth?.token.schoolId) {\n return [\n {\n success: false,\n errorCode: \"FORBIDDEN\",\n error: \"Gara non valida\",\n },\n ];\n }\n\n const contest = await getContest(participation.contestId);\n if (onlineOnly) {\n if (!contest.hasOnline) {\n return [\n {\n success: false,\n errorCode: \"CONTEST_OFFLINE\",\n error: \"Gara non abilitata per prove online\",\n },\n ];\n }\n if (isFuture(contest.contestWindowStart)) {\n return [{ success: false, errorCode: \"CONTEST_NOT_STARTED\", error: \"Gara non iniziata\" }];\n }\n if (isPast(contest.contestWindowEnd)) {\n return [{ success: false, errorCode: \"CONTEST_ENDED\", error: \"Gara già terminata\" }];\n }\n }\n\n return [null, participation, contest];\n}\n\nasync function generateToken() {\n for (;;) {\n const token = await randomToken();\n if (!(await getParticipationByToken(token))) {\n return token;\n }\n }\n}\n\nexport const teacherStartParticipation: Endpoint<TeacherStartParticipation> = async (\n request,\n data,\n) => {\n logger.info(\"Teacher start participation received\", { data });\n\n const [error, participation, contest] = await checkParticipation(request, true);\n if (error) {\n return error;\n }\n\n if (\n participation.endingTime != null &&\n contest.hasOnline &&\n (isFuture(participation.endingTime) || !contest.allowRestart)\n ) {\n return {\n success: false,\n errorCode: \"PARTICIPATION_ALREADY_STARTED\",\n error: \"Gara già iniziata\",\n };\n }\n\n if (participation.token) {\n const students = await getParticipationStudents(participation.id, participation.token);\n await revokeTokens(students);\n }\n\n const startingTime = roundToNearestMinutes(addSeconds(Date.now(), 3.5 * 60));\n const endingTime = addMinutes(startingTime, contest.hasOnline ? contest.duration : 0);\n\n const token = await generateToken();\n\n await db.doc(`participations/${participation.id}`).update({\n token,\n startingTime,\n endingTime,\n });\n\n try {\n const taskQueue = getFunctions().taskQueue(\n `locations/${region.value()}/functions/updateScores`,\n );\n await taskQueue.enqueue(\n { participationId: participation.id, token },\n { scheduleTime: addSeconds(endingTime, 5) },\n );\n } catch (error) {\n logger.error(\"Failed to queue updateScores task\", { error });\n }\n\n return { success: true, data: {} };\n};\n\nexport const teacherStopParticipation: Endpoint<TeacherStopParticipation> = async (\n request,\n data,\n) => {\n logger.info(\"Teacher stop participation received\", { data });\n\n const [error, participation] = await checkParticipation(request, true);\n if (error) {\n return error;\n }\n\n if (participation.startingTime == null) {\n return {\n success: false,\n errorCode: \"PARTICIPATION_NOT_STARTED\",\n error: \"Gara non iniziata\",\n };\n }\n\n if (isPast(participation.startingTime)) {\n return {\n success: false,\n errorCode: \"PARTICIPATION_ALREADY_STARTED\",\n error: \"Gara già iniziata\",\n };\n }\n\n const students = await getParticipationStudents(participation.id, participation.token!);\n await revokeTokens(students);\n\n for (const studentChunk of chunk(students, 300)) {\n const batch = db.batch();\n for (const student of studentChunk) {\n batch.delete(db.doc(`participations/${participation.id}/students/${student.id}`));\n }\n await batch.commit();\n }\n\n await db.doc(`participations/${participation.id}`).update({\n token: null,\n startingTime: null,\n endingTime: null,\n });\n\n return { success: true, data: {} };\n};\n\nasync function revokeTokens(students: Student[]) {\n await Promise.all(\n students.map(async (student) => {\n if (student.uid) {\n await auth.revokeRefreshTokens(student.uid).catch(() => {});\n }\n }),\n );\n}\n\nexport const teacherFinalizeParticipation: Endpoint<TeacherFinalizeParticipation> = async (\n request,\n data,\n) => {\n logger.info(\"Teacher finalize participation received\", { data });\n\n const [error, participation] = await checkParticipation(request, false);\n if (error) {\n return error;\n }\n\n await db.doc(`participations/${participation.id}`).update({ finalized: true });\n\n return { success: true, data: {} };\n};\n"],"names":["region","defineString","teacherLogin","_request","data","logger","user","getUser","auth","crypto","checkParticipation","request","onlineOnly","participation","getParticipation","contest","getContest","isFuture","isPast","generateToken","token","randomToken","getParticipationByToken","teacherStartParticipation","error","students","getParticipationStudents","revokeTokens","startingTime","roundToNearestMinutes","addSeconds","endingTime","addMinutes","db","getFunctions","teacherStopParticipation","studentChunk","chunk","batch","student","teacherFinalizeParticipation"],"mappings":"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"}
1
+ {"version":3,"file":"teacher.js","sources":["../../../../src/functions/api/teacher.ts"],"sourcesContent":["import crypto from \"node:crypto\";\n\nimport type { Contest, Participation, Student } from \"@olinfo/quizms/models\";\nimport { randomToken } from \"@olinfo/quizms/utils\";\nimport { TRPCError } from \"@trpc/server\";\nimport { addMinutes, addSeconds, isFuture, isPast, roundToNearestMinutes } from \"date-fns\";\nimport type { DecodedIdToken } from \"firebase-admin/auth\";\nimport { getFunctions } from \"firebase-admin/functions\";\nimport * as logger from \"firebase-functions/logger\";\nimport { defineString } from \"firebase-functions/params\";\nimport { chunk } from \"lodash-es\";\nimport z from \"zod\";\n\nimport { auth, db } from \"../common\";\nimport {\n getContest,\n getParticipation,\n getParticipationByToken,\n getParticipationStudents,\n getUser,\n} from \"./queries\";\nimport { publicProcedure } from \"./trpc\";\n\nconst region = defineString(\"QUIZMS_REGION\");\n\nexport const teacherLogin = publicProcedure\n .input(\n z.strictObject({\n username: z.string(),\n password: z.string(),\n }),\n )\n .mutation(async (opts) => {\n const { input: data } = opts;\n logger.info(\"Teacher login request received\", { data });\n\n const user = await getUser(data.username);\n if (!user) {\n throw new TRPCError({ code: \"BAD_REQUEST\", message: \"Username non esistente\" });\n }\n\n if (user.password !== data.password) {\n throw new TRPCError({ code: \"BAD_REQUEST\", message: \"Password non corretta\" });\n }\n if (user.role !== \"teacher\") {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Utenza non abilitata\" });\n }\n\n return auth.createCustomToken(crypto.randomUUID(), {\n role: \"teacher\",\n schoolId: user.id,\n });\n });\n\nasync function checkParticipation(\n participationId: string,\n claims: DecodedIdToken | undefined,\n onlineOnly: boolean,\n): Promise<[Participation, Contest]> {\n const participation = await getParticipation(participationId);\n if (!participation) {\n throw new TRPCError({ code: \"BAD_REQUEST\", message: \"Gara non valida\" });\n }\n\n if (participation.schoolId !== claims?.schoolId) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara non valida\" });\n }\n\n const contest = await getContest(participation.contestId);\n if (onlineOnly) {\n if (!contest.hasOnline) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara non abilitata per prove online\" });\n }\n if (isFuture(contest.contestWindowStart)) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara non iniziata\" });\n }\n if (isPast(contest.contestWindowEnd)) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara già terminata\" });\n }\n }\n\n return [participation, contest];\n}\n\nasync function generateToken() {\n for (;;) {\n const token = await randomToken();\n if (!(await getParticipationByToken(token))) {\n return token;\n }\n }\n}\n\nexport const teacherStartParticipation = publicProcedure\n .input(z.strictObject({ participationId: z.string() }))\n .mutation(async (opts) => {\n const { input: data, ctx } = opts;\n logger.info(\"Teacher start participation received\", { data });\n\n const [participation, contest] = await checkParticipation(\n data.participationId,\n ctx.claims,\n true,\n );\n\n if (\n participation.endingTime != null &&\n contest.hasOnline &&\n (isFuture(participation.endingTime) || !contest.allowRestart)\n ) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara già iniziata\" });\n }\n\n if (participation.token) {\n const students = await getParticipationStudents(participation.id, participation.token);\n await revokeTokens(students);\n }\n\n const startingTime = roundToNearestMinutes(addSeconds(Date.now(), 3.5 * 60));\n const endingTime = addMinutes(startingTime, contest.hasOnline ? contest.duration : 0);\n\n const token = await generateToken();\n\n await db.doc(`participations/${participation.id}`).update({\n token,\n startingTime,\n endingTime,\n });\n\n try {\n const taskQueue = getFunctions().taskQueue(\n `locations/${region.value()}/functions/updateScores`,\n );\n await taskQueue.enqueue(\n { participationId: participation.id, token },\n { scheduleTime: addSeconds(endingTime, 5) },\n );\n } catch (error) {\n logger.error(\"Failed to queue updateScores task\", { error });\n }\n });\n\nexport const teacherStopParticipation = publicProcedure\n .input(z.strictObject({ participationId: z.string() }))\n .mutation(async (opts) => {\n const { input: data, ctx } = opts;\n logger.info(\"Teacher stop participation received\", { data });\n\n const [participation] = await checkParticipation(data.participationId, ctx.claims, true);\n\n if (participation.startingTime == null) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara non iniziata\" });\n }\n\n if (isPast(participation.startingTime)) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara già iniziata\" });\n }\n\n const students = await getParticipationStudents(participation.id, participation.token!);\n await revokeTokens(students);\n\n for (const studentChunk of chunk(students, 300)) {\n const batch = db.batch();\n for (const student of studentChunk) {\n batch.delete(db.doc(`participations/${participation.id}/students/${student.id}`));\n }\n await batch.commit();\n }\n\n await db.doc(`participations/${participation.id}`).update({\n token: null,\n startingTime: null,\n endingTime: null,\n });\n });\n\nasync function revokeTokens(students: Student[]) {\n await Promise.all(\n students.map(async (student) => {\n if (student.uid) {\n await auth.revokeRefreshTokens(student.uid).catch(() => {});\n }\n }),\n );\n}\n\nexport const teacherFinalizeParticipation = publicProcedure\n .input(z.strictObject({ participationId: z.string() }))\n .mutation(async (opts) => {\n const { input: data, ctx } = opts;\n logger.info(\"Teacher finalize participation received\", { data });\n\n const [participation] = await checkParticipation(data.participationId, ctx.claims, false);\n\n await db.doc(`participations/${participation.id}`).update({ finalized: true });\n });\n"],"names":["region","defineString","teacherLogin","publicProcedure","z","opts","data","logger","user","getUser","TRPCError","auth","crypto","checkParticipation","participationId","claims","onlineOnly","participation","getParticipation","contest","getContest","isFuture","isPast","generateToken","token","randomToken","getParticipationByToken","teacherStartParticipation","ctx","students","getParticipationStudents","revokeTokens","startingTime","roundToNearestMinutes","addSeconds","endingTime","addMinutes","db","getFunctions","error","teacherStopParticipation","studentChunk","chunk","batch","student","teacherFinalizeParticipation"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import { initTRPC as n } from "@trpc/server";
2
+ import c from "superjson";
3
+ import { auth as a } from "../common.js";
4
+ async function p({ req: o }) {
5
+ const r = o.header("authorization");
6
+ if (r) {
7
+ const e = r.replace("Bearer ", "");
8
+ return { claims: await a.verifyIdToken(e, !0) };
9
+ }
10
+ return {};
11
+ }
12
+ const t = n.context().create({ transformer: c }), f = t.router, d = t.procedure;
13
+ export {
14
+ p as createContext,
15
+ d as publicProcedure,
16
+ f as router
17
+ };
18
+ //# sourceMappingURL=trpc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trpc.js","sources":["../../../../src/functions/api/trpc.ts"],"sourcesContent":["import { initTRPC } from \"@trpc/server\";\nimport type * as trpcExpress from \"@trpc/server/adapters/express\";\nimport superjson from \"superjson\";\n\nimport { auth } from \"~/functions/common\";\n\nexport async function createContext({ req }: trpcExpress.CreateExpressContextOptions) {\n const authorization = req.header(\"authorization\");\n if (authorization) {\n const token = authorization.replace(\"Bearer \", \"\");\n const claims = await auth.verifyIdToken(token, true);\n return { claims };\n }\n return {};\n}\nexport type Context = Awaited<ReturnType<typeof createContext>>;\n\nconst t = initTRPC.context<Context>().create({ transformer: superjson });\nexport const router = t.router;\nexport const publicProcedure = t.procedure;\n"],"names":["createContext","req","authorization","token","auth","initTRPC","superjson","router","publicProcedure"],"mappings":";;;AAMA,eAAsBA,EAAc,EAAE,KAAAC,KAAgD;AACpF,QAAMC,IAAgBD,EAAI,OAAO,eAAe;AAChD,MAAIC,GAAe;AACjB,UAAMC,IAAQD,EAAc,QAAQ,WAAW,EAAE;AAEjD,WAAO,EAAE,QADM,MAAME,EAAK,cAAcD,GAAO,EAAI,EAC1C;AAAA,EACX;AACA,SAAO,CAAA;AACT;AAGA,MAAM,IAAIE,EAAS,QAAA,EAAmB,OAAO,EAAE,aAAaC,GAAW,GAC1DC,IAAS,EAAE,QACXC,IAAkB,EAAE;"}
@@ -1,7 +1,7 @@
1
1
  import { db as d } from "./common.js";
2
2
  async function i(t) {
3
3
  const a = t.data?.after;
4
- a && await d.collection("submissions").add({
4
+ a?.exists && await d.collection("submissions").add({
5
5
  authType: t.authType,
6
6
  authId: t.authId,
7
7
  studentId: a.id,