@olinfo/quizms-firebase 4.1.0 → 4.1.2
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/package.json.js +1 -1
- package/dist/src/cli/import.js +85 -96
- package/dist/src/cli/import.js.map +1 -1
- package/dist/src/functions/api/queries.js +21 -22
- package/dist/src/functions/api/queries.js.map +1 -1
- package/dist/src/functions/api/student.js +22 -22
- package/dist/src/functions/api/student.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/hooks/collection.js.map +1 -1
- package/dist/src/web/hooks/document.js.map +1 -1
- package/package.json +22 -22
package/dist/package.json.js
CHANGED
package/dist/src/cli/import.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { existsSync as
|
|
2
|
-
import { readFile as
|
|
3
|
-
import
|
|
4
|
-
import { contestSchema as
|
|
5
|
-
import { Rng as
|
|
6
|
-
import { fatal as u, warning as
|
|
7
|
-
import { deleteApp as
|
|
8
|
-
import { groupBy as
|
|
9
|
-
import
|
|
10
|
-
import { glob as
|
|
11
|
-
import
|
|
12
|
-
import { userSchema as
|
|
13
|
-
import { websiteSchema as
|
|
14
|
-
import { importCollection as
|
|
15
|
-
import { contestConverter as
|
|
16
|
-
import { initializeFirebase as
|
|
17
|
-
import { importStorage as
|
|
18
|
-
import { importUsers as
|
|
19
|
-
async function
|
|
20
|
-
if (process.env.QUIZMS_MODE = "contest",
|
|
1
|
+
import { existsSync as I } from "node:fs";
|
|
2
|
+
import { readFile as b } from "node:fs/promises";
|
|
3
|
+
import m from "node:path";
|
|
4
|
+
import { contestSchema as $, participationSchema as g, studentSchema as C, variantsConfigSchema as j, variantSchema as V } from "@olinfo/quizms/models";
|
|
5
|
+
import { Rng as M, validate as P } from "@olinfo/quizms/utils";
|
|
6
|
+
import { fatal as u, warning as x, success as z, load as f, loadContests as v } from "@olinfo/quizms/utils-node";
|
|
7
|
+
import { deleteApp as D } from "firebase-admin/app";
|
|
8
|
+
import { groupBy as O, uniq as y } from "lodash-es";
|
|
9
|
+
import U from "picomatch";
|
|
10
|
+
import { glob as A } from "tinyglobby";
|
|
11
|
+
import l from "zod";
|
|
12
|
+
import { userSchema as N } from "../models/user.js";
|
|
13
|
+
import { websiteSchema as k } from "../models/website.js";
|
|
14
|
+
import { importCollection as w } from "./utils/collection.js";
|
|
15
|
+
import { contestConverter as q, participationConverter as F, studentConverter as Q, variantConverter as B, websiteConverter as E } from "./utils/converters-admin.js";
|
|
16
|
+
import { initializeFirebase as J } from "./utils/initialize.js";
|
|
17
|
+
import { importStorage as R } from "./utils/storage.js";
|
|
18
|
+
import { importUsers as S } from "./utils/users.js";
|
|
19
|
+
async function ht(t) {
|
|
20
|
+
if (process.env.QUIZMS_MODE = "contest", I("data") || u("Cannot find data directory. Make sure you're inside a QuizMS project."), [
|
|
21
21
|
"admins",
|
|
22
22
|
"contests",
|
|
23
23
|
"schools",
|
|
@@ -26,126 +26,115 @@ async function vt(t) {
|
|
|
26
26
|
"teachers",
|
|
27
27
|
"variants",
|
|
28
28
|
"websites"
|
|
29
|
-
].every((
|
|
30
|
-
|
|
29
|
+
].every((n) => !t[n])) {
|
|
30
|
+
x("`Nothing to import. Use `--help` for usage.`");
|
|
31
31
|
return;
|
|
32
32
|
}
|
|
33
|
-
const { app:
|
|
34
|
-
t.admins && await
|
|
33
|
+
const { app: e, bucket: o, db: i } = await J();
|
|
34
|
+
t.admins && await W(i, t), t.contests && await Z(i, t), (t.schools || t.teachers) && await _(i, t), t.students && await G(i, t), t.variants && await H(i, t), t.websites && await K(i, t), t.statements && await L(o, t), z("All done!"), await D(e);
|
|
35
35
|
}
|
|
36
|
-
async function
|
|
37
|
-
const
|
|
38
|
-
await
|
|
36
|
+
async function W(t, s) {
|
|
37
|
+
const e = await f("admins", N.omit({ role: !0 }));
|
|
38
|
+
await S(t, "admin", e, s);
|
|
39
39
|
}
|
|
40
|
-
async function
|
|
41
|
-
const
|
|
42
|
-
await
|
|
40
|
+
async function Z(t, s) {
|
|
41
|
+
const e = await f("contests", $);
|
|
42
|
+
await w(t, "contests", e, q, s);
|
|
43
43
|
}
|
|
44
|
-
async function
|
|
45
|
-
const
|
|
44
|
+
async function _(t, s) {
|
|
45
|
+
const e = g.omit({
|
|
46
46
|
schoolId: !0,
|
|
47
47
|
contestId: !0
|
|
48
48
|
}).extend({
|
|
49
|
-
contestIds:
|
|
50
|
-
password:
|
|
51
|
-
}),
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const r = c.map((a) => ({
|
|
49
|
+
contestIds: l.union([l.string(), l.array(l.string())]).default("*"),
|
|
50
|
+
password: l.string()
|
|
51
|
+
}), o = await f("schools", e), i = await v();
|
|
52
|
+
if (s.teachers) {
|
|
53
|
+
const n = o.map((a) => ({
|
|
55
54
|
name: a.name,
|
|
56
55
|
username: a.id,
|
|
57
56
|
password: a.password
|
|
58
57
|
}));
|
|
59
|
-
await
|
|
58
|
+
await S(t, "teacher", n, s);
|
|
60
59
|
}
|
|
61
|
-
if (
|
|
62
|
-
const
|
|
63
|
-
for (const a of
|
|
64
|
-
for (const
|
|
65
|
-
if (!
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
else {
|
|
71
|
-
m || (m = await d("variants", y));
|
|
72
|
-
const o = m.find((S) => S.id === a.id);
|
|
73
|
-
o || u(`Missing variants configuration for contest ${a.id}.`), f = new x(`${o.secret}-${o.id}-${e.id}-participation`).sample(o.pdfVariantIds, o.pdfPerSchool);
|
|
74
|
-
}
|
|
75
|
-
r.push({
|
|
76
|
-
id: `${e.id}-${a.id}`,
|
|
77
|
-
schoolId: e.id,
|
|
60
|
+
if (s.schools) {
|
|
61
|
+
const n = [];
|
|
62
|
+
for (const a of i)
|
|
63
|
+
for (const r of o) {
|
|
64
|
+
if (!U.isMatch(a.id, r.contestIds)) continue;
|
|
65
|
+
let c;
|
|
66
|
+
a.hasVariants && (r.pdfVariants ? c = r.pdfVariants.map((d) => `${a.id}-${d}`) : c = new M(`${a.secret}-${a.id}-${r.id}-participation`).sample(a.pdfVariantIds, a.pdfPerSchool)), n.push({
|
|
67
|
+
id: `${r.id}-${a.id}`,
|
|
68
|
+
schoolId: r.id,
|
|
78
69
|
contestId: a.id,
|
|
79
|
-
name:
|
|
70
|
+
name: r.name,
|
|
80
71
|
finalized: !1,
|
|
81
|
-
pdfVariants:
|
|
72
|
+
pdfVariants: c,
|
|
82
73
|
disabled: !1
|
|
83
74
|
});
|
|
84
75
|
}
|
|
85
|
-
await
|
|
76
|
+
await w(t, "participations", n, F, s);
|
|
86
77
|
}
|
|
87
78
|
}
|
|
88
|
-
async function
|
|
89
|
-
const
|
|
79
|
+
async function G(t, s) {
|
|
80
|
+
const e = await f("students", C), o = O(e, "participationId");
|
|
90
81
|
await Promise.all(
|
|
91
|
-
Object.entries(
|
|
92
|
-
([
|
|
82
|
+
Object.entries(o).map(
|
|
83
|
+
([i, n]) => w(
|
|
93
84
|
t,
|
|
94
|
-
`participations/${
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
`participations/${i}/students`,
|
|
86
|
+
n,
|
|
87
|
+
Q,
|
|
88
|
+
s
|
|
98
89
|
)
|
|
99
90
|
)
|
|
100
91
|
);
|
|
101
92
|
}
|
|
102
|
-
async function
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
let
|
|
93
|
+
async function H(t, s) {
|
|
94
|
+
const e = await f("variants", j), o = await Promise.all(
|
|
95
|
+
e.flatMap((i) => y([...i.variantIds, ...i.pdfVariantIds]).map(async (a) => {
|
|
96
|
+
const r = m.join("variants", i.id, a, "answers.json");
|
|
97
|
+
let c;
|
|
107
98
|
try {
|
|
108
|
-
|
|
99
|
+
c = await b(r, "utf8");
|
|
109
100
|
} catch {
|
|
110
|
-
u(`Cannot find schema for variant ${
|
|
101
|
+
u(`Cannot find schema for variant ${a}. Use \`quizms variants\` to generate it.`);
|
|
111
102
|
}
|
|
112
103
|
try {
|
|
113
|
-
return
|
|
114
|
-
} catch (
|
|
115
|
-
u(`Invalid schema for variant ${
|
|
104
|
+
return P(V, JSON.parse(c));
|
|
105
|
+
} catch (d) {
|
|
106
|
+
u(`Invalid schema for variant ${a}: ${d}`);
|
|
116
107
|
}
|
|
117
108
|
}))
|
|
118
109
|
);
|
|
119
|
-
await
|
|
110
|
+
await w(t, "variants", o, B, s);
|
|
120
111
|
}
|
|
121
|
-
async function
|
|
122
|
-
const
|
|
123
|
-
await
|
|
112
|
+
async function K(t, s) {
|
|
113
|
+
const e = await f("websites", k);
|
|
114
|
+
await w(t, "websites", e, E, s);
|
|
124
115
|
}
|
|
125
|
-
async function
|
|
126
|
-
const
|
|
127
|
-
const a =
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return (await k("*", { cwd: o })).flatMap((l) => {
|
|
133
|
-
const $ = p.extname(l);
|
|
116
|
+
async function L(t, s) {
|
|
117
|
+
const e = await v(), o = (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-"), i = e.map(async (n) => {
|
|
118
|
+
const a = y([...n.variantIds, ...n.pdfVariantIds]).map(
|
|
119
|
+
async (r) => {
|
|
120
|
+
const c = m.join("variants", n.id, r), d = m.join("statements", n.id, r);
|
|
121
|
+
return (await A("*", { cwd: c })).flatMap((p) => {
|
|
122
|
+
const h = m.extname(p);
|
|
134
123
|
return [
|
|
135
124
|
[
|
|
136
|
-
|
|
137
|
-
|
|
125
|
+
m.join(c, p),
|
|
126
|
+
m.join(d, `${m.basename(p, h)}-${o}${h}`)
|
|
138
127
|
],
|
|
139
|
-
[
|
|
128
|
+
[m.join(c, p), m.join(d, p)]
|
|
140
129
|
];
|
|
141
130
|
});
|
|
142
131
|
}
|
|
143
132
|
);
|
|
144
|
-
return (await Promise.all(
|
|
133
|
+
return (await Promise.all(a)).flat();
|
|
145
134
|
});
|
|
146
|
-
await
|
|
135
|
+
await R(t, "statements", (await Promise.all(i)).flat(), s);
|
|
147
136
|
}
|
|
148
137
|
export {
|
|
149
|
-
|
|
138
|
+
ht as default
|
|
150
139
|
};
|
|
151
140
|
//# sourceMappingURL=import.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import.js","sources":["../../../src/cli/import.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { Bucket } from \"@google-cloud/storage\";\nimport {\n contestSchema,\n type Participation,\n participationSchema,\n studentSchema,\n type VariantsConfig,\n variantSchema,\n variantsConfigSchema,\n} from \"@olinfo/quizms/models\";\nimport { Rng, validate } from \"@olinfo/quizms/utils\";\nimport { fatal, load, success, warning } from \"@olinfo/quizms/utils-node\";\nimport { deleteApp } from \"firebase-admin/app\";\nimport type { Firestore } from \"firebase-admin/firestore\";\nimport { groupBy, uniq } from \"lodash-es\";\nimport picomatch from \"picomatch\";\nimport { glob } from \"tinyglobby\";\nimport z from \"zod\";\n\nimport { userSchema } from \"~/models/user\";\nimport { websiteSchema } from \"~/models/website\";\n\nimport { importCollection } from \"./utils/collection\";\nimport {\n contestConverter,\n participationConverter,\n studentConverter,\n variantConverter,\n websiteConverter,\n} from \"./utils/converters-admin\";\nimport { initializeFirebase } from \"./utils/initialize\";\nimport { importStorage } from \"./utils/storage\";\nimport { importUsers } from \"./utils/users\";\n\ntype ImportOptions = {\n config: string;\n skipExisting?: true;\n delete?: true;\n force?: true;\n\n admins?: true;\n contests?: true;\n schools?: true;\n statements?: true;\n students?: true;\n teachers?: true;\n variants?: true;\n websites?: true;\n};\n\nexport default async function importData(options: ImportOptions) {\n process.env.QUIZMS_MODE = \"contest\";\n\n if (!existsSync(\"data\")) {\n fatal(\"Cannot find data directory. Make sure you're inside a QuizMS project.\");\n }\n\n const collections: (keyof ImportOptions)[] = [\n \"admins\",\n \"contests\",\n \"schools\",\n \"statements\",\n \"students\",\n \"teachers\",\n \"variants\",\n \"websites\",\n ];\n if (collections.every((key) => !options[key])) {\n warning(\"`Nothing to import. Use `--help` for usage.`\");\n return;\n }\n\n const { app, bucket, db } = await initializeFirebase();\n\n if (options.admins) {\n await importAdmins(db, options);\n }\n if (options.contests) {\n await importContests(db, options);\n }\n if (options.schools || options.teachers) {\n await importParticipations(db, options);\n }\n if (options.students) {\n await importStudents(db, options);\n }\n if (options.variants) {\n await importVariants(db, options);\n }\n if (options.websites) {\n await importWebsites(db, options);\n }\n if (options.statements) {\n await importStatements(bucket, options);\n }\n\n success(\"All done!\");\n await deleteApp(app);\n}\n\nasync function importAdmins(db: Firestore, options: ImportOptions) {\n const users = await load(\"admins\", userSchema.omit({ role: true }));\n await importUsers(db, \"admin\", users, options);\n}\n\nasync function importContests(db: Firestore, options: ImportOptions) {\n const contests = await load(\"contests\", contestSchema);\n await importCollection(db, \"contests\", contests, contestConverter, options);\n}\n\nasync function importParticipations(db: Firestore, options: ImportOptions) {\n const schoolSchema = participationSchema\n .omit({\n schoolId: true,\n contestId: true,\n })\n .extend({\n contestIds: z.union([z.string(), z.array(z.string())]).default(\"*\"),\n password: z.string(),\n });\n const schools = await load(\"schools\", schoolSchema);\n const contests = await load(\"contests\", contestSchema);\n let configs: VariantsConfig[] | undefined;\n\n if (options.teachers) {\n const teachers = schools.map((school) => ({\n name: school.name,\n username: school.id,\n password: school.password,\n }));\n await importUsers(db, \"teacher\", teachers, options);\n }\n\n if (options.schools) {\n const participations: Participation[] = [];\n\n for (const contest of contests) {\n for (const school of schools) {\n if (!picomatch.isMatch(contest.id, school.contestIds)) continue;\n\n let pdfVariants: string[] | undefined;\n if (contest.hasVariants) {\n if (school.pdfVariants) {\n pdfVariants = school.pdfVariants.map((id) => `${contest.id}-${id}`);\n } else {\n if (!configs) {\n configs = await load(\"variants\", variantsConfigSchema);\n }\n const config = configs.find((c) => c.id === contest.id);\n if (!config) {\n fatal(`Missing variants configuration for contest ${contest.id}.`);\n }\n\n const rng = new Rng(`${config.secret}-${config.id}-${school.id}-participation`);\n pdfVariants = rng.sample(config.pdfVariantIds, config.pdfPerSchool);\n }\n }\n\n participations.push({\n id: `${school.id}-${contest.id}`,\n schoolId: school.id,\n contestId: contest.id,\n name: school.name,\n finalized: false,\n pdfVariants,\n disabled: false,\n });\n }\n }\n\n await importCollection(db, \"participations\", participations, participationConverter, options);\n }\n}\n\nasync function importStudents(db: Firestore, options: ImportOptions) {\n const students = await load(\"students\", studentSchema);\n const participations = groupBy(students, \"participationId\");\n await Promise.all(\n Object.entries(participations).map(([participationId, students]) =>\n importCollection(\n db,\n `participations/${participationId}/students`,\n students,\n studentConverter,\n options,\n ),\n ),\n );\n}\n\nasync function importVariants(db: Firestore, options: ImportOptions) {\n const variantsConfig = await load(\"variants\", variantsConfigSchema);\n const variants = await Promise.all(\n variantsConfig.flatMap((config) => {\n const ids = uniq([...config.variantIds, ...config.pdfVariantIds]);\n return ids.map(async (id) => {\n const fileName = path.join(\"variants\", config.id, id, \"answers.json\");\n let schema: string;\n try {\n schema = await readFile(fileName, \"utf8\");\n } catch {\n fatal(`Cannot find schema for variant ${id}. Use \\`quizms variants\\` to generate it.`);\n }\n try {\n return validate(variantSchema, JSON.parse(schema));\n } catch (err) {\n fatal(`Invalid schema for variant ${id}: ${err}`);\n }\n });\n }),\n );\n await importCollection(db, \"variants\", variants, variantConverter, options);\n}\n\nasync function importWebsites(db: Firestore, options: ImportOptions) {\n const websites = await load(\"websites\", websiteSchema);\n await importCollection(db, \"websites\", websites, websiteConverter, options);\n}\n\nasync function importStatements(bucket: Bucket, options: ImportOptions) {\n const contests = await load(\"contests\", contestSchema);\n const variantsConfig = await load(\"variants\", variantsConfigSchema);\n\n const timestamp = new Date().toISOString().replace(/:/g, \"-\");\n\n const statements = contests.map(async (contest) => {\n const config = variantsConfig.find((c) => c.id === contest.id);\n if (!config) {\n fatal(`Missing variants configuration for contest ${contest.id}.`);\n }\n\n const files = uniq([...config.variantIds, ...config.pdfVariantIds]).map(\n async (id): Promise<[string, string][]> => {\n const localDir = path.join(\"variants\", config.id, id);\n const remoteDir = path.join(\"statements\", config.id, id);\n const files = await glob(\"*\", { cwd: localDir });\n\n return files.flatMap((file) => {\n const ext = path.extname(file);\n return [\n [\n path.join(localDir, file),\n path.join(remoteDir, `${path.basename(file, ext)}-${timestamp}${ext}`),\n ],\n [path.join(localDir, file), path.join(remoteDir, file)],\n ];\n });\n },\n );\n return (await Promise.all(files)).flat();\n });\n await importStorage(bucket, \"statements\", (await Promise.all(statements)).flat(), options);\n}\n"],"names":["importData","options","existsSync","fatal","key","warning","app","bucket","db","initializeFirebase","importAdmins","importContests","importParticipations","importStudents","importVariants","importWebsites","importStatements","success","deleteApp","users","load","userSchema","importUsers","contests","contestSchema","importCollection","contestConverter","schoolSchema","participationSchema","z","schools","configs","teachers","school","participations","contest","picomatch","pdfVariants","id","variantsConfigSchema","config","c","Rng","participationConverter","students","studentSchema","groupBy","participationId","studentConverter","variantsConfig","variants","uniq","fileName","path","schema","readFile","validate","variantSchema","err","variantConverter","websites","websiteSchema","websiteConverter","timestamp","statements","files","localDir","remoteDir","glob","file","ext","importStorage"],"mappings":";;;;;;;;;;;;;;;;;;AAsDA,eAA8BA,GAAWC,GAAwB;AAiB/D,MAhBA,QAAQ,IAAI,cAAc,WAErBC,EAAW,MAAM,KACpBC,EAAM,uEAAuE,GAGlC;AAAA,IAC3C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,EAEc,MAAM,CAACC,MAAQ,CAACH,EAAQG,CAAG,CAAC,GAAG;AAC7C,IAAAC,EAAQ,8CAA8C;AACtD;AAAA,EACF;AAEA,QAAM,EAAE,KAAAC,GAAK,QAAAC,GAAQ,IAAAC,EAAA,IAAO,MAAMC,EAAA;AAElC,EAAIR,EAAQ,UACV,MAAMS,EAAaF,GAAIP,CAAO,GAE5BA,EAAQ,YACV,MAAMU,EAAeH,GAAIP,CAAO,IAE9BA,EAAQ,WAAWA,EAAQ,aAC7B,MAAMW,EAAqBJ,GAAIP,CAAO,GAEpCA,EAAQ,YACV,MAAMY,EAAeL,GAAIP,CAAO,GAE9BA,EAAQ,YACV,MAAMa,EAAeN,GAAIP,CAAO,GAE9BA,EAAQ,YACV,MAAMc,EAAeP,GAAIP,CAAO,GAE9BA,EAAQ,cACV,MAAMe,EAAiBT,GAAQN,CAAO,GAGxCgB,EAAQ,WAAW,GACnB,MAAMC,EAAUZ,CAAG;AACrB;AAEA,eAAeI,EAAaF,GAAeP,GAAwB;AACjE,QAAMkB,IAAQ,MAAMC,EAAK,UAAUC,EAAW,KAAK,EAAE,MAAM,GAAA,CAAM,CAAC;AAClE,QAAMC,EAAYd,GAAI,SAASW,GAAOlB,CAAO;AAC/C;AAEA,eAAeU,EAAeH,GAAeP,GAAwB;AACnE,QAAMsB,IAAW,MAAMH,EAAK,YAAYI,CAAa;AACrD,QAAMC,EAAiBjB,GAAI,YAAYe,GAAUG,GAAkBzB,CAAO;AAC5E;AAEA,eAAeW,EAAqBJ,GAAeP,GAAwB;AACzE,QAAM0B,IAAeC,EAClB,KAAK;AAAA,IACJ,UAAU;AAAA,IACV,WAAW;AAAA,EAAA,CACZ,EACA,OAAO;AAAA,IACN,YAAYC,EAAE,MAAM,CAACA,EAAE,UAAUA,EAAE,MAAMA,EAAE,QAAQ,CAAC,CAAC,EAAE,QAAQ,GAAG;AAAA,IAClE,UAAUA,EAAE,OAAA;AAAA,EAAO,CACpB,GACGC,IAAU,MAAMV,EAAK,WAAWO,CAAY,GAC5CJ,IAAW,MAAMH,EAAK,YAAYI,CAAa;AACrD,MAAIO;AAEJ,MAAI9B,EAAQ,UAAU;AACpB,UAAM+B,IAAWF,EAAQ,IAAI,CAACG,OAAY;AAAA,MACxC,MAAMA,EAAO;AAAA,MACb,UAAUA,EAAO;AAAA,MACjB,UAAUA,EAAO;AAAA,IAAA,EACjB;AACF,UAAMX,EAAYd,GAAI,WAAWwB,GAAU/B,CAAO;AAAA,EACpD;AAEA,MAAIA,EAAQ,SAAS;AACnB,UAAMiC,IAAkC,CAAA;AAExC,eAAWC,KAAWZ;AACpB,iBAAWU,KAAUH,GAAS;AAC5B,YAAI,CAACM,EAAU,QAAQD,EAAQ,IAAIF,EAAO,UAAU,EAAG;AAEvD,YAAII;AACJ,YAAIF,EAAQ;AACV,cAAIF,EAAO;AACT,YAAAI,IAAcJ,EAAO,YAAY,IAAI,CAACK,MAAO,GAAGH,EAAQ,EAAE,IAAIG,CAAE,EAAE;AAAA,eAC7D;AACL,YAAKP,MACHA,IAAU,MAAMX,EAAK,YAAYmB,CAAoB;AAEvD,kBAAMC,IAAST,EAAQ,KAAK,CAACU,MAAMA,EAAE,OAAON,EAAQ,EAAE;AACtD,YAAKK,KACHrC,EAAM,8CAA8CgC,EAAQ,EAAE,GAAG,GAInEE,IADY,IAAIK,EAAI,GAAGF,EAAO,MAAM,IAAIA,EAAO,EAAE,IAAIP,EAAO,EAAE,gBAAgB,EAC5D,OAAOO,EAAO,eAAeA,EAAO,YAAY;AAAA,UACpE;AAGF,QAAAN,EAAe,KAAK;AAAA,UAClB,IAAI,GAAGD,EAAO,EAAE,IAAIE,EAAQ,EAAE;AAAA,UAC9B,UAAUF,EAAO;AAAA,UACjB,WAAWE,EAAQ;AAAA,UACnB,MAAMF,EAAO;AAAA,UACb,WAAW;AAAA,UACX,aAAAI;AAAA,UACA,UAAU;AAAA,QAAA,CACX;AAAA,MACH;AAGF,UAAMZ,EAAiBjB,GAAI,kBAAkB0B,GAAgBS,GAAwB1C,CAAO;AAAA,EAC9F;AACF;AAEA,eAAeY,EAAeL,GAAeP,GAAwB;AACnE,QAAM2C,IAAW,MAAMxB,EAAK,YAAYyB,CAAa,GAC/CX,IAAiBY,EAAQF,GAAU,iBAAiB;AAC1D,QAAM,QAAQ;AAAA,IACZ,OAAO,QAAQV,CAAc,EAAE;AAAA,MAAI,CAAC,CAACa,GAAiBH,CAAQ,MAC5DnB;AAAA,QACEjB;AAAA,QACA,kBAAkBuC,CAAe;AAAA,QACjCH;AAAAA,QACAI;AAAA,QACA/C;AAAA,MAAA;AAAA,IACF;AAAA,EACF;AAEJ;AAEA,eAAea,EAAeN,GAAeP,GAAwB;AACnE,QAAMgD,IAAiB,MAAM7B,EAAK,YAAYmB,CAAoB,GAC5DW,IAAW,MAAM,QAAQ;AAAA,IAC7BD,EAAe,QAAQ,CAACT,MACVW,EAAK,CAAC,GAAGX,EAAO,YAAY,GAAGA,EAAO,aAAa,CAAC,EACrD,IAAI,OAAOF,MAAO;AAC3B,YAAMc,IAAWC,EAAK,KAAK,YAAYb,EAAO,IAAIF,GAAI,cAAc;AACpE,UAAIgB;AACJ,UAAI;AACF,QAAAA,IAAS,MAAMC,EAASH,GAAU,MAAM;AAAA,MAC1C,QAAQ;AACN,QAAAjD,EAAM,kCAAkCmC,CAAE,2CAA2C;AAAA,MACvF;AACA,UAAI;AACF,eAAOkB,EAASC,GAAe,KAAK,MAAMH,CAAM,CAAC;AAAA,MACnD,SAASI,GAAK;AACZ,QAAAvD,EAAM,8BAA8BmC,CAAE,KAAKoB,CAAG,EAAE;AAAA,MAClD;AAAA,IACF,CAAC,CACF;AAAA,EAAA;AAEH,QAAMjC,EAAiBjB,GAAI,YAAY0C,GAAUS,GAAkB1D,CAAO;AAC5E;AAEA,eAAec,EAAeP,GAAeP,GAAwB;AACnE,QAAM2D,IAAW,MAAMxC,EAAK,YAAYyC,CAAa;AACrD,QAAMpC,EAAiBjB,GAAI,YAAYoD,GAAUE,GAAkB7D,CAAO;AAC5E;AAEA,eAAee,EAAiBT,GAAgBN,GAAwB;AACtE,QAAMsB,IAAW,MAAMH,EAAK,YAAYI,CAAa,GAC/CyB,IAAiB,MAAM7B,EAAK,YAAYmB,CAAoB,GAE5DwB,yBAAgB,KAAA,GAAO,cAAc,QAAQ,MAAM,GAAG,GAEtDC,IAAazC,EAAS,IAAI,OAAOY,MAAY;AACjD,UAAMK,IAASS,EAAe,KAAK,CAACR,MAAMA,EAAE,OAAON,EAAQ,EAAE;AAC7D,IAAKK,KACHrC,EAAM,8CAA8CgC,EAAQ,EAAE,GAAG;AAGnE,UAAM8B,IAAQd,EAAK,CAAC,GAAGX,EAAO,YAAY,GAAGA,EAAO,aAAa,CAAC,EAAE;AAAA,MAClE,OAAOF,MAAoC;AACzC,cAAM4B,IAAWb,EAAK,KAAK,YAAYb,EAAO,IAAIF,CAAE,GAC9C6B,IAAYd,EAAK,KAAK,cAAcb,EAAO,IAAIF,CAAE;AAGvD,gBAFc,MAAM8B,EAAK,KAAK,EAAE,KAAKF,GAAU,GAElC,QAAQ,CAACG,MAAS;AAC7B,gBAAMC,IAAMjB,EAAK,QAAQgB,CAAI;AAC7B,iBAAO;AAAA,YACL;AAAA,cACEhB,EAAK,KAAKa,GAAUG,CAAI;AAAA,cACxBhB,EAAK,KAAKc,GAAW,GAAGd,EAAK,SAASgB,GAAMC,CAAG,CAAC,IAAIP,CAAS,GAAGO,CAAG,EAAE;AAAA,YAAA;AAAA,YAEvE,CAACjB,EAAK,KAAKa,GAAUG,CAAI,GAAGhB,EAAK,KAAKc,GAAWE,CAAI,CAAC;AAAA,UAAA;AAAA,QAE1D,CAAC;AAAA,MACH;AAAA,IAAA;AAEF,YAAQ,MAAM,QAAQ,IAAIJ,CAAK,GAAG,KAAA;AAAA,EACpC,CAAC;AACD,QAAMM,EAAchE,GAAQ,eAAe,MAAM,QAAQ,IAAIyD,CAAU,GAAG,KAAA,GAAQ/D,CAAO;AAC3F;"}
|
|
1
|
+
{"version":3,"file":"import.js","sources":["../../../src/cli/import.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { Bucket } from \"@google-cloud/storage\";\nimport {\n contestSchema,\n type Participation,\n participationSchema,\n studentSchema,\n variantSchema,\n variantsConfigSchema,\n} from \"@olinfo/quizms/models\";\nimport { Rng, validate } from \"@olinfo/quizms/utils\";\nimport { fatal, load, loadContests, success, warning } from \"@olinfo/quizms/utils-node\";\nimport { deleteApp } from \"firebase-admin/app\";\nimport type { Firestore } from \"firebase-admin/firestore\";\nimport { groupBy, uniq } from \"lodash-es\";\nimport picomatch from \"picomatch\";\nimport { glob } from \"tinyglobby\";\nimport z from \"zod\";\n\nimport { userSchema } from \"~/models/user\";\nimport { websiteSchema } from \"~/models/website\";\n\nimport { importCollection } from \"./utils/collection\";\nimport {\n contestConverter,\n participationConverter,\n studentConverter,\n variantConverter,\n websiteConverter,\n} from \"./utils/converters-admin\";\nimport { initializeFirebase } from \"./utils/initialize\";\nimport { importStorage } from \"./utils/storage\";\nimport { importUsers } from \"./utils/users\";\n\ntype ImportOptions = {\n config: string;\n skipExisting?: true;\n delete?: true;\n force?: true;\n\n admins?: true;\n contests?: true;\n schools?: true;\n statements?: true;\n students?: true;\n teachers?: true;\n variants?: true;\n websites?: true;\n};\n\nexport default async function importData(options: ImportOptions) {\n process.env.QUIZMS_MODE = \"contest\";\n\n if (!existsSync(\"data\")) {\n fatal(\"Cannot find data directory. Make sure you're inside a QuizMS project.\");\n }\n\n const collections: (keyof ImportOptions)[] = [\n \"admins\",\n \"contests\",\n \"schools\",\n \"statements\",\n \"students\",\n \"teachers\",\n \"variants\",\n \"websites\",\n ];\n if (collections.every((key) => !options[key])) {\n warning(\"`Nothing to import. Use `--help` for usage.`\");\n return;\n }\n\n const { app, bucket, db } = await initializeFirebase();\n\n if (options.admins) {\n await importAdmins(db, options);\n }\n if (options.contests) {\n await importContests(db, options);\n }\n if (options.schools || options.teachers) {\n await importParticipations(db, options);\n }\n if (options.students) {\n await importStudents(db, options);\n }\n if (options.variants) {\n await importVariants(db, options);\n }\n if (options.websites) {\n await importWebsites(db, options);\n }\n if (options.statements) {\n await importStatements(bucket, options);\n }\n\n success(\"All done!\");\n await deleteApp(app);\n}\n\nasync function importAdmins(db: Firestore, options: ImportOptions) {\n const users = await load(\"admins\", userSchema.omit({ role: true }));\n await importUsers(db, \"admin\", users, options);\n}\n\nasync function importContests(db: Firestore, options: ImportOptions) {\n const contests = await load(\"contests\", contestSchema);\n await importCollection(db, \"contests\", contests, contestConverter, options);\n}\n\nasync function importParticipations(db: Firestore, options: ImportOptions) {\n const schoolSchema = participationSchema\n .omit({\n schoolId: true,\n contestId: true,\n })\n .extend({\n contestIds: z.union([z.string(), z.array(z.string())]).default(\"*\"),\n password: z.string(),\n });\n const schools = await load(\"schools\", schoolSchema);\n const contests = await loadContests();\n\n if (options.teachers) {\n const teachers = schools.map((school) => ({\n name: school.name,\n username: school.id,\n password: school.password,\n }));\n await importUsers(db, \"teacher\", teachers, options);\n }\n\n if (options.schools) {\n const participations: Participation[] = [];\n\n for (const contest of contests) {\n for (const school of schools) {\n if (!picomatch.isMatch(contest.id, school.contestIds)) continue;\n\n let pdfVariants: string[] | undefined;\n if (contest.hasVariants) {\n if (school.pdfVariants) {\n pdfVariants = school.pdfVariants.map((id) => `${contest.id}-${id}`);\n } else {\n const rng = new Rng(`${contest.secret}-${contest.id}-${school.id}-participation`);\n pdfVariants = rng.sample(contest.pdfVariantIds, contest.pdfPerSchool);\n }\n }\n\n participations.push({\n id: `${school.id}-${contest.id}`,\n schoolId: school.id,\n contestId: contest.id,\n name: school.name,\n finalized: false,\n pdfVariants,\n disabled: false,\n });\n }\n }\n\n await importCollection(db, \"participations\", participations, participationConverter, options);\n }\n}\n\nasync function importStudents(db: Firestore, options: ImportOptions) {\n const students = await load(\"students\", studentSchema);\n const participations = groupBy(students, \"participationId\");\n await Promise.all(\n Object.entries(participations).map(([participationId, students]) =>\n importCollection(\n db,\n `participations/${participationId}/students`,\n students,\n studentConverter,\n options,\n ),\n ),\n );\n}\n\nasync function importVariants(db: Firestore, options: ImportOptions) {\n const variantsConfig = await load(\"variants\", variantsConfigSchema);\n const variants = await Promise.all(\n variantsConfig.flatMap((config) => {\n const ids = uniq([...config.variantIds, ...config.pdfVariantIds]);\n return ids.map(async (id) => {\n const fileName = path.join(\"variants\", config.id, id, \"answers.json\");\n let schema: string;\n try {\n schema = await readFile(fileName, \"utf8\");\n } catch {\n fatal(`Cannot find schema for variant ${id}. Use \\`quizms variants\\` to generate it.`);\n }\n try {\n return validate(variantSchema, JSON.parse(schema));\n } catch (err) {\n fatal(`Invalid schema for variant ${id}: ${err}`);\n }\n });\n }),\n );\n await importCollection(db, \"variants\", variants, variantConverter, options);\n}\n\nasync function importWebsites(db: Firestore, options: ImportOptions) {\n const websites = await load(\"websites\", websiteSchema);\n await importCollection(db, \"websites\", websites, websiteConverter, options);\n}\n\nasync function importStatements(bucket: Bucket, options: ImportOptions) {\n const contests = await loadContests();\n\n const timestamp = new Date().toISOString().replace(/:/g, \"-\");\n\n const statements = contests.map(async (config) => {\n const files = uniq([...config.variantIds, ...config.pdfVariantIds]).map(\n async (id): Promise<[string, string][]> => {\n const localDir = path.join(\"variants\", config.id, id);\n const remoteDir = path.join(\"statements\", config.id, id);\n const files = await glob(\"*\", { cwd: localDir });\n\n return files.flatMap((file) => {\n const ext = path.extname(file);\n return [\n [\n path.join(localDir, file),\n path.join(remoteDir, `${path.basename(file, ext)}-${timestamp}${ext}`),\n ],\n [path.join(localDir, file), path.join(remoteDir, file)],\n ];\n });\n },\n );\n return (await Promise.all(files)).flat();\n });\n await importStorage(bucket, \"statements\", (await Promise.all(statements)).flat(), options);\n}\n"],"names":["importData","options","existsSync","fatal","key","warning","app","bucket","db","initializeFirebase","importAdmins","importContests","importParticipations","importStudents","importVariants","importWebsites","importStatements","success","deleteApp","users","load","userSchema","importUsers","contests","contestSchema","importCollection","contestConverter","schoolSchema","participationSchema","z","schools","loadContests","teachers","school","participations","contest","picomatch","pdfVariants","id","Rng","participationConverter","students","studentSchema","groupBy","participationId","studentConverter","variantsConfig","variantsConfigSchema","variants","config","uniq","fileName","path","schema","readFile","validate","variantSchema","err","variantConverter","websites","websiteSchema","websiteConverter","timestamp","statements","files","localDir","remoteDir","glob","file","ext","importStorage"],"mappings":"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"}
|
|
@@ -1,37 +1,36 @@
|
|
|
1
|
-
import i from "
|
|
2
|
-
import {
|
|
3
|
-
import { userConverter as p, participationConverter as o, contestConverter as h, studentConverter as a, 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
|
|
6
|
-
async function
|
|
7
|
-
return (await e.doc(`contests/${t}`).withConverter(
|
|
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
|
|
8
|
+
async function C(t) {
|
|
10
9
|
return (await e.doc(`participations/${t}`).withConverter(o).get()).data();
|
|
11
10
|
}
|
|
12
|
-
async function
|
|
13
|
-
return (await e.collection(`participations/${t}/students`).where("token", "==", n).withConverter(a).get()).docs.map((
|
|
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
|
|
14
|
+
async function f(t) {
|
|
16
15
|
return (await e.collection("participations").withConverter(o).where("token", "==", t).get()).docs[0]?.data();
|
|
17
16
|
}
|
|
18
|
-
async function
|
|
19
|
-
return (await e.collection("users").where("username", "==", t).withConverter(
|
|
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
|
|
22
|
-
const n = await e.collection("variants").withConverter(
|
|
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
|
|
24
|
+
async function $(t, n) {
|
|
26
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
|
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,38 +1,38 @@
|
|
|
1
|
-
import D, {
|
|
2
|
-
import { studentSchema as
|
|
3
|
-
import { TRPCError as
|
|
4
|
-
import { Timestamp as
|
|
5
|
-
import * as
|
|
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
6
|
import s from "zod";
|
|
7
|
-
import { db as
|
|
8
|
-
import { getParticipationByToken as
|
|
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
9
|
import { publicProcedure as C } from "./trpc.js";
|
|
10
10
|
function R(o, t) {
|
|
11
|
-
return I("sha256"
|
|
11
|
+
return I("sha256", w(o, { userData: t }));
|
|
12
12
|
}
|
|
13
13
|
const U = C.input(
|
|
14
14
|
s.strictObject({
|
|
15
15
|
contestId: s.string(),
|
|
16
16
|
token: s.string(),
|
|
17
|
-
userData:
|
|
18
|
-
extraData:
|
|
17
|
+
userData: p.shape.userData.unwrap().required(),
|
|
18
|
+
extraData: p.shape.extraData.unwrap()
|
|
19
19
|
})
|
|
20
20
|
).mutation(async (o) => {
|
|
21
21
|
const { input: t } = o;
|
|
22
|
-
|
|
23
|
-
const a = await
|
|
22
|
+
h.info("Student login request received", { data: t });
|
|
23
|
+
const a = await l(t.token);
|
|
24
24
|
if (!a)
|
|
25
|
-
throw new
|
|
25
|
+
throw new i({ code: "BAD_REQUEST", message: "Codice prova non valido" });
|
|
26
26
|
if (a.contestId !== t.contestId)
|
|
27
|
-
throw new
|
|
28
|
-
const
|
|
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
29
|
if (e) {
|
|
30
30
|
if (e.token !== t.token)
|
|
31
|
-
throw new
|
|
31
|
+
throw new i({
|
|
32
32
|
code: "FORBIDDEN",
|
|
33
33
|
message: "Hai già partecipato a questa gara"
|
|
34
34
|
});
|
|
35
|
-
return await
|
|
35
|
+
return await u.doc(`studentRestores/${n}`).set({
|
|
36
36
|
studentId: e.id,
|
|
37
37
|
participationId: e.participationId,
|
|
38
38
|
token: t.token,
|
|
@@ -40,7 +40,7 @@ const U = C.input(
|
|
|
40
40
|
surname: t.userData.surname,
|
|
41
41
|
approvalCode: Math.round(Math.random() * 1e3),
|
|
42
42
|
status: "pending",
|
|
43
|
-
createdAt:
|
|
43
|
+
createdAt: r.now()
|
|
44
44
|
}), m.createCustomToken(n, {
|
|
45
45
|
role: "student",
|
|
46
46
|
studentId: e.id,
|
|
@@ -48,7 +48,7 @@ const U = C.input(
|
|
|
48
48
|
variant: e.variant
|
|
49
49
|
});
|
|
50
50
|
}
|
|
51
|
-
const c = await T(t.contestId),
|
|
51
|
+
const c = await T(t.contestId), g = await u.collection(`participations/${a.id}/students`).add({
|
|
52
52
|
uid: n,
|
|
53
53
|
userData: t.userData,
|
|
54
54
|
userDataHash: d,
|
|
@@ -62,12 +62,12 @@ const U = C.input(
|
|
|
62
62
|
variant: c.id,
|
|
63
63
|
answers: {},
|
|
64
64
|
extraData: t.extraData,
|
|
65
|
-
createdAt:
|
|
66
|
-
updatedAt:
|
|
65
|
+
createdAt: r.now(),
|
|
66
|
+
updatedAt: r.now()
|
|
67
67
|
});
|
|
68
68
|
return m.createCustomToken(n, {
|
|
69
69
|
role: "student",
|
|
70
|
-
studentId:
|
|
70
|
+
studentId: g.id,
|
|
71
71
|
participationId: a.id,
|
|
72
72
|
variant: c
|
|
73
73
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"student.js","sources":["../../../../src/functions/api/student.ts"],"sourcesContent":["import crypto, {
|
|
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,58 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { validate as
|
|
3
|
-
import { serverTimestamp as
|
|
4
|
-
import { cloneDeepWith as
|
|
5
|
-
import { websiteSchema as
|
|
6
|
-
function
|
|
7
|
-
return
|
|
8
|
-
if (
|
|
9
|
-
return
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
12
|
-
if (
|
|
1
|
+
import { contestSchema as f, participationSchema as d, studentRestoreSchema as p, variantSchema as u, announcementSchema as F, studentSchema as S } from "@olinfo/quizms/models";
|
|
2
|
+
import { validate as h } from "@olinfo/quizms/utils";
|
|
3
|
+
import { serverTimestamp as c, Timestamp as m } from "firebase/firestore";
|
|
4
|
+
import { cloneDeepWith as v, omit as C, isDate as D, isString as b, transform as T, isObject as w } from "lodash-es";
|
|
5
|
+
import { websiteSchema as A } from "../../models/website.js";
|
|
6
|
+
function i(r) {
|
|
7
|
+
return v(C(r, "id"), (t) => {
|
|
8
|
+
if (D(t))
|
|
9
|
+
return m.fromDate(t);
|
|
10
|
+
if (b(t))
|
|
11
|
+
return t.trim();
|
|
12
|
+
if (t === void 0)
|
|
13
13
|
return null;
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
return r.toDate();
|
|
16
|
+
function a(r) {
|
|
17
|
+
return T(r, (t, e, n) => {
|
|
18
|
+
e === null ? t[n] = void 0 : e instanceof m ? t[n] = e.toDate() : w(e) ? t[n] = a(e) : t[n] = e;
|
|
20
19
|
});
|
|
21
20
|
}
|
|
22
|
-
function
|
|
23
|
-
const
|
|
24
|
-
return
|
|
21
|
+
function s(r, t) {
|
|
22
|
+
const e = a({ ...t.data(), id: t.id });
|
|
23
|
+
return h(r, e);
|
|
25
24
|
}
|
|
26
|
-
function
|
|
25
|
+
function o(r) {
|
|
27
26
|
return {
|
|
28
|
-
toFirestore: (
|
|
29
|
-
fromFirestore: (
|
|
27
|
+
toFirestore: (t) => i(t),
|
|
28
|
+
fromFirestore: (t) => s(r, t)
|
|
30
29
|
};
|
|
31
30
|
}
|
|
32
|
-
const
|
|
33
|
-
toFirestore(
|
|
31
|
+
const W = o(f), q = o(d), z = o(p), B = o(u), E = o(A), G = {
|
|
32
|
+
toFirestore(r) {
|
|
34
33
|
return {
|
|
35
|
-
...
|
|
36
|
-
createdAt:
|
|
34
|
+
...i(r),
|
|
35
|
+
createdAt: c()
|
|
37
36
|
};
|
|
38
37
|
},
|
|
39
|
-
fromFirestore: (
|
|
40
|
-
},
|
|
41
|
-
toFirestore(
|
|
38
|
+
fromFirestore: (r) => s(F, r)
|
|
39
|
+
}, H = {
|
|
40
|
+
toFirestore(r) {
|
|
42
41
|
return {
|
|
43
|
-
...
|
|
44
|
-
updatedAt:
|
|
42
|
+
...i(r),
|
|
43
|
+
updatedAt: c()
|
|
45
44
|
};
|
|
46
45
|
},
|
|
47
|
-
fromFirestore: (
|
|
46
|
+
fromFirestore: (r) => s(S, r)
|
|
48
47
|
};
|
|
49
48
|
export {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
G as announcementConverter,
|
|
50
|
+
W as contestConverter,
|
|
51
|
+
q as participationConverter,
|
|
52
|
+
H as studentConverter,
|
|
53
|
+
z as studentRestoreConvert,
|
|
54
|
+
B as variantConverter,
|
|
55
|
+
E as websiteConverter
|
|
57
56
|
};
|
|
58
57
|
//# sourceMappingURL=converters.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"converters.js","sources":["../../../../src/web/common/converters.ts"],"sourcesContent":["import {\n type Announcement,\n announcementSchema,\n contestSchema,\n participationSchema,\n type Student,\n studentRestoreSchema,\n studentSchema,\n variantSchema,\n} from \"@olinfo/quizms/models\";\nimport { validate } from \"@olinfo/quizms/utils\";\nimport {\n type DocumentSnapshot,\n type FirestoreDataConverter,\n serverTimestamp,\n Timestamp,\n} from \"firebase/firestore\";\nimport { cloneDeepWith, isDate, isString, omit } from \"lodash-es\";\nimport type z from \"zod\";\n\nimport { websiteSchema } from \"~/models/website\";\n\nfunction convertToFirestore(data: object) {\n return cloneDeepWith(omit(data, \"id\"), (value) => {\n if (isDate(value)) {\n return Timestamp.fromDate(value);\n }\n if (isString(value)) {\n return value.trim();\n }\n if (value === undefined) {\n return null;\n }\n });\n}\n\nfunction convertFromFirestore(data: object) {\n return
|
|
1
|
+
{"version":3,"file":"converters.js","sources":["../../../../src/web/common/converters.ts"],"sourcesContent":["import {\n type Announcement,\n announcementSchema,\n contestSchema,\n participationSchema,\n type Student,\n studentRestoreSchema,\n studentSchema,\n variantSchema,\n} from \"@olinfo/quizms/models\";\nimport { validate } from \"@olinfo/quizms/utils\";\nimport {\n type DocumentSnapshot,\n type FirestoreDataConverter,\n serverTimestamp,\n Timestamp,\n} from \"firebase/firestore\";\nimport { cloneDeepWith, isDate, isObject, isString, omit, transform } from \"lodash-es\";\nimport type z from \"zod\";\n\nimport { websiteSchema } from \"~/models/website\";\n\nfunction convertToFirestore(data: object) {\n return cloneDeepWith(omit(data, \"id\"), (value) => {\n if (isDate(value)) {\n return Timestamp.fromDate(value);\n }\n if (isString(value)) {\n return value.trim();\n }\n if (value === undefined) {\n return null;\n }\n });\n}\n\nfunction convertFromFirestore(data: object) {\n return transform(data, (result: any, value: any, key: string | number) => {\n if (value === null) {\n result[key] = undefined;\n } else if (value instanceof Timestamp) {\n result[key] = value.toDate();\n } else if (isObject(value)) {\n result[key] = convertFromFirestore(value);\n } else {\n result[key] = value;\n }\n });\n}\n\nfunction parse<T>(schema: z.core.$ZodType<T>, snapshot: DocumentSnapshot): T {\n const data = convertFromFirestore({ ...snapshot.data(), id: snapshot.id });\n return validate(schema, data);\n}\n\nfunction converter<T extends object>(schema: z.core.$ZodType<T>): FirestoreDataConverter<T> {\n return {\n toFirestore: (data) => convertToFirestore(data),\n fromFirestore: (snapshot) => parse(schema, snapshot),\n };\n}\n\nexport const contestConverter = converter(contestSchema);\nexport const participationConverter = converter(participationSchema);\nexport const studentRestoreConvert = converter(studentRestoreSchema);\nexport const variantConverter = converter(variantSchema);\nexport const websiteConverter = converter(websiteSchema);\n\nexport const announcementConverter: FirestoreDataConverter<Announcement> = {\n toFirestore(data) {\n return {\n ...convertToFirestore(data),\n createdAt: serverTimestamp(),\n };\n },\n fromFirestore: (snapshot) => parse(announcementSchema, snapshot),\n};\n\nexport const studentConverter: FirestoreDataConverter<Student> = {\n toFirestore(data) {\n return {\n ...convertToFirestore(data),\n updatedAt: serverTimestamp(),\n };\n },\n fromFirestore: (snapshot) => parse(studentSchema, snapshot),\n};\n"],"names":["convertToFirestore","data","cloneDeepWith","omit","value","isDate","Timestamp","isString","convertFromFirestore","transform","result","key","isObject","parse","schema","snapshot","validate","converter","contestConverter","contestSchema","participationConverter","participationSchema","studentRestoreConvert","studentRestoreSchema","variantConverter","variantSchema","websiteConverter","websiteSchema","announcementConverter","serverTimestamp","announcementSchema","studentConverter","studentSchema"],"mappings":";;;;;AAsBA,SAASA,EAAmBC,GAAc;AACxC,SAAOC,EAAcC,EAAKF,GAAM,IAAI,GAAG,CAACG,MAAU;AAChD,QAAIC,EAAOD,CAAK;AACd,aAAOE,EAAU,SAASF,CAAK;AAEjC,QAAIG,EAASH,CAAK;AAChB,aAAOA,EAAM,KAAA;AAEf,QAAIA,MAAU;AACZ,aAAO;AAAA,EAEX,CAAC;AACH;AAEA,SAASI,EAAqBP,GAAc;AAC1C,SAAOQ,EAAUR,GAAM,CAACS,GAAaN,GAAYO,MAAyB;AACxE,IAAIP,MAAU,OACZM,EAAOC,CAAG,IAAI,SACLP,aAAiBE,IAC1BI,EAAOC,CAAG,IAAIP,EAAM,OAAA,IACXQ,EAASR,CAAK,IACvBM,EAAOC,CAAG,IAAIH,EAAqBJ,CAAK,IAExCM,EAAOC,CAAG,IAAIP;AAAA,EAElB,CAAC;AACH;AAEA,SAASS,EAASC,GAA4BC,GAA+B;AAC3E,QAAMd,IAAOO,EAAqB,EAAE,GAAGO,EAAS,QAAQ,IAAIA,EAAS,IAAI;AACzE,SAAOC,EAASF,GAAQb,CAAI;AAC9B;AAEA,SAASgB,EAA4BH,GAAuD;AAC1F,SAAO;AAAA,IACL,aAAa,CAACb,MAASD,EAAmBC,CAAI;AAAA,IAC9C,eAAe,CAACc,MAAaF,EAAMC,GAAQC,CAAQ;AAAA,EAAA;AAEvD;AAEO,MAAMG,IAAmBD,EAAUE,CAAa,GAC1CC,IAAyBH,EAAUI,CAAmB,GACtDC,IAAwBL,EAAUM,CAAoB,GACtDC,IAAmBP,EAAUQ,CAAa,GAC1CC,IAAmBT,EAAUU,CAAa,GAE1CC,IAA8D;AAAA,EACzE,YAAY3B,GAAM;AAChB,WAAO;AAAA,MACL,GAAGD,EAAmBC,CAAI;AAAA,MAC1B,WAAW4B,EAAA;AAAA,IAAgB;AAAA,EAE/B;AAAA,EACA,eAAe,CAACd,MAAaF,EAAMiB,GAAoBf,CAAQ;AACjE,GAEagB,IAAoD;AAAA,EAC/D,YAAY9B,GAAM;AAChB,WAAO;AAAA,MACL,GAAGD,EAAmBC,CAAI;AAAA,MAC1B,WAAW4B,EAAA;AAAA,IAAgB;AAAA,EAE/B;AAAA,EACA,eAAe,CAACd,MAAaF,EAAMmB,GAAejB,CAAQ;AAC5D;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collection.js","sources":["../../../../src/web/hooks/collection.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport {\n type CollectionReference,\n collection,\n doc,\n type FirestoreDataConverter,\n getDocs,\n onSnapshot,\n type Query,\n setDoc,\n} from \"firebase/firestore\";\nimport { sortBy } from \"lodash-es\";\nimport { useErrorBoundary } from \"react-error-boundary\";\nimport useSWR, { type KeyedMutator, type MutatorOptions, type SWRConfiguration } from \"swr\";\n\nimport { useDb } from \"~/web/common/base-login\";\nimport query, { type QueryOption } from \"~/web/common/query\";\n\nimport { useSubscriptionListener } from \"./subscription\";\n\nconst mutationConfig: MutatorOptions = {\n revalidate: false,\n rollbackOnError: true,\n populateCache: true,\n};\n\nexport function useCollection<T extends { id: string }>(\n path: string,\n converter: FirestoreDataConverter<T>,\n options?: QueryOption<T>,\n) {\n const db = useDb();\n const { showBoundary } = useErrorBoundary();\n\n const ref = collection(db, path).withConverter(converter);\n const q = query(db, path, converter, options);\n\n const swrConfig: SWRConfiguration = {\n revalidateIfStale: false,\n revalidateOnFocus: false,\n revalidateOnMount: false,\n revalidateOnReconnect: false,\n suspense: true,\n };\n\n const key = `${path}?${JSON.stringify(options)}`;\n const { data, mutate } = useSWR<T[]>(key, () => fetcher(q), swrConfig);\n\n const setData = useCallback(\n (newDoc: T) => setDocument(ref, newDoc, mutate, options),\n [ref, mutate, options],\n );\n\n useSubscriptionListener<T[]>(\n key,\n (setData) => {\n const unsubscribe = onSnapshot(\n q,\n (snap) => {\n try {\n setData(snap.docs.map((doc) => doc.data()));\n } catch (err) {\n showBoundary(err);\n }\n },\n (error) => showBoundary(error),\n );\n return () => unsubscribe();\n },\n (data) => mutate(data, mutationConfig),\n );\n\n return [data as T[], setData] as const;\n}\n\nasync function setDocument<T extends { id: string }>(\n ref: CollectionReference<T>,\n newDoc: T,\n mutator: KeyedMutator<T[]>,\n options?: QueryOption<T>,\n): Promise<void> {\n await mutator(\n async (prev) => {\n const docRef = doc(ref, newDoc.id);\n await setDoc(docRef, newDoc);\n return merge(prev, newDoc, options);\n },\n {\n ...mutationConfig,\n optimisticData: (prev) => merge(prev, newDoc, options),\n },\n );\n}\n\nfunction merge<T extends { id: string }>(\n prev: T[] | undefined,\n newDoc: T,\n options?: QueryOption<T>,\n) {\n if (!prev) return [newDoc];\n let coll = prev.filter((doc) => newDoc.id !== doc.id);\n coll.push(newDoc);\n coll = sortBy(coll, [options?.orderBy ?? \"id\"]);\n if (options?.limit) coll = coll.slice(0, options.limit);\n return coll;\n}\n\nasync function fetcher<T>(ref: Query<T>) {\n const snapshot = await getDocs(ref);\n return snapshot.docs.map((doc) => doc.data({ serverTimestamps: \"estimate\" }));\n}\n"],"names":["mutationConfig","useCollection","path","converter","options","db","useDb","showBoundary","useErrorBoundary","ref","collection","q","query","swrConfig","key","data","mutate","useSWR","fetcher","setData","useCallback","newDoc","setDocument","useSubscriptionListener","unsubscribe","onSnapshot","snap","doc","err","error","mutator","prev","docRef","setDoc","merge","coll","sortBy","getDocs"],"mappings":";;;;;;;;AAqBA,MAAMA,IAAiC;AAAA,EACrC,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,eAAe;AACjB;AAEO,SAASC,EACdC,GACAC,GACAC,GACA;AACA,QAAMC,IAAKC,EAAA,GACL,EAAE,cAAAC,EAAA,IAAiBC,EAAA,GAEnBC,IAAMC,EAAWL,GAAIH,CAAI,EAAE,cAAcC,CAAS,GAClDQ,IAAIC,EAAMP,GAAIH,GAAMC,GAAWC,CAAO,GAEtCS,IAA8B;AAAA,IAClC,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,UAAU;AAAA,EAAA,GAGNC,IAAM,GAAGZ,CAAI,IAAI,KAAK,UAAUE,CAAO,CAAC,IACxC,EAAE,MAAAW,GAAM,QAAAC,EAAA,IAAWC,EAAYH,GAAK,MAAMI,EAAQP,CAAC,GAAGE,CAAS,GAE/DM,IAAUC;AAAA,IACd,CAACC,MAAcC,EAAYb,GAAKY,GAAQL,GAAQZ,CAAO;AAAA,IACvD,CAACK,GAAKO,GAAQZ,CAAO;AAAA,EAAA;AAGvB,SAAAmB;AAAA,IACET;AAAA,IACA,CAACK,MAAY;AACX,YAAMK,IAAcC;AAAA,QAClBd;AAAA,QACA,CAACe,MAAS;AACR,cAAI;AACFP,YAAAA,EAAQO,EAAK,KAAK,IAAI,CAACC,MAAQA,EAAI,KAAA,CAAM,CAAC;AAAA,UAC5C,SAASC,GAAK;AACZ,YAAArB,EAAaqB,
|
|
1
|
+
{"version":3,"file":"collection.js","sources":["../../../../src/web/hooks/collection.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport {\n type CollectionReference,\n collection,\n doc,\n type FirestoreDataConverter,\n getDocs,\n onSnapshot,\n type Query,\n setDoc,\n} from \"firebase/firestore\";\nimport { sortBy } from \"lodash-es\";\nimport { useErrorBoundary } from \"react-error-boundary\";\nimport useSWR, { type KeyedMutator, type MutatorOptions, type SWRConfiguration } from \"swr\";\n\nimport { useDb } from \"~/web/common/base-login\";\nimport query, { type QueryOption } from \"~/web/common/query\";\n\nimport { useSubscriptionListener } from \"./subscription\";\n\nconst mutationConfig: MutatorOptions = {\n revalidate: false,\n rollbackOnError: true,\n populateCache: true,\n};\n\nexport function useCollection<T extends { id: string }>(\n path: string,\n converter: FirestoreDataConverter<T>,\n options?: QueryOption<T>,\n) {\n const db = useDb();\n const { showBoundary } = useErrorBoundary();\n\n const ref = collection(db, path).withConverter(converter);\n const q = query(db, path, converter, options);\n\n const swrConfig: SWRConfiguration = {\n revalidateIfStale: false,\n revalidateOnFocus: false,\n revalidateOnMount: false,\n revalidateOnReconnect: false,\n suspense: true,\n };\n\n const key = `${path}?${JSON.stringify(options)}`;\n const { data, mutate } = useSWR<T[]>(key, () => fetcher(q), swrConfig);\n\n const setData = useCallback(\n (newDoc: T) => setDocument(ref, newDoc, mutate, options),\n [ref, mutate, options],\n );\n\n useSubscriptionListener<T[]>(\n key,\n (setData) => {\n const unsubscribe = onSnapshot(\n q,\n (snap) => {\n try {\n setData(snap.docs.map((doc) => doc.data()));\n } catch (err) {\n showBoundary(err as Error);\n }\n },\n (error) => showBoundary(error),\n );\n return () => unsubscribe();\n },\n (data) => mutate(data, mutationConfig),\n );\n\n return [data as T[], setData] as const;\n}\n\nasync function setDocument<T extends { id: string }>(\n ref: CollectionReference<T>,\n newDoc: T,\n mutator: KeyedMutator<T[]>,\n options?: QueryOption<T>,\n): Promise<void> {\n await mutator(\n async (prev) => {\n const docRef = doc(ref, newDoc.id);\n await setDoc(docRef, newDoc);\n return merge(prev, newDoc, options);\n },\n {\n ...mutationConfig,\n optimisticData: (prev) => merge(prev, newDoc, options),\n },\n );\n}\n\nfunction merge<T extends { id: string }>(\n prev: T[] | undefined,\n newDoc: T,\n options?: QueryOption<T>,\n) {\n if (!prev) return [newDoc];\n let coll = prev.filter((doc) => newDoc.id !== doc.id);\n coll.push(newDoc);\n coll = sortBy(coll, [options?.orderBy ?? \"id\"]);\n if (options?.limit) coll = coll.slice(0, options.limit);\n return coll;\n}\n\nasync function fetcher<T>(ref: Query<T>) {\n const snapshot = await getDocs(ref);\n return snapshot.docs.map((doc) => doc.data({ serverTimestamps: \"estimate\" }));\n}\n"],"names":["mutationConfig","useCollection","path","converter","options","db","useDb","showBoundary","useErrorBoundary","ref","collection","q","query","swrConfig","key","data","mutate","useSWR","fetcher","setData","useCallback","newDoc","setDocument","useSubscriptionListener","unsubscribe","onSnapshot","snap","doc","err","error","mutator","prev","docRef","setDoc","merge","coll","sortBy","getDocs"],"mappings":";;;;;;;;AAqBA,MAAMA,IAAiC;AAAA,EACrC,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,eAAe;AACjB;AAEO,SAASC,EACdC,GACAC,GACAC,GACA;AACA,QAAMC,IAAKC,EAAA,GACL,EAAE,cAAAC,EAAA,IAAiBC,EAAA,GAEnBC,IAAMC,EAAWL,GAAIH,CAAI,EAAE,cAAcC,CAAS,GAClDQ,IAAIC,EAAMP,GAAIH,GAAMC,GAAWC,CAAO,GAEtCS,IAA8B;AAAA,IAClC,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,UAAU;AAAA,EAAA,GAGNC,IAAM,GAAGZ,CAAI,IAAI,KAAK,UAAUE,CAAO,CAAC,IACxC,EAAE,MAAAW,GAAM,QAAAC,EAAA,IAAWC,EAAYH,GAAK,MAAMI,EAAQP,CAAC,GAAGE,CAAS,GAE/DM,IAAUC;AAAA,IACd,CAACC,MAAcC,EAAYb,GAAKY,GAAQL,GAAQZ,CAAO;AAAA,IACvD,CAACK,GAAKO,GAAQZ,CAAO;AAAA,EAAA;AAGvB,SAAAmB;AAAA,IACET;AAAA,IACA,CAACK,MAAY;AACX,YAAMK,IAAcC;AAAA,QAClBd;AAAA,QACA,CAACe,MAAS;AACR,cAAI;AACFP,YAAAA,EAAQO,EAAK,KAAK,IAAI,CAACC,MAAQA,EAAI,KAAA,CAAM,CAAC;AAAA,UAC5C,SAASC,GAAK;AACZ,YAAArB,EAAaqB,CAAY;AAAA,UAC3B;AAAA,QACF;AAAA,QACA,CAACC,MAAUtB,EAAasB,CAAK;AAAA,MAAA;AAE/B,aAAO,MAAML,EAAA;AAAA,IACf;AAAA,IACA,CAACT,MAASC,EAAOD,GAAMf,CAAc;AAAA,EAAA,GAGhC,CAACe,GAAaI,CAAO;AAC9B;AAEA,eAAeG,EACbb,GACAY,GACAS,GACA1B,GACe;AACf,QAAM0B;AAAA,IACJ,OAAOC,MAAS;AACd,YAAMC,IAASL,EAAIlB,GAAKY,EAAO,EAAE;AACjC,mBAAMY,EAAOD,GAAQX,CAAM,GACpBa,EAAMH,GAAMV,GAAQjB,CAAO;AAAA,IACpC;AAAA,IACA;AAAA,MACE,GAAGJ;AAAA,MACH,gBAAgB,CAAC+B,MAASG,EAAMH,GAAMV,GAAQjB,CAAO;AAAA,IAAA;AAAA,EACvD;AAEJ;AAEA,SAAS8B,EACPH,GACAV,GACAjB,GACA;AACA,MAAI,CAAC2B,EAAM,QAAO,CAACV,CAAM;AACzB,MAAIc,IAAOJ,EAAK,OAAO,CAACJ,MAAQN,EAAO,OAAOM,EAAI,EAAE;AACpD,SAAAQ,EAAK,KAAKd,CAAM,GAChBc,IAAOC,EAAOD,GAAM,CAAC/B,GAAS,WAAW,IAAI,CAAC,GAC1CA,GAAS,UAAO+B,IAAOA,EAAK,MAAM,GAAG/B,EAAQ,KAAK,IAC/C+B;AACT;AAEA,eAAejB,EAAWT,GAAe;AAEvC,UADiB,MAAM4B,EAAQ5B,CAAG,GAClB,KAAK,IAAI,CAACkB,MAAQA,EAAI,KAAK,EAAE,kBAAkB,WAAA,CAAY,CAAC;AAC9E;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document.js","sources":["../../../../src/web/hooks/document.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport {\n type DocumentReference,\n doc,\n type FirestoreDataConverter,\n getDoc,\n onSnapshot,\n setDoc,\n} from \"firebase/firestore\";\nimport { useErrorBoundary } from \"react-error-boundary\";\nimport useSWR, { type KeyedMutator, type MutatorOptions, type SWRConfiguration } from \"swr\";\n\nimport { useDb } from \"~/web/common/base-login\";\n\nimport { useSubscriptionListener } from \"./subscription\";\n\nconst mutationConfig: MutatorOptions = {\n revalidate: false,\n rollbackOnError: true,\n populateCache: true,\n};\n\nexport function useDocument<T>(path: string, id: string, converter: FirestoreDataConverter<T>) {\n const [data, setData] = useDocumentOptional<T>(path, id, converter);\n if (!data) throw new Error(`Document ${path}/${id} not found`);\n return [data, setData] as const;\n}\n\nexport function useDocumentOptional<T>(\n path: string,\n id: string,\n converter: FirestoreDataConverter<T>,\n) {\n const db = useDb();\n const { showBoundary } = useErrorBoundary();\n\n const ref = doc(db, path, id).withConverter(converter);\n\n const swrConfig: SWRConfiguration = {\n revalidateIfStale: false,\n revalidateOnFocus: false,\n revalidateOnMount: false,\n revalidateOnReconnect: false,\n suspense: true,\n };\n\n const { data, mutate } = useSWR<[T?]>(ref.path, () => fetcher(ref), swrConfig);\n\n useSubscriptionListener<[T?]>(\n ref.path,\n (setData) => {\n const unsubscribe = onSnapshot(\n ref,\n (snap) => {\n try {\n setData([snap.data()]);\n } catch (err) {\n showBoundary(err);\n }\n },\n (error) => showBoundary(error),\n );\n return () => unsubscribe();\n },\n (data) => mutate(data, mutationConfig),\n );\n\n const setData = useCallback((newData: T) => setDocument<T>(ref, newData, mutate), [mutate, ref]);\n return [data![0], setData] as const;\n}\n\nasync function setDocument<T>(ref: DocumentReference<T>, newDoc: T, mutator: KeyedMutator<[T?]>) {\n await mutator(\n async () => {\n await setDoc(ref, newDoc);\n return [newDoc];\n },\n { ...mutationConfig, optimisticData: [newDoc] },\n );\n}\n\nasync function fetcher<T>(ref: DocumentReference<T>): Promise<[T?]> {\n const snapshot = await getDoc(ref);\n if (!snapshot.exists()) {\n return [undefined];\n }\n return [snapshot.data({ serverTimestamps: \"estimate\" })];\n}\n"],"names":["mutationConfig","useDocument","path","id","converter","data","setData","useDocumentOptional","db","useDb","showBoundary","useErrorBoundary","ref","doc","swrConfig","mutate","useSWR","fetcher","useSubscriptionListener","unsubscribe","onSnapshot","snap","err","error","useCallback","newData","setDocument","newDoc","mutator","setDoc","snapshot","getDoc"],"mappings":";;;;;;AAiBA,MAAMA,IAAiC;AAAA,EACrC,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,eAAe;AACjB;AAEO,SAASC,EAAeC,GAAcC,GAAYC,GAAsC;AAC7F,QAAM,CAACC,GAAMC,CAAO,IAAIC,EAAuBL,GAAMC,GAAIC,CAAS;AAClE,MAAI,CAACC,EAAM,OAAM,IAAI,MAAM,YAAYH,CAAI,IAAIC,CAAE,YAAY;AAC7D,SAAO,CAACE,GAAMC,CAAO;AACvB;AAEO,SAASC,EACdL,GACAC,GACAC,GACA;AACA,QAAMI,IAAKC,EAAA,GACL,EAAE,cAAAC,EAAA,IAAiBC,EAAA,GAEnBC,IAAMC,EAAIL,GAAIN,GAAMC,CAAE,EAAE,cAAcC,CAAS,GAE/CU,IAA8B;AAAA,IAClC,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,UAAU;AAAA,EAAA,GAGN,EAAE,MAAAT,GAAM,QAAAU,EAAA,IAAWC,EAAaJ,EAAI,MAAM,MAAMK,EAAQL,CAAG,GAAGE,CAAS;AAE7E,EAAAI;AAAA,IACEN,EAAI;AAAA,IACJ,CAACN,MAAY;AACX,YAAMa,IAAcC;AAAA,QAClBR;AAAA,QACA,CAACS,MAAS;AACR,cAAI;AACFf,YAAAA,EAAQ,CAACe,EAAK,KAAA,CAAM,CAAC;AAAA,UACvB,SAASC,GAAK;AACZ,YAAAZ,EAAaY,
|
|
1
|
+
{"version":3,"file":"document.js","sources":["../../../../src/web/hooks/document.ts"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport {\n type DocumentReference,\n doc,\n type FirestoreDataConverter,\n getDoc,\n onSnapshot,\n setDoc,\n} from \"firebase/firestore\";\nimport { useErrorBoundary } from \"react-error-boundary\";\nimport useSWR, { type KeyedMutator, type MutatorOptions, type SWRConfiguration } from \"swr\";\n\nimport { useDb } from \"~/web/common/base-login\";\n\nimport { useSubscriptionListener } from \"./subscription\";\n\nconst mutationConfig: MutatorOptions = {\n revalidate: false,\n rollbackOnError: true,\n populateCache: true,\n};\n\nexport function useDocument<T>(path: string, id: string, converter: FirestoreDataConverter<T>) {\n const [data, setData] = useDocumentOptional<T>(path, id, converter);\n if (!data) throw new Error(`Document ${path}/${id} not found`);\n return [data, setData] as const;\n}\n\nexport function useDocumentOptional<T>(\n path: string,\n id: string,\n converter: FirestoreDataConverter<T>,\n) {\n const db = useDb();\n const { showBoundary } = useErrorBoundary();\n\n const ref = doc(db, path, id).withConverter(converter);\n\n const swrConfig: SWRConfiguration = {\n revalidateIfStale: false,\n revalidateOnFocus: false,\n revalidateOnMount: false,\n revalidateOnReconnect: false,\n suspense: true,\n };\n\n const { data, mutate } = useSWR<[T?]>(ref.path, () => fetcher(ref), swrConfig);\n\n useSubscriptionListener<[T?]>(\n ref.path,\n (setData) => {\n const unsubscribe = onSnapshot(\n ref,\n (snap) => {\n try {\n setData([snap.data()]);\n } catch (err) {\n showBoundary(err as Error);\n }\n },\n (error) => showBoundary(error),\n );\n return () => unsubscribe();\n },\n (data) => mutate(data, mutationConfig),\n );\n\n const setData = useCallback((newData: T) => setDocument<T>(ref, newData, mutate), [mutate, ref]);\n return [data![0], setData] as const;\n}\n\nasync function setDocument<T>(ref: DocumentReference<T>, newDoc: T, mutator: KeyedMutator<[T?]>) {\n await mutator(\n async () => {\n await setDoc(ref, newDoc);\n return [newDoc];\n },\n { ...mutationConfig, optimisticData: [newDoc] },\n );\n}\n\nasync function fetcher<T>(ref: DocumentReference<T>): Promise<[T?]> {\n const snapshot = await getDoc(ref);\n if (!snapshot.exists()) {\n return [undefined];\n }\n return [snapshot.data({ serverTimestamps: \"estimate\" })];\n}\n"],"names":["mutationConfig","useDocument","path","id","converter","data","setData","useDocumentOptional","db","useDb","showBoundary","useErrorBoundary","ref","doc","swrConfig","mutate","useSWR","fetcher","useSubscriptionListener","unsubscribe","onSnapshot","snap","err","error","useCallback","newData","setDocument","newDoc","mutator","setDoc","snapshot","getDoc"],"mappings":";;;;;;AAiBA,MAAMA,IAAiC;AAAA,EACrC,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,eAAe;AACjB;AAEO,SAASC,EAAeC,GAAcC,GAAYC,GAAsC;AAC7F,QAAM,CAACC,GAAMC,CAAO,IAAIC,EAAuBL,GAAMC,GAAIC,CAAS;AAClE,MAAI,CAACC,EAAM,OAAM,IAAI,MAAM,YAAYH,CAAI,IAAIC,CAAE,YAAY;AAC7D,SAAO,CAACE,GAAMC,CAAO;AACvB;AAEO,SAASC,EACdL,GACAC,GACAC,GACA;AACA,QAAMI,IAAKC,EAAA,GACL,EAAE,cAAAC,EAAA,IAAiBC,EAAA,GAEnBC,IAAMC,EAAIL,GAAIN,GAAMC,CAAE,EAAE,cAAcC,CAAS,GAE/CU,IAA8B;AAAA,IAClC,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,UAAU;AAAA,EAAA,GAGN,EAAE,MAAAT,GAAM,QAAAU,EAAA,IAAWC,EAAaJ,EAAI,MAAM,MAAMK,EAAQL,CAAG,GAAGE,CAAS;AAE7E,EAAAI;AAAA,IACEN,EAAI;AAAA,IACJ,CAACN,MAAY;AACX,YAAMa,IAAcC;AAAA,QAClBR;AAAA,QACA,CAACS,MAAS;AACR,cAAI;AACFf,YAAAA,EAAQ,CAACe,EAAK,KAAA,CAAM,CAAC;AAAA,UACvB,SAASC,GAAK;AACZ,YAAAZ,EAAaY,CAAY;AAAA,UAC3B;AAAA,QACF;AAAA,QACA,CAACC,MAAUb,EAAaa,CAAK;AAAA,MAAA;AAE/B,aAAO,MAAMJ,EAAA;AAAA,IACf;AAAA,IACA,CAACd,MAASU,EAAOV,GAAML,CAAc;AAAA,EAAA;AAGvC,QAAMM,IAAUkB,EAAY,CAACC,MAAeC,EAAed,GAAKa,GAASV,CAAM,GAAG,CAACA,GAAQH,CAAG,CAAC;AAC/F,SAAO,CAACP,EAAM,CAAC,GAAGC,CAAO;AAC3B;AAEA,eAAeoB,EAAed,GAA2Be,GAAWC,GAA6B;AAC/F,QAAMA;AAAA,IACJ,aACE,MAAMC,EAAOjB,GAAKe,CAAM,GACjB,CAACA,CAAM;AAAA,IAEhB,EAAE,GAAG3B,GAAgB,gBAAgB,CAAC2B,CAAM,EAAA;AAAA,EAAE;AAElD;AAEA,eAAeV,EAAWL,GAA0C;AAClE,QAAMkB,IAAW,MAAMC,EAAOnB,CAAG;AACjC,SAAKkB,EAAS,WAGP,CAACA,EAAS,KAAK,EAAE,kBAAkB,WAAA,CAAY,CAAC,IAF9C,CAAC,MAAS;AAGrB;"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olinfo/quizms-firebase",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Alessandro Bortolin <bortolin.alessandro@outlook.it>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,40 +26,40 @@
|
|
|
26
26
|
"dist"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@inquirer/prompts": "^8.1
|
|
30
|
-
"@olinfo/react-components": "^0.12.
|
|
31
|
-
"@trpc/client": "^11.
|
|
32
|
-
"@trpc/server": "^11.
|
|
29
|
+
"@inquirer/prompts": "^8.2.1",
|
|
30
|
+
"@olinfo/react-components": "^0.12.1",
|
|
31
|
+
"@trpc/client": "^11.10.0",
|
|
32
|
+
"@trpc/server": "^11.10.0",
|
|
33
33
|
"@types/cli-progress": "^3.11.6",
|
|
34
34
|
"@types/express": "^5.0.6",
|
|
35
|
-
"@types/express-serve-static-core": "^5.1.
|
|
35
|
+
"@types/express-serve-static-core": "^5.1.1",
|
|
36
36
|
"@types/lodash-es": "^4.17.12",
|
|
37
37
|
"@types/picomatch": "^4.0.2",
|
|
38
|
-
"ag-grid-community": "^35.
|
|
38
|
+
"ag-grid-community": "^35.1.0",
|
|
39
39
|
"cli-progress": "^3.12.0",
|
|
40
40
|
"clsx": "^2.1.1",
|
|
41
|
-
"commander": "^14.0.
|
|
41
|
+
"commander": "^14.0.3",
|
|
42
42
|
"date-fns": "^4.1.0",
|
|
43
43
|
"express": "^5.2.1",
|
|
44
|
-
"fastify": "^5.
|
|
45
|
-
"firebase": "^12.
|
|
46
|
-
"firebase-admin": "^13.6.
|
|
47
|
-
"firebase-functions": "^7.0.
|
|
48
|
-
"lodash-es": "^4.17.
|
|
44
|
+
"fastify": "^5.7.4",
|
|
45
|
+
"firebase": "^12.9.0",
|
|
46
|
+
"firebase-admin": "^13.6.1",
|
|
47
|
+
"firebase-functions": "^7.0.5",
|
|
48
|
+
"lodash-es": "^4.17.23",
|
|
49
49
|
"lucide-react": "^0.562.0",
|
|
50
50
|
"picomatch": "^4.0.3",
|
|
51
|
-
"react-error-boundary": "
|
|
51
|
+
"react-error-boundary": "~6.0.3",
|
|
52
52
|
"react-markdown": "^10.1.0",
|
|
53
53
|
"rollup-plugin-node-externals": "^8.1.2",
|
|
54
54
|
"rollup-preserve-directives": "^1.1.3",
|
|
55
55
|
"superjson": "^2.2.6",
|
|
56
|
-
"swr": "^2.
|
|
56
|
+
"swr": "^2.4.0",
|
|
57
57
|
"tinyglobby": "^0.2.15",
|
|
58
|
-
"vite": "^7.3.
|
|
58
|
+
"vite": "^7.3.1",
|
|
59
59
|
"vite-plugin-dts": "^4.5.4",
|
|
60
60
|
"wouter": "^3.9.0",
|
|
61
|
-
"zod": "^4.
|
|
62
|
-
"@olinfo/quizms": "^4.1.
|
|
61
|
+
"zod": "^4.3.6",
|
|
62
|
+
"@olinfo/quizms": "^4.1.2"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
65
|
"react": "~19.1.4"
|
|
@@ -70,12 +70,12 @@
|
|
|
70
70
|
}
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
|
-
"@biomejs/biome": "^2.
|
|
74
|
-
"@google-cloud/storage": "^7.
|
|
75
|
-
"@types/react": "^19.2.
|
|
73
|
+
"@biomejs/biome": "^2.4.2",
|
|
74
|
+
"@google-cloud/storage": "^7.19.0",
|
|
75
|
+
"@types/react": "^19.2.14",
|
|
76
76
|
"@types/wicg-file-system-access": "^2023.10.7",
|
|
77
77
|
"typescript": "5.8.2",
|
|
78
|
-
"vite-tsconfig-paths": "^6.
|
|
78
|
+
"vite-tsconfig-paths": "^6.1.1"
|
|
79
79
|
},
|
|
80
80
|
"scripts": {
|
|
81
81
|
"prebuild": "biome check && tsc --noEmit",
|