@olinfo/quizms-firebase 4.0.0 → 4.1.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/functions.d.ts +2 -11
- package/dist/functions.js +2 -2
- package/dist/package.json.js +1 -1
- package/dist/src/cli/files/functions-index.js +4 -4
- package/dist/src/cli/files/functions-index.js.map +1 -1
- package/dist/src/cli/init.js +8 -8
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/update-scores.js +21 -32
- package/dist/src/cli/update-scores.js.map +1 -1
- package/dist/src/cli/utils/collection.js +20 -18
- package/dist/src/cli/utils/collection.js.map +1 -1
- package/dist/src/cli/utils/converters-admin.js +31 -32
- package/dist/src/cli/utils/converters-admin.js.map +1 -1
- package/dist/src/cli/utils/region-finder.js +6 -5
- package/dist/src/cli/utils/region-finder.js.map +1 -1
- package/dist/src/cli/utils/storage.js +26 -35
- package/dist/src/cli/utils/storage.js.map +1 -1
- package/dist/src/cli/utils/users.js +13 -12
- package/dist/src/cli/utils/users.js.map +1 -1
- package/dist/src/functions/api/admin.js +28 -0
- package/dist/src/functions/api/admin.js.map +1 -0
- package/dist/src/functions/api/index.js +26 -34
- package/dist/src/functions/api/index.js.map +1 -1
- package/dist/src/functions/api/queries.js +5 -5
- package/dist/src/functions/api/student.js +62 -72
- package/dist/src/functions/api/student.js.map +1 -1
- package/dist/src/functions/api/teacher.js +104 -111
- package/dist/src/functions/api/teacher.js.map +1 -1
- package/dist/src/functions/api/trpc.js +18 -0
- package/dist/src/functions/api/trpc.js.map +1 -0
- package/dist/src/functions/submissions.js +1 -1
- package/dist/src/functions/submissions.js.map +1 -1
- package/dist/src/models/user.js +2 -2
- package/dist/src/models/user.js.map +1 -1
- package/dist/src/models/website.js +1 -1
- package/dist/src/models/website.js.map +1 -1
- package/dist/src/vite/preset.js +3 -4
- package/dist/src/vite/preset.js.map +1 -1
- package/dist/src/web/admin/announcements.js +9 -10
- package/dist/src/web/admin/announcements.js.map +1 -1
- package/dist/src/web/admin/school-table.js +7 -8
- package/dist/src/web/admin/school-table.js.map +1 -1
- package/dist/src/web/common/api.js +32 -35
- package/dist/src/web/common/api.js.map +1 -1
- package/dist/src/web/common/converters.js +35 -36
- package/dist/src/web/common/converters.js.map +1 -1
- package/dist/src/web/common/token-login.js +11 -11
- package/dist/src/web/common/token-login.js.map +1 -1
- package/dist/src/web/entry/admin.js +5 -7
- package/dist/src/web/entry/admin.js.map +1 -1
- package/dist/src/web/entry/student-restoring.js +18 -20
- package/dist/src/web/entry/student-restoring.js.map +1 -1
- package/dist/src/web/entry/student.js +70 -76
- package/dist/src/web/entry/student.js.map +1 -1
- package/dist/src/web/entry/teacher.js +68 -68
- package/dist/src/web/entry/teacher.js.map +1 -1
- package/dist/src/web/hooks/collection.js +29 -30
- package/dist/src/web/hooks/collection.js.map +1 -1
- package/dist/src/web/hooks/document.js +35 -36
- package/dist/src/web/hooks/document.js.map +1 -1
- package/dist/src/web/hooks/subscription.js +26 -22
- package/dist/src/web/hooks/subscription.js.map +1 -1
- package/package.json +23 -19
- package/dist/src/functions/api/schema.js +0 -42
- package/dist/src/functions/api/schema.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","sources":["../../../../src/cli/utils/storage.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\
|
|
1
|
+
{"version":3,"file":"storage.js","sources":["../../../../src/cli/utils/storage.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { styleText } from \"node:util\";\n\nimport type { Bucket } from \"@google-cloud/storage\";\nimport { confirm } from \"@inquirer/prompts\";\nimport { fatal, success, withProgress } from \"@olinfo/quizms/utils-node\";\nimport { partition } from \"lodash-es\";\n\ntype ImportOptions = {\n skipExisting?: true;\n force?: true;\n};\n\nexport async function importStorage(\n bucket: Bucket,\n collection: string,\n files: [string, string][],\n options: ImportOptions,\n) {\n const existingFiles = new Set<string>();\n\n await withProgress(files, files.length, async ([local, remote]) => {\n if (!existsSync(local)) {\n fatal(\n `Cannot find ${collection}. Make sure to generate them first, use \\`quizms --help\\` for usage.`,\n );\n }\n\n const file = bucket.file(remote);\n const [exists] = await file.exists().catch(() => [false]);\n if (exists) {\n existingFiles.add(remote);\n }\n });\n\n const [existing, nonExisting] = partition(files, ([_, remote]) => existingFiles.has(remote));\n if (existing.length > 0 && !options.skipExisting && !options.force) {\n fatal(\n `Files already exist in \\`${collection}\\`. Use \\`--force\\` to overwrite or \\`--skip-existing\\` to ignore.`,\n );\n }\n\n const filesToImport = options.skipExisting ? nonExisting : [...existing, ...nonExisting];\n\n if (options.force && existing.length > 0) {\n const confirmed = await confirm({\n message: `You are about to import the ${styleText(\n \"bold\",\n collection,\n )}. ${existing.length} files will be overwritten, the previous data will be lost. Are you really sure?`,\n default: false,\n });\n if (!confirmed) {\n fatal(\"Command aborted.\");\n }\n }\n\n await withProgress(filesToImport, filesToImport.length, ([local, remote]) =>\n bucket.upload(local, { destination: remote }),\n );\n\n success(`${filesToImport.length} ${collection} imported!`);\n}\n"],"names":["importStorage","bucket","collection","files","options","existingFiles","withProgress","local","remote","existsSync","fatal","file","exists","existing","nonExisting","partition","_","filesToImport","confirm","styleText","success"],"mappings":";;;;;AAaA,eAAsBA,EACpBC,GACAC,GACAC,GACAC,GACA;AACA,QAAMC,wBAAoB,IAAA;AAE1B,QAAMC,EAAaH,GAAOA,EAAM,QAAQ,OAAO,CAACI,GAAOC,CAAM,MAAM;AACjE,IAAKC,EAAWF,CAAK,KACnBG;AAAA,MACE,eAAeR,CAAU;AAAA,IAAA;AAI7B,UAAMS,IAAOV,EAAO,KAAKO,CAAM,GACzB,CAACI,CAAM,IAAI,MAAMD,EAAK,OAAA,EAAS,MAAM,MAAM,CAAC,EAAK,CAAC;AACxD,IAAIC,KACFP,EAAc,IAAIG,CAAM;AAAA,EAE5B,CAAC;AAED,QAAM,CAACK,GAAUC,CAAW,IAAIC,EAAUZ,GAAO,CAAC,CAACa,GAAGR,CAAM,MAAMH,EAAc,IAAIG,CAAM,CAAC;AAC3F,EAAIK,EAAS,SAAS,KAAK,CAACT,EAAQ,gBAAgB,CAACA,EAAQ,SAC3DM;AAAA,IACE,4BAA4BR,CAAU;AAAA,EAAA;AAI1C,QAAMe,IAAgBb,EAAQ,eAAeU,IAAc,CAAC,GAAGD,GAAU,GAAGC,CAAW;AAEvF,EAAIV,EAAQ,SAASS,EAAS,SAAS,MACnB,MAAMK,EAAQ;AAAA,IAC9B,SAAS,+BAA+BC;AAAA,MACtC;AAAA,MACAjB;AAAA,IAAA,CACD,KAAKW,EAAS,MAAM;AAAA,IACrB,SAAS;AAAA,EAAA,CACV,KAECH,EAAM,kBAAkB,IAI5B,MAAMJ;AAAA,IAAaW;AAAA,IAAeA,EAAc;AAAA,IAAQ,CAAC,CAACV,GAAOC,CAAM,MACrEP,EAAO,OAAOM,GAAO,EAAE,aAAaC,EAAA,CAAQ;AAAA,EAAA,GAG9CY,EAAQ,GAAGH,EAAc,MAAM,IAAIf,CAAU,YAAY;AAC3D;"}
|
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { styleText as m } from "node:util";
|
|
2
|
+
import { confirm as s } from "@inquirer/prompts";
|
|
3
|
+
import { fatal as l } from "@olinfo/quizms/utils-node";
|
|
3
4
|
import { info as n } from "firebase-functions/logger";
|
|
4
5
|
import { upperFirst as f } from "lodash-es";
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
async function A(e, o, i, t) {
|
|
6
|
+
import { importCollection as u } from "./collection.js";
|
|
7
|
+
import { userConverter as p } from "./converters-admin.js";
|
|
8
|
+
async function x(e, o, i, t) {
|
|
9
9
|
if (t.delete) {
|
|
10
|
-
await
|
|
11
|
-
message: `You are about to delete all ${
|
|
10
|
+
await s({
|
|
11
|
+
message: `You are about to delete all ${m(
|
|
12
|
+
"bold",
|
|
12
13
|
o
|
|
13
14
|
)} users. Any previous data will be lost, this operation cannot be undone. Are you really sure?`,
|
|
14
15
|
default: !1
|
|
15
|
-
}) ||
|
|
16
|
+
}) || l("Command aborted.");
|
|
16
17
|
const a = e.collection("users");
|
|
17
18
|
await e.recursiveDelete(a), n(`${f(o)} users deleted!`);
|
|
18
19
|
}
|
|
19
|
-
await
|
|
20
|
+
await u(
|
|
20
21
|
e,
|
|
21
22
|
"users",
|
|
22
23
|
i.map((r) => ({ ...r, id: r.username, role: o })),
|
|
23
|
-
|
|
24
|
+
p,
|
|
24
25
|
{ ...t, delete: void 0 }
|
|
25
26
|
);
|
|
26
27
|
}
|
|
27
28
|
export {
|
|
28
|
-
|
|
29
|
+
x as importUsers
|
|
29
30
|
};
|
|
30
31
|
//# sourceMappingURL=users.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"users.js","sources":["../../../../src/cli/utils/users.ts"],"sourcesContent":["import { confirm } from \"@inquirer/prompts\";\nimport { fatal } from \"@olinfo/quizms/utils-node\";\nimport type { Firestore } from \"firebase-admin/firestore\";\nimport { info } from \"firebase-functions/logger\";\nimport { upperFirst } from \"lodash-es\";\
|
|
1
|
+
{"version":3,"file":"users.js","sources":["../../../../src/cli/utils/users.ts"],"sourcesContent":["import { styleText } from \"node:util\";\n\nimport { confirm } from \"@inquirer/prompts\";\nimport { fatal } from \"@olinfo/quizms/utils-node\";\nimport type { Firestore } from \"firebase-admin/firestore\";\nimport { info } from \"firebase-functions/logger\";\nimport { upperFirst } from \"lodash-es\";\n\nimport type { User } from \"~/models/user\";\n\nimport { importCollection } from \"./collection\";\nimport { userConverter } from \"./converters-admin\";\n\ntype ImportOptions = {\n delete?: true;\n skipExisting?: true;\n force?: true;\n};\n\nexport async function importUsers(\n db: Firestore,\n role: string,\n users: Omit<User, \"id\" | \"role\">[],\n options: ImportOptions,\n) {\n if (options.delete) {\n const confirmed = await confirm({\n message: `You are about to delete all ${styleText(\n \"bold\",\n role,\n )} users. Any previous data will be lost, this operation cannot be undone. Are you really sure?`,\n default: false,\n });\n if (!confirmed) {\n fatal(\"Command aborted.\");\n }\n\n const ref = db.collection(\"users\");\n await db.recursiveDelete(ref);\n info(`${upperFirst(role)} users deleted!`);\n }\n\n await importCollection(\n db,\n \"users\",\n users.map((u) => ({ ...u, id: u.username, role })),\n userConverter,\n { ...options, delete: undefined },\n );\n}\n"],"names":["importUsers","db","role","users","options","confirm","styleText","fatal","ref","info","upperFirst","importCollection","u","userConverter"],"mappings":";;;;;;;AAmBA,eAAsBA,EACpBC,GACAC,GACAC,GACAC,GACA;AACA,MAAIA,EAAQ,QAAQ;AAQlB,IAPkB,MAAMC,EAAQ;AAAA,MAC9B,SAAS,+BAA+BC;AAAA,QACtC;AAAA,QACAJ;AAAA,MAAA,CACD;AAAA,MACD,SAAS;AAAA,IAAA,CACV,KAECK,EAAM,kBAAkB;AAG1B,UAAMC,IAAMP,EAAG,WAAW,OAAO;AACjC,UAAMA,EAAG,gBAAgBO,CAAG,GAC5BC,EAAK,GAAGC,EAAWR,CAAI,CAAC,iBAAiB;AAAA,EAC3C;AAEA,QAAMS;AAAA,IACJV;AAAA,IACA;AAAA,IACAE,EAAM,IAAI,CAACS,OAAO,EAAE,GAAGA,GAAG,IAAIA,EAAE,UAAU,MAAAV,EAAA,EAAO;AAAA,IACjDW;AAAA,IACA,EAAE,GAAGT,GAAS,QAAQ,OAAA;AAAA,EAAU;AAEpC;"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import s from "node:crypto";
|
|
2
|
+
import { TRPCError as e } from "@trpc/server";
|
|
3
|
+
import * as i from "firebase-functions/logger";
|
|
4
|
+
import t from "zod";
|
|
5
|
+
import { auth as a } from "../common.js";
|
|
6
|
+
import { getUser as m } from "./queries.js";
|
|
7
|
+
import { publicProcedure as c } from "./trpc.js";
|
|
8
|
+
const U = c.input(
|
|
9
|
+
t.strictObject({
|
|
10
|
+
username: t.string(),
|
|
11
|
+
password: t.string()
|
|
12
|
+
})
|
|
13
|
+
).mutation(async (n) => {
|
|
14
|
+
const { input: r } = n;
|
|
15
|
+
i.info("Admin login request received", { data: r });
|
|
16
|
+
const o = await m(r.username);
|
|
17
|
+
if (!o)
|
|
18
|
+
throw new e({ code: "BAD_REQUEST", message: "Username non esistente" });
|
|
19
|
+
if (o.password !== r.password)
|
|
20
|
+
throw new e({ code: "BAD_REQUEST", message: "Password non corretta" });
|
|
21
|
+
if (o.role !== "admin")
|
|
22
|
+
throw new e({ code: "FORBIDDEN", message: "Utenza non abilitata" });
|
|
23
|
+
return a.createCustomToken(s.randomUUID(), { role: "admin" });
|
|
24
|
+
});
|
|
25
|
+
export {
|
|
26
|
+
U as adminLogin
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=admin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.js","sources":["../../../../src/functions/api/admin.ts"],"sourcesContent":["import crypto from \"node:crypto\";\n\nimport { TRPCError } from \"@trpc/server\";\nimport * as logger from \"firebase-functions/logger\";\nimport z from \"zod\";\n\nimport { auth } from \"../common\";\nimport { getUser } from \"./queries\";\nimport { publicProcedure } from \"./trpc\";\n\nexport const adminLogin = 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(\"Admin 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 !== \"admin\") {\n throw new TRPCError({ code: \"FORBIDDEN\", message: \"Utenza non abilitata\" });\n }\n return auth.createCustomToken(crypto.randomUUID(), { role: \"admin\" });\n });\n"],"names":["adminLogin","publicProcedure","z","opts","data","logger","user","getUser","TRPCError","auth","crypto"],"mappings":";;;;;;;AAUO,MAAMA,IAAaC,EACvB;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,gCAAgC,EAAE,MAAAD,EAAA,CAAM;AAEpD,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;AAE5E,SAAOC,EAAK,kBAAkBC,EAAO,WAAA,GAAc,EAAE,MAAM,SAAS;AACtE,CAAC;"}
|
|
@@ -1,38 +1,30 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
case "teacherStartParticipation":
|
|
25
|
-
return await p(a, r);
|
|
26
|
-
case "teacherStopParticipation":
|
|
27
|
-
return await s(a, r);
|
|
28
|
-
case "teacherFinalizeParticipation":
|
|
29
|
-
return await n(a, r);
|
|
1
|
+
import * as a from "@trpc/server/adapters/express";
|
|
2
|
+
import p from "express";
|
|
3
|
+
import { logger as m } from "firebase-functions/logger";
|
|
4
|
+
import { adminLogin as n } from "./admin.js";
|
|
5
|
+
import { studentLogin as s } from "./student.js";
|
|
6
|
+
import { teacherFinalizeParticipation as c, teacherStopParticipation as f, teacherStartParticipation as u, teacherLogin as g } from "./teacher.js";
|
|
7
|
+
import { router as x, createContext as d } from "./trpc.js";
|
|
8
|
+
const h = x({
|
|
9
|
+
adminLogin: n,
|
|
10
|
+
studentLogin: s,
|
|
11
|
+
teacherLogin: g,
|
|
12
|
+
teacherStartParticipation: u,
|
|
13
|
+
teacherStopParticipation: f,
|
|
14
|
+
teacherFinalizeParticipation: c
|
|
15
|
+
}), l = p();
|
|
16
|
+
l.use(
|
|
17
|
+
"/",
|
|
18
|
+
a.createExpressMiddleware({
|
|
19
|
+
router: h,
|
|
20
|
+
createContext: d,
|
|
21
|
+
onError(t) {
|
|
22
|
+
const { error: r, path: o, input: e, ctx: i } = t;
|
|
23
|
+
m.error(r.message, { error: r, path: o, input: e, ctx: i });
|
|
30
24
|
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
}
|
|
25
|
+
})
|
|
26
|
+
);
|
|
35
27
|
export {
|
|
36
|
-
|
|
28
|
+
l as default
|
|
37
29
|
};
|
|
38
30
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../../../../src/functions/api/index.ts"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../../../../src/functions/api/index.ts"],"sourcesContent":["import * as trpcExpress from \"@trpc/server/adapters/express\";\nimport express from \"express\";\nimport { logger } from \"firebase-functions/logger\";\n\nimport { adminLogin } from \"./admin\";\nimport { studentLogin } from \"./student\";\nimport {\n teacherFinalizeParticipation,\n teacherLogin,\n teacherStartParticipation,\n teacherStopParticipation,\n} from \"./teacher\";\nimport { createContext, router } from \"./trpc\";\n\nconst appRouter = router({\n adminLogin,\n studentLogin,\n teacherLogin,\n teacherStartParticipation,\n teacherStopParticipation,\n teacherFinalizeParticipation,\n});\n\nexport type AppRouter = typeof appRouter;\n\nconst app = express();\napp.use(\n \"/\",\n trpcExpress.createExpressMiddleware({\n router: appRouter,\n createContext,\n onError(opts) {\n const { error, path, input, ctx } = opts;\n logger.error(error.message, { error, path, input, ctx });\n },\n }),\n);\n\nexport default app;\n"],"names":["appRouter","router","adminLogin","studentLogin","teacherLogin","teacherStartParticipation","teacherStopParticipation","teacherFinalizeParticipation","app","express","trpcExpress","createContext","opts","error","path","input","ctx","logger"],"mappings":";;;;;;;AAcA,MAAMA,IAAYC,EAAO;AAAA,EACvB,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA,cAAAC;AAAA,EACA,2BAAAC;AAAA,EACA,0BAAAC;AAAA,EACA,8BAAAC;AACF,CAAC,GAIKC,IAAMC,EAAA;AACZD,EAAI;AAAA,EACF;AAAA,EACAE,EAAY,wBAAwB;AAAA,IAClC,QAAQV;AAAA,IACR,eAAAW;AAAA,IACA,QAAQC,GAAM;AACZ,YAAM,EAAE,OAAAC,GAAO,MAAAC,GAAM,OAAAC,GAAO,KAAAC,MAAQJ;AACpC,MAAAK,EAAO,MAAMJ,EAAM,SAAS,EAAE,OAAAA,GAAO,MAAAC,GAAM,OAAAC,GAAO,KAAAC,GAAK;AAAA,IACzD;AAAA,EAAA,CACD;AACH;"}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import i from "node:crypto";
|
|
2
2
|
import { Rng as c } from "@olinfo/quizms/utils";
|
|
3
|
-
import {
|
|
3
|
+
import { userConverter as p, participationConverter as o, contestConverter as h, studentConverter as a, variantConverter as d } from "../../cli/utils/converters-admin.js";
|
|
4
4
|
import { db as e } from "../common.js";
|
|
5
5
|
const u = new c(i.randomUUID());
|
|
6
6
|
async function l(t) {
|
|
7
7
|
return (await e.doc(`contests/${t}`).withConverter(h).get()).data();
|
|
8
8
|
}
|
|
9
9
|
async function m(t) {
|
|
10
|
-
return (await e.doc(`participations/${t}`).withConverter(
|
|
10
|
+
return (await e.doc(`participations/${t}`).withConverter(o).get()).data();
|
|
11
11
|
}
|
|
12
12
|
async function f(t, n) {
|
|
13
|
-
return (await e.collection(`participations/${t}/students`).where("token", "==", n).withConverter(
|
|
13
|
+
return (await e.collection(`participations/${t}/students`).where("token", "==", n).withConverter(a).get()).docs.map((s) => s.data());
|
|
14
14
|
}
|
|
15
15
|
async function y(t) {
|
|
16
|
-
return (await e.collection("participations").withConverter(
|
|
16
|
+
return (await e.collection("participations").withConverter(o).where("token", "==", t).get()).docs[0]?.data();
|
|
17
17
|
}
|
|
18
18
|
async function $(t) {
|
|
19
19
|
return (await e.collection("users").where("username", "==", t).withConverter(p).get()).docs[0]?.data();
|
|
@@ -23,7 +23,7 @@ async function k(t) {
|
|
|
23
23
|
return u.choice(n.docs).data();
|
|
24
24
|
}
|
|
25
25
|
async function P(t, n) {
|
|
26
|
-
return (await e.collection(`participations/${t}/students`).withConverter(
|
|
26
|
+
return (await e.collection(`participations/${t}/students`).withConverter(a).where("userDataHash", "==", n).where("disabled", "!=", !0).get()).docs[0]?.data();
|
|
27
27
|
}
|
|
28
28
|
export {
|
|
29
29
|
l as getContest,
|
|
@@ -1,88 +1,78 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { Timestamp as
|
|
5
|
-
import * as
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import D, { createHash as I } from "node:crypto";
|
|
2
|
+
import { studentSchema as u, getNormalizedUserData as w } from "@olinfo/quizms/models";
|
|
3
|
+
import { TRPCError as r } from "@trpc/server";
|
|
4
|
+
import { Timestamp as i } from "firebase-admin/firestore";
|
|
5
|
+
import * as l from "firebase-functions/logger";
|
|
6
|
+
import s from "zod";
|
|
7
|
+
import { db as p, auth as m } from "../common.js";
|
|
8
|
+
import { getParticipationByToken as h, 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").update(w(o, { userData: t })).digest().toString("base64url");
|
|
11
12
|
}
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
r
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
|
|
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: u.shape.userData.unwrap().required(),
|
|
18
|
+
extraData: u.shape.extraData.unwrap()
|
|
19
|
+
})
|
|
20
|
+
).mutation(async (o) => {
|
|
21
|
+
const { input: t } = o;
|
|
22
|
+
l.info("Student login request received", { data: t });
|
|
23
|
+
const a = await h(t.token);
|
|
24
|
+
if (!a)
|
|
25
|
+
throw new r({ code: "BAD_REQUEST", message: "Codice prova non valido" });
|
|
26
|
+
if (a.contestId !== t.contestId)
|
|
27
|
+
throw new r({ code: "FORBIDDEN", message: "Codice prova non valido" });
|
|
28
|
+
const g = await k(a.contestId), n = D.randomUUID(), d = R(g, t.userData), e = await v(a.id, d);
|
|
29
|
+
if (e) {
|
|
30
|
+
if (e.token !== t.token)
|
|
31
|
+
throw new r({
|
|
32
|
+
code: "FORBIDDEN",
|
|
33
|
+
message: "Hai già partecipato a questa gara"
|
|
34
|
+
});
|
|
35
|
+
return await p.doc(`studentRestores/${n}`).set({
|
|
36
|
+
studentId: e.id,
|
|
37
|
+
participationId: e.participationId,
|
|
49
38
|
token: t.token,
|
|
50
|
-
name:
|
|
51
|
-
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:
|
|
55
|
-
}),
|
|
43
|
+
createdAt: i.now()
|
|
44
|
+
}), m.createCustomToken(n, {
|
|
56
45
|
role: "student",
|
|
57
|
-
studentId:
|
|
58
|
-
participationId:
|
|
59
|
-
variant:
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
46
|
+
studentId: e.id,
|
|
47
|
+
participationId: e.participationId,
|
|
48
|
+
variant: e.variant
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const c = await T(t.contestId), f = await p.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:
|
|
57
|
+
participationId: a.id,
|
|
68
58
|
contestId: t.contestId,
|
|
69
59
|
token: t.token,
|
|
70
|
-
startedAt:
|
|
71
|
-
finishedAt:
|
|
72
|
-
variant:
|
|
60
|
+
startedAt: a.startingTime,
|
|
61
|
+
finishedAt: a.endingTime,
|
|
62
|
+
variant: c.id,
|
|
73
63
|
answers: {},
|
|
74
64
|
extraData: t.extraData,
|
|
75
|
-
createdAt:
|
|
76
|
-
updatedAt:
|
|
65
|
+
createdAt: i.now(),
|
|
66
|
+
updatedAt: i.now()
|
|
77
67
|
});
|
|
78
|
-
return
|
|
68
|
+
return m.createCustomToken(n, {
|
|
79
69
|
role: "student",
|
|
80
|
-
studentId:
|
|
81
|
-
participationId:
|
|
82
|
-
variant:
|
|
83
|
-
})
|
|
84
|
-
};
|
|
70
|
+
studentId: f.id,
|
|
71
|
+
participationId: a.id,
|
|
72
|
+
variant: c
|
|
73
|
+
});
|
|
74
|
+
});
|
|
85
75
|
export {
|
|
86
|
-
|
|
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 {
|
|
1
|
+
{"version":3,"file":"student.js","sources":["../../../../src/functions/api/student.ts"],"sourcesContent":["import crypto, { createHash } 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 createHash(\"sha256\")\n .update(getNormalizedUserData(contest, { userData }))\n .digest()\n .toString(\"base64url\");\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","createHash","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,EAAW,QAAQ,EACvB,OAAOC,EAAsBH,GAAS,EAAE,UAAAC,EAAA,CAAU,CAAC,EACnD,SACA,SAAS,WAAW;AACzB;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;"}
|