@olinfo/quizms-firebase 4.1.3 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +14 -14
- package/dist/cli.js.map +1 -1
- package/dist/package.json.js +1 -1
- package/dist/src/cli/definalize.js +17 -17
- package/dist/src/cli/definalize.js.map +1 -1
- package/dist/src/cli/export.js +42 -34
- package/dist/src/cli/export.js.map +1 -1
- package/dist/src/cli/files/firestore-indexes.json.js +1 -1
- package/dist/src/cli/files/firestore.rules.js +27 -27
- package/dist/src/cli/files/firestore.rules.js.map +1 -1
- package/dist/src/cli/files/functions-index.js +3 -3
- package/dist/src/cli/files/functions-index.js.map +1 -1
- package/dist/src/cli/files/storage.rules.js +3 -3
- package/dist/src/cli/files/storage.rules.js.map +1 -1
- package/dist/src/cli/import.js +65 -71
- package/dist/src/cli/import.js.map +1 -1
- package/dist/src/cli/update-scores.js +7 -7
- package/dist/src/cli/update-scores.js.map +1 -1
- package/dist/src/cli/utils/converters-admin.js +23 -22
- package/dist/src/cli/utils/converters-admin.js.map +1 -1
- package/dist/src/functions/api/index.js +18 -18
- package/dist/src/functions/api/index.js.map +1 -1
- package/dist/src/functions/api/queries.js +21 -21
- package/dist/src/functions/api/queries.js.map +1 -1
- package/dist/src/functions/api/student.js +46 -47
- package/dist/src/functions/api/student.js.map +1 -1
- package/dist/src/functions/api/teacher.js +92 -95
- package/dist/src/functions/api/teacher.js.map +1 -1
- package/dist/src/functions/submissions.js +11 -7
- package/dist/src/functions/submissions.js.map +1 -1
- package/dist/src/functions/update-scores.js +14 -14
- package/dist/src/functions/update-scores.js.map +1 -1
- package/dist/src/models/submission.js +13 -0
- package/dist/src/models/submission.js.map +1 -0
- package/dist/src/web/admin/announcements.js +45 -42
- package/dist/src/web/admin/announcements.js.map +1 -1
- package/dist/src/web/admin/dashboard.js +43 -43
- package/dist/src/web/admin/dashboard.js.map +1 -1
- package/dist/src/web/admin/export.js +56 -52
- package/dist/src/web/admin/export.js.map +1 -1
- package/dist/src/web/admin/provider.js +28 -28
- package/dist/src/web/admin/provider.js.map +1 -1
- package/dist/src/web/admin/school-table.js +20 -24
- package/dist/src/web/admin/school-table.js.map +1 -1
- package/dist/src/web/common/api.js +25 -25
- package/dist/src/web/common/api.js.map +1 -1
- package/dist/src/web/common/converters.js +38 -39
- package/dist/src/web/common/converters.js.map +1 -1
- package/dist/src/web/entry/student-statement.js +1 -1
- package/dist/src/web/entry/student-statement.js.map +1 -1
- package/dist/src/web/entry/student.js +80 -75
- package/dist/src/web/entry/student.js.map +1 -1
- package/dist/src/web/entry/teacher.js +63 -62
- package/dist/src/web/entry/teacher.js.map +1 -1
- package/package.json +13 -13
|
@@ -1,75 +1,74 @@
|
|
|
1
|
-
import D, { hash as
|
|
2
|
-
import { studentSchema as
|
|
3
|
-
import { TRPCError as
|
|
4
|
-
import { Timestamp as
|
|
5
|
-
import * as
|
|
6
|
-
import
|
|
7
|
-
import { db as
|
|
8
|
-
import {
|
|
9
|
-
import { publicProcedure as
|
|
10
|
-
function
|
|
11
|
-
return
|
|
1
|
+
import D, { hash as f } from "node:crypto";
|
|
2
|
+
import { studentSchema as c, getNormalizedUserData as v } from "@olinfo/quizms/models";
|
|
3
|
+
import { TRPCError as r } from "@trpc/server";
|
|
4
|
+
import { Timestamp as s } from "firebase-admin/firestore";
|
|
5
|
+
import * as g from "firebase-functions/logger";
|
|
6
|
+
import d from "zod";
|
|
7
|
+
import { db as m, auth as p } from "../common.js";
|
|
8
|
+
import { getVenueByToken as h, getContest as l, getStudentByHash as k, getRandomVariant as C } from "./queries.js";
|
|
9
|
+
import { publicProcedure as R } from "./trpc.js";
|
|
10
|
+
function T(o, t) {
|
|
11
|
+
return f("sha256", v(o, { userData: t }));
|
|
12
12
|
}
|
|
13
|
-
const U =
|
|
14
|
-
|
|
15
|
-
contestId:
|
|
16
|
-
token:
|
|
17
|
-
userData:
|
|
18
|
-
extraData:
|
|
13
|
+
const U = R.input(
|
|
14
|
+
d.strictObject({
|
|
15
|
+
contestId: d.string(),
|
|
16
|
+
token: d.string(),
|
|
17
|
+
userData: c.shape.userData.unwrap().required(),
|
|
18
|
+
extraData: c.shape.extraData.unwrap()
|
|
19
19
|
})
|
|
20
20
|
).mutation(async (o) => {
|
|
21
21
|
const { input: t } = o;
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
if (!
|
|
25
|
-
throw new
|
|
26
|
-
if (
|
|
27
|
-
throw new
|
|
28
|
-
const
|
|
29
|
-
if (
|
|
30
|
-
if (
|
|
31
|
-
throw new
|
|
22
|
+
g.info("Student login request received", { data: t });
|
|
23
|
+
const e = await h(t.token);
|
|
24
|
+
if (!e)
|
|
25
|
+
throw new r({ code: "BAD_REQUEST", message: "Codice prova non valido" });
|
|
26
|
+
if (e.contestId !== t.contestId)
|
|
27
|
+
throw new r({ code: "FORBIDDEN", message: "Codice prova non valido" });
|
|
28
|
+
const I = await l(e.contestId), n = D.randomUUID(), i = T(I, t.userData), a = await k(e.id, i);
|
|
29
|
+
if (a) {
|
|
30
|
+
if (a.token !== t.token)
|
|
31
|
+
throw new r({
|
|
32
32
|
code: "FORBIDDEN",
|
|
33
33
|
message: "Hai già partecipato a questa gara"
|
|
34
34
|
});
|
|
35
|
-
return await
|
|
36
|
-
studentId:
|
|
37
|
-
|
|
35
|
+
return await m.doc(`studentRestores/${n}`).set({
|
|
36
|
+
studentId: a.id,
|
|
37
|
+
venueId: a.venueId,
|
|
38
38
|
token: t.token,
|
|
39
39
|
name: t.userData.name,
|
|
40
40
|
surname: t.userData.surname,
|
|
41
41
|
approvalCode: Math.round(Math.random() * 1e3),
|
|
42
42
|
status: "pending",
|
|
43
|
-
createdAt:
|
|
44
|
-
}),
|
|
43
|
+
createdAt: s.now()
|
|
44
|
+
}), p.createCustomToken(n, {
|
|
45
45
|
role: "student",
|
|
46
|
-
studentId:
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
studentId: a.id,
|
|
47
|
+
venueId: a.venueId,
|
|
48
|
+
variantId: a.variantId
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
-
const
|
|
51
|
+
const u = await C(t.contestId), w = await m.collection(`venues/${e.id}/students`).add({
|
|
52
52
|
uid: n,
|
|
53
53
|
userData: t.userData,
|
|
54
|
-
userDataHash:
|
|
54
|
+
userDataHash: i,
|
|
55
55
|
absent: !1,
|
|
56
56
|
disabled: !1,
|
|
57
|
-
|
|
57
|
+
venueId: e.id,
|
|
58
58
|
contestId: t.contestId,
|
|
59
59
|
token: t.token,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
variant: c.id,
|
|
60
|
+
participationWindow: e.participationWindow,
|
|
61
|
+
variant: u.id,
|
|
63
62
|
answers: {},
|
|
64
63
|
extraData: t.extraData,
|
|
65
|
-
createdAt:
|
|
66
|
-
updatedAt:
|
|
64
|
+
createdAt: s.now(),
|
|
65
|
+
updatedAt: s.now()
|
|
67
66
|
});
|
|
68
|
-
return
|
|
67
|
+
return p.createCustomToken(n, {
|
|
69
68
|
role: "student",
|
|
70
|
-
studentId:
|
|
71
|
-
|
|
72
|
-
variant:
|
|
69
|
+
studentId: w.id,
|
|
70
|
+
venueId: e.id,
|
|
71
|
+
variant: u
|
|
73
72
|
});
|
|
74
73
|
});
|
|
75
74
|
export {
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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, getRandomVariant, getStudentByHash, getVenueByToken } 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 venue = await getVenueByToken(data.token);\n if (!venue) {\n throw new TRPCError({ code: \"BAD_REQUEST\", message: \"Codice prova non valido\" });\n }\n if (venue.contestId !== data.contestId) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Codice prova non valido\" });\n }\n\n const contest = await getContest(venue.contestId);\n\n const uid = crypto.randomUUID();\n const userDataHash = studentHash(contest, data.userData);\n\n const duplicated = await getStudentByHash(venue.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 venueId: duplicated.venueId,\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 venueId: duplicated.venueId,\n variantId: duplicated.variantId,\n });\n }\n\n const variant = await getRandomVariant(data.contestId);\n\n const studentRef = await db.collection(`venues/${venue.id}/students`).add({\n uid,\n userData: data.userData,\n userDataHash,\n absent: false,\n disabled: false,\n venueId: venue.id,\n contestId: data.contestId,\n token: data.token,\n participationWindow: venue.participationWindow,\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 venueId: venue.id,\n variant,\n });\n });\n"],"names":["studentHash","contest","userData","hash","getNormalizedUserData","studentLogin","publicProcedure","z","studentSchema","opts","data","logger","venue","getVenueByToken","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,IAAQ,MAAMC,EAAgBH,EAAK,KAAK;AAC9C,MAAI,CAACE;AACH,UAAM,IAAIE,EAAU,EAAE,MAAM,eAAe,SAAS,2BAA2B;AAEjF,MAAIF,EAAM,cAAcF,EAAK;AAC3B,UAAM,IAAII,EAAU,EAAE,MAAM,aAAa,SAAS,2BAA2B;AAG/E,QAAMb,IAAU,MAAMc,EAAWH,EAAM,SAAS,GAE1CI,IAAMC,EAAO,WAAA,GACbC,IAAelB,EAAYC,GAASS,EAAK,QAAQ,GAEjDS,IAAa,MAAMC,EAAiBR,EAAM,IAAIM,CAAY;AAChE,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,SAASA,EAAW;AAAA,MACpB,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,SAASA,EAAW;AAAA,MACpB,WAAWA,EAAW;AAAA,IAAA,CACvB;AAAA,EACH;AAEA,QAAMK,IAAU,MAAMC,EAAiBf,EAAK,SAAS,GAE/CgB,IAAa,MAAML,EAAG,WAAW,UAAUT,EAAM,EAAE,WAAW,EAAE,IAAI;AAAA,IACxE,KAAAI;AAAA,IACA,UAAUN,EAAK;AAAA,IACf,cAAAQ;AAAA,IACA,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,SAASN,EAAM;AAAA,IACf,WAAWF,EAAK;AAAA,IAChB,OAAOA,EAAK;AAAA,IACZ,qBAAqBE,EAAM;AAAA,IAC3B,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,SAASd,EAAM;AAAA,IACf,SAAAY;AAAA,EAAA,CACD;AACH,CAAC;"}
|
|
@@ -1,129 +1,126 @@
|
|
|
1
1
|
import D from "node:crypto";
|
|
2
|
-
import { randomToken as
|
|
3
|
-
import { TRPCError as
|
|
4
|
-
import { isFuture as g, roundToNearestMinutes as
|
|
5
|
-
import { getFunctions as
|
|
2
|
+
import { randomToken as T } from "@olinfo/quizms/utils";
|
|
3
|
+
import { TRPCError as a } from "@trpc/server";
|
|
4
|
+
import { isFuture as g, roundToNearestMinutes as S, addSeconds as l, addMinutes as E, isPast as h } from "date-fns";
|
|
5
|
+
import { getFunctions as R } from "firebase-admin/functions";
|
|
6
6
|
import * as d from "firebase-functions/logger";
|
|
7
|
-
import { defineString as
|
|
8
|
-
import { chunk as
|
|
9
|
-
import
|
|
10
|
-
import { auth as
|
|
11
|
-
import { getUser as
|
|
12
|
-
import { publicProcedure as
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
username:
|
|
16
|
-
password:
|
|
7
|
+
import { defineString as O } from "firebase-functions/params";
|
|
8
|
+
import { chunk as B } from "lodash-es";
|
|
9
|
+
import s from "zod";
|
|
10
|
+
import { auth as v, db as c } from "../common.js";
|
|
11
|
+
import { getUser as F, getVenueStudents as I, getVenue as y, getContest as b, getVenueByToken as N } from "./queries.js";
|
|
12
|
+
import { publicProcedure as p } from "./trpc.js";
|
|
13
|
+
const W = O("QUIZMS_REGION"), A = p.input(
|
|
14
|
+
s.strictObject({
|
|
15
|
+
username: s.string(),
|
|
16
|
+
password: s.string()
|
|
17
17
|
})
|
|
18
|
-
).mutation(async (
|
|
19
|
-
const { input:
|
|
20
|
-
d.info("Teacher login request received", { data:
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
23
|
-
throw new
|
|
24
|
-
if (
|
|
25
|
-
throw new
|
|
26
|
-
if (
|
|
27
|
-
throw new
|
|
28
|
-
return
|
|
18
|
+
).mutation(async (n) => {
|
|
19
|
+
const { input: t } = n;
|
|
20
|
+
d.info("Teacher login request received", { data: t });
|
|
21
|
+
const o = await F(t.username);
|
|
22
|
+
if (!o)
|
|
23
|
+
throw new a({ code: "BAD_REQUEST", message: "Username non esistente" });
|
|
24
|
+
if (o.password !== t.password)
|
|
25
|
+
throw new a({ code: "BAD_REQUEST", message: "Password non corretta" });
|
|
26
|
+
if (o.role !== "teacher")
|
|
27
|
+
throw new a({ code: "FORBIDDEN", message: "Utenza non abilitata" });
|
|
28
|
+
return v.createCustomToken(D.randomUUID(), {
|
|
29
29
|
role: "teacher",
|
|
30
|
-
schoolId:
|
|
30
|
+
schoolId: o.id
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
|
-
async function f(
|
|
34
|
-
const
|
|
35
|
-
if (!
|
|
36
|
-
throw new
|
|
37
|
-
if (
|
|
38
|
-
throw new
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
if (!
|
|
42
|
-
throw new
|
|
43
|
-
if (g(
|
|
44
|
-
throw new
|
|
45
|
-
if (h(
|
|
46
|
-
throw new
|
|
33
|
+
async function f(n, t, o) {
|
|
34
|
+
const e = await y(n);
|
|
35
|
+
if (!e)
|
|
36
|
+
throw new a({ code: "BAD_REQUEST", message: "Gara non valida" });
|
|
37
|
+
if (e.schoolId !== t?.schoolId)
|
|
38
|
+
throw new a({ code: "FORBIDDEN", message: "Gara non valida" });
|
|
39
|
+
const i = await b(e.contestId);
|
|
40
|
+
if (o) {
|
|
41
|
+
if (!i.onlineSettings)
|
|
42
|
+
throw new a({ code: "FORBIDDEN", message: "Gara non abilitata per prove online" });
|
|
43
|
+
if (g(i.onlineSettings.contestWindow.start))
|
|
44
|
+
throw new a({ code: "FORBIDDEN", message: "Gara non iniziata" });
|
|
45
|
+
if (h(i.onlineSettings.contestWindow.end))
|
|
46
|
+
throw new a({ code: "FORBIDDEN", message: "Gara già terminata" });
|
|
47
47
|
}
|
|
48
|
-
return [
|
|
48
|
+
return [e, i];
|
|
49
49
|
}
|
|
50
50
|
async function z() {
|
|
51
51
|
for (; ; ) {
|
|
52
|
-
const
|
|
53
|
-
if (!await
|
|
54
|
-
return
|
|
52
|
+
const n = await T();
|
|
53
|
+
if (!await N(n))
|
|
54
|
+
return n;
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
const
|
|
58
|
-
const { input:
|
|
59
|
-
d.info("Teacher start
|
|
60
|
-
const [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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);
|
|
57
|
+
const M = p.input(s.strictObject({ venueId: s.string() })).mutation(async (n) => {
|
|
58
|
+
const { input: t, ctx: o } = n;
|
|
59
|
+
d.info("Teacher start venue received", { data: t });
|
|
60
|
+
const [e, i] = await f(t.venueId, o.claims, !0);
|
|
61
|
+
if (e.participationWindow && i.onlineSettings && (g(e.participationWindow.end) || !i.onlineSettings.allowRestart))
|
|
62
|
+
throw new a({ code: "FORBIDDEN", message: "Gara già iniziata" });
|
|
63
|
+
if (e.token) {
|
|
64
|
+
const m = await I(e.id, e.token);
|
|
69
65
|
await k(m);
|
|
70
66
|
}
|
|
71
|
-
const
|
|
72
|
-
await c.doc(`
|
|
73
|
-
token:
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
const u = S(l(Date.now(), 3.5 * 60)), r = E(u, i.onlineSettings?.duration ?? 0), w = await z();
|
|
68
|
+
await c.doc(`venues/${e.id}`).update({
|
|
69
|
+
token: w,
|
|
70
|
+
participationWindow: {
|
|
71
|
+
start: u,
|
|
72
|
+
end: r
|
|
73
|
+
}
|
|
76
74
|
});
|
|
77
75
|
try {
|
|
78
|
-
await
|
|
79
|
-
`locations/${
|
|
76
|
+
await R().taskQueue(
|
|
77
|
+
`locations/${W.value()}/functions/updateScores`
|
|
80
78
|
).enqueue(
|
|
81
|
-
{
|
|
82
|
-
{ scheduleTime: l(
|
|
79
|
+
{ venueId: e.id, token: w },
|
|
80
|
+
{ scheduleTime: l(r, 5) }
|
|
83
81
|
);
|
|
84
82
|
} catch (m) {
|
|
85
83
|
d.error("Failed to queue updateScores task", { error: m });
|
|
86
84
|
}
|
|
87
|
-
}),
|
|
88
|
-
const { input:
|
|
89
|
-
d.info("Teacher stop
|
|
90
|
-
const [
|
|
91
|
-
if (
|
|
92
|
-
throw new
|
|
93
|
-
if (h(
|
|
94
|
-
throw new
|
|
95
|
-
const
|
|
96
|
-
await k(
|
|
97
|
-
for (const
|
|
98
|
-
const
|
|
99
|
-
for (const
|
|
100
|
-
|
|
101
|
-
await
|
|
85
|
+
}), L = p.input(s.strictObject({ venueId: s.string() })).mutation(async (n) => {
|
|
86
|
+
const { input: t, ctx: o } = n;
|
|
87
|
+
d.info("Teacher stop venue received", { data: t });
|
|
88
|
+
const [e] = await f(t.venueId, o.claims, !0);
|
|
89
|
+
if (e.participationWindow == null)
|
|
90
|
+
throw new a({ code: "FORBIDDEN", message: "Gara non iniziata" });
|
|
91
|
+
if (h(e.participationWindow.start))
|
|
92
|
+
throw new a({ code: "FORBIDDEN", message: "Gara già iniziata" });
|
|
93
|
+
const i = await I(e.id, e.token);
|
|
94
|
+
await k(i);
|
|
95
|
+
for (const u of B(i, 300)) {
|
|
96
|
+
const r = c.batch();
|
|
97
|
+
for (const w of u)
|
|
98
|
+
r.delete(c.doc(`venues/${e.id}/students/${w.id}`));
|
|
99
|
+
await r.commit();
|
|
102
100
|
}
|
|
103
|
-
await c.doc(`
|
|
101
|
+
await c.doc(`venues/${e.id}`).update({
|
|
104
102
|
token: null,
|
|
105
|
-
|
|
106
|
-
endingTime: null
|
|
103
|
+
participationWindow: null
|
|
107
104
|
});
|
|
108
105
|
});
|
|
109
|
-
async function k(
|
|
106
|
+
async function k(n) {
|
|
110
107
|
await Promise.all(
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
n.map(async (t) => {
|
|
109
|
+
t.uid && await v.revokeRefreshTokens(t.uid).catch(() => {
|
|
113
110
|
});
|
|
114
111
|
})
|
|
115
112
|
);
|
|
116
113
|
}
|
|
117
|
-
const
|
|
118
|
-
const { input:
|
|
119
|
-
d.info("Teacher finalize
|
|
120
|
-
const [
|
|
121
|
-
await c.doc(`
|
|
114
|
+
const Z = p.input(s.strictObject({ venueId: s.string() })).mutation(async (n) => {
|
|
115
|
+
const { input: t, ctx: o } = n;
|
|
116
|
+
d.info("Teacher finalize venue received", { data: t });
|
|
117
|
+
const [e] = await f(t.venueId, o.claims, !1);
|
|
118
|
+
await c.doc(`venues/${e.id}`).update({ finalized: !0 });
|
|
122
119
|
});
|
|
123
120
|
export {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
Z as teacherFinalizeVenue,
|
|
122
|
+
A as teacherLogin,
|
|
123
|
+
M as teacherStartContestWindow,
|
|
124
|
+
L as teacherStopContestWindow
|
|
128
125
|
};
|
|
129
126
|
//# 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 { 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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"}
|
|
1
|
+
{"version":3,"file":"teacher.js","sources":["../../../../src/functions/api/teacher.ts"],"sourcesContent":["import crypto from \"node:crypto\";\n\nimport type { Contest, Student, Venue } 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 { getContest, getUser, getVenue, getVenueByToken, getVenueStudents } 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 checkVenue(\n venueId: string,\n claims: DecodedIdToken | undefined,\n onlineOnly: boolean,\n): Promise<[Venue, Contest]> {\n const venue = await getVenue(venueId);\n if (!venue) {\n throw new TRPCError({ code: \"BAD_REQUEST\", message: \"Gara non valida\" });\n }\n\n if (venue.schoolId !== claims?.schoolId) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara non valida\" });\n }\n\n const contest = await getContest(venue.contestId);\n if (onlineOnly) {\n if (!contest.onlineSettings) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara non abilitata per prove online\" });\n }\n if (isFuture(contest.onlineSettings.contestWindow.start)) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara non iniziata\" });\n }\n if (isPast(contest.onlineSettings.contestWindow.end)) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara già terminata\" });\n }\n }\n\n return [venue, contest];\n}\n\nasync function generateToken() {\n for (;;) {\n const token = await randomToken();\n if (!(await getVenueByToken(token))) {\n return token;\n }\n }\n}\n\nexport const teacherStartContestWindow = publicProcedure\n .input(z.strictObject({ venueId: z.string() }))\n .mutation(async (opts) => {\n const { input: data, ctx } = opts;\n logger.info(\"Teacher start venue received\", { data });\n\n const [venue, contest] = await checkVenue(data.venueId, ctx.claims, true);\n\n if (\n venue.participationWindow &&\n contest.onlineSettings &&\n (isFuture(venue.participationWindow.end) || !contest.onlineSettings.allowRestart)\n ) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara già iniziata\" });\n }\n\n if (venue.token) {\n const students = await getVenueStudents(venue.id, venue.token);\n await revokeTokens(students);\n }\n\n const startingTime = roundToNearestMinutes(addSeconds(Date.now(), 3.5 * 60));\n const endingTime = addMinutes(startingTime, contest.onlineSettings?.duration ?? 0);\n\n const token = await generateToken();\n\n await db.doc(`venues/${venue.id}`).update({\n token,\n participationWindow: {\n start: startingTime,\n end: endingTime,\n },\n });\n\n try {\n const taskQueue = getFunctions().taskQueue(\n `locations/${region.value()}/functions/updateScores`,\n );\n await taskQueue.enqueue(\n { venueId: venue.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 teacherStopContestWindow = publicProcedure\n .input(z.strictObject({ venueId: z.string() }))\n .mutation(async (opts) => {\n const { input: data, ctx } = opts;\n logger.info(\"Teacher stop venue received\", { data });\n\n const [venue] = await checkVenue(data.venueId, ctx.claims, true);\n\n if (venue.participationWindow == null) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara non iniziata\" });\n }\n\n if (isPast(venue.participationWindow.start)) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Gara già iniziata\" });\n }\n\n const students = await getVenueStudents(venue.id, venue.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(`venues/${venue.id}/students/${student.id}`));\n }\n await batch.commit();\n }\n\n await db.doc(`venues/${venue.id}`).update({\n token: null,\n participationWindow: 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 teacherFinalizeVenue = publicProcedure\n .input(z.strictObject({ venueId: z.string() }))\n .mutation(async (opts) => {\n const { input: data, ctx } = opts;\n logger.info(\"Teacher finalize venue received\", { data });\n\n const [venue] = await checkVenue(data.venueId, ctx.claims, false);\n\n await db.doc(`venues/${venue.id}`).update({ finalized: true });\n });\n"],"names":["region","defineString","teacherLogin","publicProcedure","z","opts","data","logger","user","getUser","TRPCError","auth","crypto","checkVenue","venueId","claims","onlineOnly","venue","getVenue","contest","getContest","isFuture","isPast","generateToken","token","randomToken","getVenueByToken","teacherStartContestWindow","ctx","students","getVenueStudents","revokeTokens","startingTime","roundToNearestMinutes","addSeconds","endingTime","addMinutes","db","getFunctions","error","teacherStopContestWindow","studentChunk","chunk","batch","student","teacherFinalizeVenue"],"mappings":";;;;;;;;;;;;AAiBA,MAAMA,IAASC,EAAa,eAAe,GAE9BC,IAAeC,EACzB;AAAA,EACCC,EAAE,aAAa;AAAA,IACb,UAAUA,EAAE,OAAA;AAAA,IACZ,UAAUA,EAAE,OAAA;AAAA,EAAO,CACpB;AACH,EACC,SAAS,OAAOC,MAAS;AACxB,QAAM,EAAE,OAAOC,EAAA,IAASD;AACxB,EAAAE,EAAO,KAAK,kCAAkC,EAAE,MAAAD,EAAA,CAAM;AAEtD,QAAME,IAAO,MAAMC,EAAQH,EAAK,QAAQ;AACxC,MAAI,CAACE;AACH,UAAM,IAAIE,EAAU,EAAE,MAAM,eAAe,SAAS,0BAA0B;AAGhF,MAAIF,EAAK,aAAaF,EAAK;AACzB,UAAM,IAAII,EAAU,EAAE,MAAM,eAAe,SAAS,yBAAyB;AAE/E,MAAIF,EAAK,SAAS;AAChB,UAAM,IAAIE,EAAU,EAAE,MAAM,aAAa,SAAS,wBAAwB;AAG5E,SAAOC,EAAK,kBAAkBC,EAAO,WAAA,GAAc;AAAA,IACjD,MAAM;AAAA,IACN,UAAUJ,EAAK;AAAA,EAAA,CAChB;AACH,CAAC;AAEH,eAAeK,EACbC,GACAC,GACAC,GAC2B;AAC3B,QAAMC,IAAQ,MAAMC,EAASJ,CAAO;AACpC,MAAI,CAACG;AACH,UAAM,IAAIP,EAAU,EAAE,MAAM,eAAe,SAAS,mBAAmB;AAGzE,MAAIO,EAAM,aAAaF,GAAQ;AAC7B,UAAM,IAAIL,EAAU,EAAE,MAAM,aAAa,SAAS,mBAAmB;AAGvE,QAAMS,IAAU,MAAMC,EAAWH,EAAM,SAAS;AAChD,MAAID,GAAY;AACd,QAAI,CAACG,EAAQ;AACX,YAAM,IAAIT,EAAU,EAAE,MAAM,aAAa,SAAS,uCAAuC;AAE3F,QAAIW,EAASF,EAAQ,eAAe,cAAc,KAAK;AACrD,YAAM,IAAIT,EAAU,EAAE,MAAM,aAAa,SAAS,qBAAqB;AAEzE,QAAIY,EAAOH,EAAQ,eAAe,cAAc,GAAG;AACjD,YAAM,IAAIT,EAAU,EAAE,MAAM,aAAa,SAAS,sBAAsB;AAAA,EAE5E;AAEA,SAAO,CAACO,GAAOE,CAAO;AACxB;AAEA,eAAeI,IAAgB;AAC7B,aAAS;AACP,UAAMC,IAAQ,MAAMC,EAAA;AACpB,QAAI,CAAE,MAAMC,EAAgBF,CAAK;AAC/B,aAAOA;AAAA,EAEX;AACF;AAEO,MAAMG,IAA4BxB,EACtC,MAAMC,EAAE,aAAa,EAAE,SAASA,EAAE,OAAA,GAAU,CAAC,EAC7C,SAAS,OAAOC,MAAS;AACxB,QAAM,EAAE,OAAOC,GAAM,KAAAsB,EAAA,IAAQvB;AAC7B,EAAAE,EAAO,KAAK,gCAAgC,EAAE,MAAAD,EAAA,CAAM;AAEpD,QAAM,CAACW,GAAOE,CAAO,IAAI,MAAMN,EAAWP,EAAK,SAASsB,EAAI,QAAQ,EAAI;AAExE,MACEX,EAAM,uBACNE,EAAQ,mBACPE,EAASJ,EAAM,oBAAoB,GAAG,KAAK,CAACE,EAAQ,eAAe;AAEpE,UAAM,IAAIT,EAAU,EAAE,MAAM,aAAa,SAAS,qBAAqB;AAGzE,MAAIO,EAAM,OAAO;AACf,UAAMY,IAAW,MAAMC,EAAiBb,EAAM,IAAIA,EAAM,KAAK;AAC7D,UAAMc,EAAaF,CAAQ;AAAA,EAC7B;AAEA,QAAMG,IAAeC,EAAsBC,EAAW,KAAK,OAAO,MAAM,EAAE,CAAC,GACrEC,IAAaC,EAAWJ,GAAcb,EAAQ,gBAAgB,YAAY,CAAC,GAE3EK,IAAQ,MAAMD,EAAA;AAEpB,QAAMc,EAAG,IAAI,UAAUpB,EAAM,EAAE,EAAE,EAAE,OAAO;AAAA,IACxC,OAAAO;AAAA,IACA,qBAAqB;AAAA,MACnB,OAAOQ;AAAA,MACP,KAAKG;AAAA,IAAA;AAAA,EACP,CACD;AAED,MAAI;AAIF,UAHkBG,IAAe;AAAA,MAC/B,aAAatC,EAAO,MAAA,CAAO;AAAA,IAAA,EAEb;AAAA,MACd,EAAE,SAASiB,EAAM,IAAI,OAAAO,EAAA;AAAA,MACrB,EAAE,cAAcU,EAAWC,GAAY,CAAC,EAAA;AAAA,IAAE;AAAA,EAE9C,SAASI,GAAO;AACd,IAAAhC,EAAO,MAAM,qCAAqC,EAAE,OAAAgC,EAAA,CAAO;AAAA,EAC7D;AACF,CAAC,GAEUC,IAA2BrC,EACrC,MAAMC,EAAE,aAAa,EAAE,SAASA,EAAE,OAAA,GAAU,CAAC,EAC7C,SAAS,OAAOC,MAAS;AACxB,QAAM,EAAE,OAAOC,GAAM,KAAAsB,EAAA,IAAQvB;AAC7B,EAAAE,EAAO,KAAK,+BAA+B,EAAE,MAAAD,EAAA,CAAM;AAEnD,QAAM,CAACW,CAAK,IAAI,MAAMJ,EAAWP,EAAK,SAASsB,EAAI,QAAQ,EAAI;AAE/D,MAAIX,EAAM,uBAAuB;AAC/B,UAAM,IAAIP,EAAU,EAAE,MAAM,aAAa,SAAS,qBAAqB;AAGzE,MAAIY,EAAOL,EAAM,oBAAoB,KAAK;AACxC,UAAM,IAAIP,EAAU,EAAE,MAAM,aAAa,SAAS,qBAAqB;AAGzE,QAAMmB,IAAW,MAAMC,EAAiBb,EAAM,IAAIA,EAAM,KAAM;AAC9D,QAAMc,EAAaF,CAAQ;AAE3B,aAAWY,KAAgBC,EAAMb,GAAU,GAAG,GAAG;AAC/C,UAAMc,IAAQN,EAAG,MAAA;AACjB,eAAWO,KAAWH;AACpB,MAAAE,EAAM,OAAON,EAAG,IAAI,UAAUpB,EAAM,EAAE,aAAa2B,EAAQ,EAAE,EAAE,CAAC;AAElE,UAAMD,EAAM,OAAA;AAAA,EACd;AAEA,QAAMN,EAAG,IAAI,UAAUpB,EAAM,EAAE,EAAE,EAAE,OAAO;AAAA,IACxC,OAAO;AAAA,IACP,qBAAqB;AAAA,EAAA,CACtB;AACH,CAAC;AAEH,eAAec,EAAaF,GAAqB;AAC/C,QAAM,QAAQ;AAAA,IACZA,EAAS,IAAI,OAAOe,MAAY;AAC9B,MAAIA,EAAQ,OACV,MAAMjC,EAAK,oBAAoBiC,EAAQ,GAAG,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAE9D,CAAC;AAAA,EAAA;AAEL;AAEO,MAAMC,IAAuB1C,EACjC,MAAMC,EAAE,aAAa,EAAE,SAASA,EAAE,OAAA,GAAU,CAAC,EAC7C,SAAS,OAAOC,MAAS;AACxB,QAAM,EAAE,OAAOC,GAAM,KAAAsB,EAAA,IAAQvB;AAC7B,EAAAE,EAAO,KAAK,mCAAmC,EAAE,MAAAD,EAAA,CAAM;AAEvD,QAAM,CAACW,CAAK,IAAI,MAAMJ,EAAWP,EAAK,SAASsB,EAAI,QAAQ,EAAK;AAEhE,QAAMS,EAAG,IAAI,UAAUpB,EAAM,EAAE,EAAE,EAAE,OAAO,EAAE,WAAW,GAAA,CAAM;AAC/D,CAAC;"}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { studentSchema as s, calcScore as i } from "@olinfo/quizms/models";
|
|
2
|
+
import { parseSnapshot as n, variantConverter as d, submissionConverter as c } from "../cli/utils/converters-admin.js";
|
|
3
|
+
import { db as e } from "./common.js";
|
|
4
|
+
async function p(t) {
|
|
5
|
+
const r = t.data?.after;
|
|
6
|
+
if (!r?.exists) return;
|
|
7
|
+
const a = n(s, r), o = await e.doc(`variants/${a.variantId}`).withConverter(d).get();
|
|
8
|
+
a.score = i(a, o.data()?.schema), await e.collection("submissions").withConverter(c).add({
|
|
5
9
|
authType: t.authType,
|
|
6
10
|
authId: t.authId,
|
|
7
|
-
studentId:
|
|
8
|
-
student: a
|
|
11
|
+
studentId: r.id,
|
|
12
|
+
student: a,
|
|
9
13
|
submittedAt: t.time
|
|
10
14
|
});
|
|
11
15
|
}
|
|
12
16
|
export {
|
|
13
|
-
|
|
17
|
+
p as studentUpdatedHandler
|
|
14
18
|
};
|
|
15
19
|
//# sourceMappingURL=submissions.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submissions.js","sources":["../../../src/functions/submissions.ts"],"sourcesContent":["import type { DocumentSnapshot } from \"firebase-admin/firestore\";\nimport type { Change, FirestoreAuthEvent } from \"firebase-functions/firestore\";\n\nimport { db } from \"./common\";\n\nexport async function studentUpdatedHandler(\n event: FirestoreAuthEvent<Change<DocumentSnapshot> | undefined>,\n) {\n const data = event.data?.after;\n if (!data?.exists) return;\n\n await db.collection(\"submissions\").add({\n authType: event.authType,\n authId: event.authId,\n studentId: data.id,\n student
|
|
1
|
+
{"version":3,"file":"submissions.js","sources":["../../../src/functions/submissions.ts"],"sourcesContent":["import { calcScore, studentSchema } from \"@olinfo/quizms/models\";\nimport type { DocumentSnapshot } from \"firebase-admin/firestore\";\nimport type { Change, FirestoreAuthEvent } from \"firebase-functions/firestore\";\n\nimport { parseSnapshot, submissionConverter, variantConverter } from \"~/cli/utils/converters-admin\";\n\nimport { db } from \"./common\";\n\nexport async function studentUpdatedHandler(\n event: FirestoreAuthEvent<Change<DocumentSnapshot> | undefined>,\n) {\n const data = event.data?.after;\n if (!data?.exists) return;\n\n const student = parseSnapshot(studentSchema, data);\n\n const variant = await db\n .doc(`variants/${student.variantId}`)\n .withConverter(variantConverter)\n .get();\n student.score = calcScore(student, variant.data()?.schema);\n\n await db.collection(\"submissions\").withConverter(submissionConverter).add({\n authType: event.authType,\n authId: event.authId,\n studentId: data.id,\n student,\n submittedAt: event.time,\n });\n}\n"],"names":["studentUpdatedHandler","event","data","student","parseSnapshot","studentSchema","variant","db","variantConverter","calcScore","submissionConverter"],"mappings":";;;AAQA,eAAsBA,EACpBC,GACA;AACA,QAAMC,IAAOD,EAAM,MAAM;AACzB,MAAI,CAACC,GAAM,OAAQ;AAEnB,QAAMC,IAAUC,EAAcC,GAAeH,CAAI,GAE3CI,IAAU,MAAMC,EACnB,IAAI,YAAYJ,EAAQ,SAAS,EAAE,EACnC,cAAcK,CAAgB,EAC9B,IAAA;AACH,EAAAL,EAAQ,QAAQM,EAAUN,GAASG,EAAQ,KAAA,GAAQ,MAAM,GAEzD,MAAMC,EAAG,WAAW,aAAa,EAAE,cAAcG,CAAmB,EAAE,IAAI;AAAA,IACxE,UAAUT,EAAM;AAAA,IAChB,QAAQA,EAAM;AAAA,IACd,WAAWC,EAAK;AAAA,IAChB,SAAAC;AAAA,IACA,aAAaF,EAAM;AAAA,EAAA,CACpB;AACH;"}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { pipeline as u } from "node:stream/promises";
|
|
2
|
-
import { calcScore as
|
|
3
|
-
import { variantConverter as
|
|
4
|
-
import { db as
|
|
5
|
-
async function
|
|
6
|
-
const {
|
|
7
|
-
await u(
|
|
8
|
-
for await (const
|
|
9
|
-
const
|
|
10
|
-
await
|
|
11
|
-
const
|
|
12
|
-
if (!
|
|
13
|
-
const
|
|
14
|
-
|
|
2
|
+
import { calcScore as m } from "@olinfo/quizms/models";
|
|
3
|
+
import { variantConverter as w, studentConverter as l } from "../cli/utils/converters-admin.js";
|
|
4
|
+
import { db as a } from "./common.js";
|
|
5
|
+
async function k(s) {
|
|
6
|
+
const { venueId: i, token: c } = s.data, d = await a.collection("variants").withConverter(w).get(), f = Object.fromEntries(d.docs.map((t) => [t.id, t.data()])), p = a.collection(`venues/${i}/students`).where("token", "==", c).withConverter(l);
|
|
7
|
+
await u(p.stream(), async function* (t) {
|
|
8
|
+
for await (const v of t) {
|
|
9
|
+
const e = v.ref;
|
|
10
|
+
await a.runTransaction(async (o) => {
|
|
11
|
+
const n = (await o.get(e)).data();
|
|
12
|
+
if (!n?.variantId) return;
|
|
13
|
+
const r = m(n, f[n.variantId]?.schema);
|
|
14
|
+
r !== n.score && o.update(e, { score: r });
|
|
15
15
|
});
|
|
16
16
|
}
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
export {
|
|
20
|
-
|
|
20
|
+
k as updateScoresHandler
|
|
21
21
|
};
|
|
22
22
|
//# sourceMappingURL=update-scores.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-scores.js","sources":["../../../src/functions/update-scores.ts"],"sourcesContent":["import { pipeline } from \"node:stream/promises\";\n\nimport { calcScore, type Student } from \"@olinfo/quizms/models\";\nimport type { QueryDocumentSnapshot } from \"firebase-admin/firestore\";\nimport type { Request } from \"firebase-functions/tasks\";\n\nimport { studentConverter, variantConverter } from \"~/cli/utils/converters-admin\";\n\nimport { db } from \"./common\";\n\nexport async function updateScoresHandler(request: Request) {\n const {
|
|
1
|
+
{"version":3,"file":"update-scores.js","sources":["../../../src/functions/update-scores.ts"],"sourcesContent":["import { pipeline } from \"node:stream/promises\";\n\nimport { calcScore, type Student } from \"@olinfo/quizms/models\";\nimport type { QueryDocumentSnapshot } from \"firebase-admin/firestore\";\nimport type { Request } from \"firebase-functions/tasks\";\n\nimport { studentConverter, variantConverter } from \"~/cli/utils/converters-admin\";\n\nimport { db } from \"./common\";\n\nexport async function updateScoresHandler(request: Request) {\n const { venueId, token } = request.data;\n\n const variantRef = db.collection(\"variants\").withConverter(variantConverter);\n const variantSnap = await variantRef.get();\n const variants = Object.fromEntries(variantSnap.docs.map((doc) => [doc.id, doc.data()]));\n\n const ref = db\n .collection(`venues/${venueId}/students`)\n .where(\"token\", \"==\", token)\n .withConverter(studentConverter);\n\n await pipeline(ref.stream(), async function* (stream) {\n for await (const data of stream) {\n const snapshot = data as unknown as QueryDocumentSnapshot<Student>;\n const ref = snapshot.ref;\n await db.runTransaction(async (t) => {\n const studentSnap = await t.get(ref);\n const student = studentSnap.data();\n if (!student?.variantId) return;\n const score = calcScore(student, variants[student.variantId]?.schema);\n if (score !== student.score) {\n t.update(ref, { score });\n }\n });\n }\n });\n}\n"],"names":["updateScoresHandler","request","venueId","token","variantSnap","db","variantConverter","variants","doc","ref","studentConverter","pipeline","stream","data","t","student","score","calcScore"],"mappings":";;;;AAUA,eAAsBA,EAAoBC,GAAkB;AAC1D,QAAM,EAAE,SAAAC,GAAS,OAAAC,EAAA,IAAUF,EAAQ,MAG7BG,IAAc,MADDC,EAAG,WAAW,UAAU,EAAE,cAAcC,CAAgB,EACtC,IAAA,GAC/BC,IAAW,OAAO,YAAYH,EAAY,KAAK,IAAI,CAACI,MAAQ,CAACA,EAAI,IAAIA,EAAI,KAAA,CAAM,CAAC,CAAC,GAEjFC,IAAMJ,EACT,WAAW,UAAUH,CAAO,WAAW,EACvC,MAAM,SAAS,MAAMC,CAAK,EAC1B,cAAcO,CAAgB;AAEjC,QAAMC,EAASF,EAAI,OAAA,GAAU,iBAAiBG,GAAQ;AACpD,qBAAiBC,KAAQD,GAAQ;AAE/B,YAAMH,IADWI,EACI;AACrB,YAAMR,EAAG,eAAe,OAAOS,MAAM;AAEnC,cAAMC,KADc,MAAMD,EAAE,IAAIL,CAAG,GACP,KAAA;AAC5B,YAAI,CAACM,GAAS,UAAW;AACzB,cAAMC,IAAQC,EAAUF,GAASR,EAASQ,EAAQ,SAAS,GAAG,MAAM;AACpE,QAAIC,MAAUD,EAAQ,SACpBD,EAAE,OAAOL,GAAK,EAAE,OAAAO,EAAA,CAAO;AAAA,MAE3B,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { studentSchema as s } from "@olinfo/quizms/models";
|
|
2
|
+
import t from "zod";
|
|
3
|
+
const o = t.strictObject({
|
|
4
|
+
authType: t.string(),
|
|
5
|
+
authId: t.string().optional(),
|
|
6
|
+
studentId: t.string(),
|
|
7
|
+
student: s,
|
|
8
|
+
submittedAt: t.iso.datetime()
|
|
9
|
+
});
|
|
10
|
+
export {
|
|
11
|
+
o as submissionSchema
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=submission.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submission.js","sources":["../../../src/models/submission.ts"],"sourcesContent":["import { studentSchema } from \"@olinfo/quizms/models\";\nimport z from \"zod\";\n\nexport const submissionSchema = z.strictObject({\n authType: z.string(),\n authId: z.string().optional(),\n studentId: z.string(),\n student: studentSchema,\n submittedAt: z.iso.datetime(),\n});\n"],"names":["submissionSchema","z","studentSchema"],"mappings":";;AAGO,MAAMA,IAAmBC,EAAE,aAAa;AAAA,EAC7C,UAAUA,EAAE,OAAA;AAAA,EACZ,QAAQA,EAAE,OAAA,EAAS,SAAA;AAAA,EACnB,WAAWA,EAAE,OAAA;AAAA,EACb,SAASC;AAAA,EACT,aAAaD,EAAE,IAAI,SAAA;AACrB,CAAC;"}
|