@olinfo/quizms-firebase 4.1.3 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/cli.js +14 -14
  2. package/dist/cli.js.map +1 -1
  3. package/dist/package.json.js +1 -1
  4. package/dist/src/cli/definalize.js +17 -17
  5. package/dist/src/cli/definalize.js.map +1 -1
  6. package/dist/src/cli/export.js +42 -34
  7. package/dist/src/cli/export.js.map +1 -1
  8. package/dist/src/cli/files/firestore-indexes.json.js +1 -1
  9. package/dist/src/cli/files/firestore.rules.js +27 -27
  10. package/dist/src/cli/files/firestore.rules.js.map +1 -1
  11. package/dist/src/cli/files/functions-index.js +3 -3
  12. package/dist/src/cli/files/functions-index.js.map +1 -1
  13. package/dist/src/cli/files/storage.rules.js +3 -3
  14. package/dist/src/cli/files/storage.rules.js.map +1 -1
  15. package/dist/src/cli/import.js +65 -71
  16. package/dist/src/cli/import.js.map +1 -1
  17. package/dist/src/cli/init.js +1 -1
  18. package/dist/src/cli/init.js.map +1 -1
  19. package/dist/src/cli/update-scores.js +7 -7
  20. package/dist/src/cli/update-scores.js.map +1 -1
  21. package/dist/src/cli/utils/converters-admin.js +23 -22
  22. package/dist/src/cli/utils/converters-admin.js.map +1 -1
  23. package/dist/src/functions/api/index.js +18 -18
  24. package/dist/src/functions/api/index.js.map +1 -1
  25. package/dist/src/functions/api/queries.js +21 -21
  26. package/dist/src/functions/api/queries.js.map +1 -1
  27. package/dist/src/functions/api/student.js +46 -47
  28. package/dist/src/functions/api/student.js.map +1 -1
  29. package/dist/src/functions/api/teacher.js +92 -95
  30. package/dist/src/functions/api/teacher.js.map +1 -1
  31. package/dist/src/functions/submissions.js +11 -7
  32. package/dist/src/functions/submissions.js.map +1 -1
  33. package/dist/src/functions/update-scores.js +14 -14
  34. package/dist/src/functions/update-scores.js.map +1 -1
  35. package/dist/src/models/submission.js +13 -0
  36. package/dist/src/models/submission.js.map +1 -0
  37. package/dist/src/web/admin/announcements.js +45 -42
  38. package/dist/src/web/admin/announcements.js.map +1 -1
  39. package/dist/src/web/admin/dashboard.js +43 -43
  40. package/dist/src/web/admin/dashboard.js.map +1 -1
  41. package/dist/src/web/admin/export.js +59 -54
  42. package/dist/src/web/admin/export.js.map +1 -1
  43. package/dist/src/web/admin/provider.js +28 -28
  44. package/dist/src/web/admin/provider.js.map +1 -1
  45. package/dist/src/web/admin/school-table.js +20 -24
  46. package/dist/src/web/admin/school-table.js.map +1 -1
  47. package/dist/src/web/common/api.js +25 -25
  48. package/dist/src/web/common/api.js.map +1 -1
  49. package/dist/src/web/common/converters.js +38 -39
  50. package/dist/src/web/common/converters.js.map +1 -1
  51. package/dist/src/web/entry/student-statement.js +1 -1
  52. package/dist/src/web/entry/student-statement.js.map +1 -1
  53. package/dist/src/web/entry/student.js +80 -75
  54. package/dist/src/web/entry/student.js.map +1 -1
  55. package/dist/src/web/entry/teacher.js +63 -62
  56. package/dist/src/web/entry/teacher.js.map +1 -1
  57. package/package.json +35 -26
package/dist/cli.js CHANGED
@@ -1,36 +1,36 @@
1
1
  import { existsSync as n } from "node:fs";
2
2
  import { homedir as a } from "node:os";
3
- import e from "node:path";
3
+ import i from "node:path";
4
4
  import { exit as c, cwd as m, chdir as d } from "node:process";
5
5
  import { fatal as f } from "@olinfo/quizms/utils-node";
6
- import { program as t, Option as r } from "commander";
7
- import { version as s } from "./package.json.js";
6
+ import { program as t, Option as s } from "commander";
7
+ import { version as r } from "./package.json.js";
8
8
  import l from "./src/cli/definalize.js";
9
9
  import h from "./src/cli/export.js";
10
10
  import u from "./src/cli/import.js";
11
11
  import x from "./src/cli/init.js";
12
- import g from "./src/cli/update-scores.js";
13
- function I() {
12
+ import v from "./src/cli/update-scores.js";
13
+ function g() {
14
14
  const p = a();
15
15
  let o = m();
16
16
  for (; ; ) {
17
- if (n(e.join(o, "package.json")) && n(e.join(o, "src"))) {
17
+ if (n(i.join(o, "package.json")) && n(i.join(o, "src"))) {
18
18
  d(o);
19
19
  return;
20
20
  }
21
- const i = e.dirname(o);
22
- (i === o || i === p) && f("Invalid directory. Make sure you're inside a QuizMS project."), o = i;
21
+ const e = i.dirname(o);
22
+ (e === o || e === p) && f("Invalid directory. Make sure you're inside a QuizMS project."), o = e;
23
23
  }
24
24
  }
25
- async function v() {
26
- t.addHelpText("beforeAll", `QuizMS-firebase cli v${s}
27
- `), t.name("quizms-firebase"), t.version(s), t.command("init").description("Initialize the Firebase project.").option("--force", "Overwrite existing files.").action(x), t.command("export").description("Export the contests data.").option("--contests", "Export the contests.").option("--participations", "Export the participations.").option("--students", "Export the students.").option("--variants", "Export the variants.").action(h), t.command("import").description("Import the contests data.").option("-c, --config <config>", "The contests config file.").option("--admins", "Import the admins.").option("--contests", "Import the contests.").option("--schools", "Import the schools.").option("--statements", "Import the statements.").option("--students", "Import the students.").option("--teachers", "Import the teachers.").option("--variants", "Import the variants.").option("--websites", "Import the websites.").option("-d, --delete", "Delete existing collections.").addOption(new r("-s, --skip-existing", "Skip existing files.").conflicts(["--delete"])).addOption(
28
- new r("-f, --force", "Overwrite existing documents.").conflicts([
25
+ async function I() {
26
+ t.addHelpText("beforeAll", `QuizMS-firebase cli v${r}
27
+ `), t.name("quizms-firebase"), t.version(r), t.command("init").description("Initialize the Firebase project.").option("--force", "Overwrite existing files.").action(x), t.command("export").description("Export the contests data.").option("--contests", "Export the contests.").option("--venues", "Export the venues.").option("--students", "Export the students.").option("--submissions", "Export the submissions.").option("--variants", "Export the variants.").action(h), t.command("import").description("Import the contests data.").option("-c, --config <config>", "The contests config file.").option("--admins", "Import the admins.").option("--contests", "Import the contests.").option("--schools", "Import the schools.").option("--statements", "Import the statements.").option("--students", "Import the students.").option("--teachers", "Import the teachers.").option("--variants", "Import the variants.").option("--websites", "Import the websites.").option("-d, --delete", "Delete existing collections.").addOption(new s("-s, --skip-existing", "Skip existing files.").conflicts(["--delete"])).addOption(
28
+ new s("-f, --force", "Overwrite existing documents.").conflicts([
29
29
  "--delete",
30
30
  "--skip-existing"
31
31
  ])
32
- ).action(u), t.command("definalize").description("Definalize all participations.").action(l), t.command("update-scores").description("Update student scores.").action(g), I(), await t.parseAsync();
32
+ ).action(u), t.command("definalize").description("Definalize all venues.").action(l), t.command("update-scores").description("Update student scores.").action(v), g(), await t.parseAsync();
33
33
  }
34
- await v();
34
+ await I();
35
35
  c(0);
36
36
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sources":["../src/cli/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { chdir, cwd, exit } from \"node:process\";\n\nimport { fatal } from \"@olinfo/quizms/utils-node\";\nimport { Option, program } from \"commander\";\nimport { version } from \"package.json\";\n\nimport definalize from \"./definalize\";\nimport exportData from \"./export\";\nimport importData from \"./import\";\nimport init from \"./init\";\nimport updateScores from \"./update-scores\";\n\nfunction findProjectDirectory() {\n const home = homedir();\n let root = cwd();\n for (;;) {\n if (existsSync(path.join(root, \"package.json\")) && existsSync(path.join(root, \"src\"))) {\n chdir(root);\n return;\n }\n const parent = path.dirname(root);\n if (parent === root || parent === home) {\n fatal(\"Invalid directory. Make sure you're inside a QuizMS project.\");\n }\n root = parent;\n }\n}\n\nasync function main() {\n program.addHelpText(\"beforeAll\", `QuizMS-firebase cli v${version}\\n`);\n program.name(\"quizms-firebase\");\n program.version(version);\n\n program\n .command(\"init\")\n .description(\"Initialize the Firebase project.\")\n .option(\"--force\", \"Overwrite existing files.\")\n .action(init);\n\n program\n .command(\"export\")\n .description(\"Export the contests data.\")\n .option(\"--contests\", \"Export the contests.\")\n .option(\"--participations\", \"Export the participations.\")\n .option(\"--students\", \"Export the students.\")\n .option(\"--variants\", \"Export the variants.\")\n .action(exportData);\n\n program\n .command(\"import\")\n .description(\"Import the contests data.\")\n .option(\"-c, --config <config>\", \"The contests config file.\")\n .option(\"--admins\", \"Import the admins.\")\n .option(\"--contests\", \"Import the contests.\")\n .option(\"--schools\", \"Import the schools.\")\n .option(\"--statements\", \"Import the statements.\")\n .option(\"--students\", \"Import the students.\")\n .option(\"--teachers\", \"Import the teachers.\")\n .option(\"--variants\", \"Import the variants.\")\n .option(\"--websites\", \"Import the websites.\")\n .option(\"-d, --delete\", \"Delete existing collections.\")\n .addOption(new Option(\"-s, --skip-existing\", \"Skip existing files.\").conflicts([\"--delete\"]))\n .addOption(\n new Option(\"-f, --force\", \"Overwrite existing documents.\").conflicts([\n \"--delete\",\n \"--skip-existing\",\n ]),\n )\n .action(importData);\n\n program.command(\"definalize\").description(\"Definalize all participations.\").action(definalize);\n program.command(\"update-scores\").description(\"Update student scores.\").action(updateScores);\n\n findProjectDirectory();\n await program.parseAsync();\n}\n\nawait main();\nexit(0);\n"],"names":["findProjectDirectory","home","homedir","root","cwd","existsSync","path","chdir","parent","fatal","main","program","version","init","exportData","Option","importData","definalize","updateScores","exit"],"mappings":";;;;;;;;;;;;AAeA,SAASA,IAAuB;AAC9B,QAAMC,IAAOC,EAAA;AACb,MAAIC,IAAOC,EAAA;AACX,aAAS;AACP,QAAIC,EAAWC,EAAK,KAAKH,GAAM,cAAc,CAAC,KAAKE,EAAWC,EAAK,KAAKH,GAAM,KAAK,CAAC,GAAG;AACrF,MAAAI,EAAMJ,CAAI;AACV;AAAA,IACF;AACA,UAAMK,IAASF,EAAK,QAAQH,CAAI;AAChC,KAAIK,MAAWL,KAAQK,MAAWP,MAChCQ,EAAM,8DAA8D,GAEtEN,IAAOK;AAAA,EACT;AACF;AAEA,eAAeE,IAAO;AACpB,EAAAC,EAAQ,YAAY,aAAa,wBAAwBC,CAAO;AAAA,CAAI,GACpED,EAAQ,KAAK,iBAAiB,GAC9BA,EAAQ,QAAQC,CAAO,GAEvBD,EACG,QAAQ,MAAM,EACd,YAAY,kCAAkC,EAC9C,OAAO,WAAW,2BAA2B,EAC7C,OAAOE,CAAI,GAEdF,EACG,QAAQ,QAAQ,EAChB,YAAY,2BAA2B,EACvC,OAAO,cAAc,sBAAsB,EAC3C,OAAO,oBAAoB,4BAA4B,EACvD,OAAO,cAAc,sBAAsB,EAC3C,OAAO,cAAc,sBAAsB,EAC3C,OAAOG,CAAU,GAEpBH,EACG,QAAQ,QAAQ,EAChB,YAAY,2BAA2B,EACvC,OAAO,yBAAyB,2BAA2B,EAC3D,OAAO,YAAY,oBAAoB,EACvC,OAAO,cAAc,sBAAsB,EAC3C,OAAO,aAAa,qBAAqB,EACzC,OAAO,gBAAgB,wBAAwB,EAC/C,OAAO,cAAc,sBAAsB,EAC3C,OAAO,cAAc,sBAAsB,EAC3C,OAAO,cAAc,sBAAsB,EAC3C,OAAO,cAAc,sBAAsB,EAC3C,OAAO,gBAAgB,8BAA8B,EACrD,UAAU,IAAII,EAAO,uBAAuB,sBAAsB,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC,EAC3F;AAAA,IACC,IAAIA,EAAO,eAAe,+BAA+B,EAAE,UAAU;AAAA,MACnE;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EAAA,EAEF,OAAOC,CAAU,GAEpBL,EAAQ,QAAQ,YAAY,EAAE,YAAY,gCAAgC,EAAE,OAAOM,CAAU,GAC7FN,EAAQ,QAAQ,eAAe,EAAE,YAAY,wBAAwB,EAAE,OAAOO,CAAY,GAE1FlB,EAAA,GACA,MAAMW,EAAQ,WAAA;AAChB;AAEA,MAAMD,EAAA;AACNS,EAAK,CAAC;"}
1
+ {"version":3,"file":"cli.js","sources":["../src/cli/index.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport { chdir, cwd, exit } from \"node:process\";\n\nimport { fatal } from \"@olinfo/quizms/utils-node\";\nimport { Option, program } from \"commander\";\nimport { version } from \"package.json\";\n\nimport definalize from \"./definalize\";\nimport exportData from \"./export\";\nimport importData from \"./import\";\nimport init from \"./init\";\nimport updateScores from \"./update-scores\";\n\nfunction findProjectDirectory() {\n const home = homedir();\n let root = cwd();\n for (;;) {\n if (existsSync(path.join(root, \"package.json\")) && existsSync(path.join(root, \"src\"))) {\n chdir(root);\n return;\n }\n const parent = path.dirname(root);\n if (parent === root || parent === home) {\n fatal(\"Invalid directory. Make sure you're inside a QuizMS project.\");\n }\n root = parent;\n }\n}\n\nasync function main() {\n program.addHelpText(\"beforeAll\", `QuizMS-firebase cli v${version}\\n`);\n program.name(\"quizms-firebase\");\n program.version(version);\n\n program\n .command(\"init\")\n .description(\"Initialize the Firebase project.\")\n .option(\"--force\", \"Overwrite existing files.\")\n .action(init);\n\n program\n .command(\"export\")\n .description(\"Export the contests data.\")\n .option(\"--contests\", \"Export the contests.\")\n .option(\"--venues\", \"Export the venues.\")\n .option(\"--students\", \"Export the students.\")\n .option(\"--submissions\", \"Export the submissions.\")\n .option(\"--variants\", \"Export the variants.\")\n .action(exportData);\n\n program\n .command(\"import\")\n .description(\"Import the contests data.\")\n .option(\"-c, --config <config>\", \"The contests config file.\")\n .option(\"--admins\", \"Import the admins.\")\n .option(\"--contests\", \"Import the contests.\")\n .option(\"--schools\", \"Import the schools.\")\n .option(\"--statements\", \"Import the statements.\")\n .option(\"--students\", \"Import the students.\")\n .option(\"--teachers\", \"Import the teachers.\")\n .option(\"--variants\", \"Import the variants.\")\n .option(\"--websites\", \"Import the websites.\")\n .option(\"-d, --delete\", \"Delete existing collections.\")\n .addOption(new Option(\"-s, --skip-existing\", \"Skip existing files.\").conflicts([\"--delete\"]))\n .addOption(\n new Option(\"-f, --force\", \"Overwrite existing documents.\").conflicts([\n \"--delete\",\n \"--skip-existing\",\n ]),\n )\n .action(importData);\n\n program.command(\"definalize\").description(\"Definalize all venues.\").action(definalize);\n program.command(\"update-scores\").description(\"Update student scores.\").action(updateScores);\n\n findProjectDirectory();\n await program.parseAsync();\n}\n\nawait main();\nexit(0);\n"],"names":["findProjectDirectory","home","homedir","root","cwd","existsSync","path","chdir","parent","fatal","main","program","version","init","exportData","Option","importData","definalize","updateScores","exit"],"mappings":";;;;;;;;;;;;AAeA,SAASA,IAAuB;AAC9B,QAAMC,IAAOC,EAAA;AACb,MAAIC,IAAOC,EAAA;AACX,aAAS;AACP,QAAIC,EAAWC,EAAK,KAAKH,GAAM,cAAc,CAAC,KAAKE,EAAWC,EAAK,KAAKH,GAAM,KAAK,CAAC,GAAG;AACrF,MAAAI,EAAMJ,CAAI;AACV;AAAA,IACF;AACA,UAAMK,IAASF,EAAK,QAAQH,CAAI;AAChC,KAAIK,MAAWL,KAAQK,MAAWP,MAChCQ,EAAM,8DAA8D,GAEtEN,IAAOK;AAAA,EACT;AACF;AAEA,eAAeE,IAAO;AACpB,EAAAC,EAAQ,YAAY,aAAa,wBAAwBC,CAAO;AAAA,CAAI,GACpED,EAAQ,KAAK,iBAAiB,GAC9BA,EAAQ,QAAQC,CAAO,GAEvBD,EACG,QAAQ,MAAM,EACd,YAAY,kCAAkC,EAC9C,OAAO,WAAW,2BAA2B,EAC7C,OAAOE,CAAI,GAEdF,EACG,QAAQ,QAAQ,EAChB,YAAY,2BAA2B,EACvC,OAAO,cAAc,sBAAsB,EAC3C,OAAO,YAAY,oBAAoB,EACvC,OAAO,cAAc,sBAAsB,EAC3C,OAAO,iBAAiB,yBAAyB,EACjD,OAAO,cAAc,sBAAsB,EAC3C,OAAOG,CAAU,GAEpBH,EACG,QAAQ,QAAQ,EAChB,YAAY,2BAA2B,EACvC,OAAO,yBAAyB,2BAA2B,EAC3D,OAAO,YAAY,oBAAoB,EACvC,OAAO,cAAc,sBAAsB,EAC3C,OAAO,aAAa,qBAAqB,EACzC,OAAO,gBAAgB,wBAAwB,EAC/C,OAAO,cAAc,sBAAsB,EAC3C,OAAO,cAAc,sBAAsB,EAC3C,OAAO,cAAc,sBAAsB,EAC3C,OAAO,cAAc,sBAAsB,EAC3C,OAAO,gBAAgB,8BAA8B,EACrD,UAAU,IAAII,EAAO,uBAAuB,sBAAsB,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC,EAC3F;AAAA,IACC,IAAIA,EAAO,eAAe,+BAA+B,EAAE,UAAU;AAAA,MACnE;AAAA,MACA;AAAA,IAAA,CACD;AAAA,EAAA,EAEF,OAAOC,CAAU,GAEpBL,EAAQ,QAAQ,YAAY,EAAE,YAAY,wBAAwB,EAAE,OAAOM,CAAU,GACrFN,EAAQ,QAAQ,eAAe,EAAE,YAAY,wBAAwB,EAAE,OAAOO,CAAY,GAE1FlB,EAAA,GACA,MAAMW,EAAQ,WAAA;AAChB;AAEA,MAAMD,EAAA;AACNS,EAAK,CAAC;"}
@@ -1,4 +1,4 @@
1
- const e = "4.1.3", n = { "firebase-functions": "^7.0.5" }, s = {
1
+ const e = "5.0.1", n = { "firebase-functions": "^7.3.2" }, s = {
2
2
  version: e,
3
3
  dependencies: n
4
4
  };
@@ -1,23 +1,23 @@
1
- import { info as r, success as l } from "@olinfo/quizms/utils-node";
2
- import { sumBy as p } from "lodash-es";
3
- import { participationConverter as c } from "./utils/converters-admin.js";
4
- import { initializeFirebase as f } from "./utils/initialize.js";
5
- async function h() {
6
- const { db: a } = await f();
7
- r("Definalizing all participations.");
8
- const e = a.collection("participations").where("finalized", "==", !1).withConverter(c).limit(1e3);
9
- let i = await e.get(), t = 0;
10
- for (; !i.empty; ) {
11
- const o = await Promise.all(
12
- i.docs.map(async (n) => n.data().finalized ? (await a.doc(n.ref.path).update({ finalized: !1 }), 1) : 0)
1
+ import { info as s, success as o } from "@olinfo/quizms/utils-node";
2
+ import { sumBy as f } from "lodash-es";
3
+ import { venueConverter as u } from "./utils/converters-admin.js";
4
+ import { initializeFirebase as c } from "./utils/initialize.js";
5
+ async function z() {
6
+ const { db: t } = await c();
7
+ s("Definalizing all venues.");
8
+ const a = t.collection("venues").where("finalized", "==", !1).withConverter(u).limit(1e3);
9
+ let e = await a.get(), i = 0;
10
+ for (; !e.empty; ) {
11
+ const l = await Promise.all(
12
+ e.docs.map(async (n) => n.data().finalized ? (await t.doc(n.ref.path).update({ finalized: !1 }), 1) : 0)
13
13
  );
14
- t += p(o), r(`Definalized ${t} participations.`);
15
- const s = i.docs.at(-1);
16
- i = await e.startAfter(s).get();
14
+ i += f(l), s(`Definalized ${i} venues.`);
15
+ const r = e.docs.at(-1);
16
+ e = await a.startAfter(r).get();
17
17
  }
18
- l(`${t} participations were successfully definalized.`);
18
+ o(`${i} venues were successfully definalized.`);
19
19
  }
20
20
  export {
21
- h as default
21
+ z as default
22
22
  };
23
23
  //# sourceMappingURL=definalize.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"definalize.js","sources":["../../../src/cli/definalize.ts"],"sourcesContent":["import { info, success } from \"@olinfo/quizms/utils-node\";\nimport { sumBy } from \"lodash-es\";\n\nimport { participationConverter } from \"./utils/converters-admin\";\nimport { initializeFirebase } from \"./utils/initialize\";\n\nexport default async function definalize() {\n const { db } = await initializeFirebase();\n\n info(\"Definalizing all participations.\");\n\n const ref = db\n .collection(\"participations\")\n .where(\"finalized\", \"==\", false)\n .withConverter(participationConverter)\n .limit(1000);\n\n let snapshot = await ref.get();\n\n let sum = 0;\n while (!snapshot.empty) {\n const finalized = await Promise.all(\n snapshot.docs.map(async (doc) => {\n const participation = doc.data();\n if (participation.finalized) {\n await db.doc(doc.ref.path).update({ finalized: false });\n return 1;\n }\n return 0;\n }),\n );\n\n sum += sumBy(finalized);\n info(`Definalized ${sum} participations.`);\n\n const last = snapshot.docs.at(-1);\n snapshot = await ref.startAfter(last).get();\n }\n\n success(`${sum} participations were successfully definalized.`);\n}\n"],"names":["definalize","db","initializeFirebase","info","ref","participationConverter","snapshot","sum","finalized","doc","sumBy","last","success"],"mappings":";;;;AAMA,eAA8BA,IAAa;AACzC,QAAM,EAAE,IAAAC,MAAO,MAAMC,EAAA;AAErB,EAAAC,EAAK,kCAAkC;AAEvC,QAAMC,IAAMH,EACT,WAAW,gBAAgB,EAC3B,MAAM,aAAa,MAAM,EAAK,EAC9B,cAAcI,CAAsB,EACpC,MAAM,GAAI;AAEb,MAAIC,IAAW,MAAMF,EAAI,IAAA,GAErBG,IAAM;AACV,SAAO,CAACD,EAAS,SAAO;AACtB,UAAME,IAAY,MAAM,QAAQ;AAAA,MAC9BF,EAAS,KAAK,IAAI,OAAOG,MACDA,EAAI,KAAA,EACR,aAChB,MAAMR,EAAG,IAAIQ,EAAI,IAAI,IAAI,EAAE,OAAO,EAAE,WAAW,IAAO,GAC/C,KAEF,CACR;AAAA,IAAA;AAGH,IAAAF,KAAOG,EAAMF,CAAS,GACtBL,EAAK,eAAeI,CAAG,kBAAkB;AAEzC,UAAMI,IAAOL,EAAS,KAAK,GAAG,EAAE;AAChC,IAAAA,IAAW,MAAMF,EAAI,WAAWO,CAAI,EAAE,IAAA;AAAA,EACxC;AAEA,EAAAC,EAAQ,GAAGL,CAAG,gDAAgD;AAChE;"}
1
+ {"version":3,"file":"definalize.js","sources":["../../../src/cli/definalize.ts"],"sourcesContent":["import { info, success } from \"@olinfo/quizms/utils-node\";\nimport { sumBy } from \"lodash-es\";\n\nimport { venueConverter } from \"./utils/converters-admin\";\nimport { initializeFirebase } from \"./utils/initialize\";\n\nexport default async function definalize() {\n const { db } = await initializeFirebase();\n\n info(\"Definalizing all venues.\");\n\n const ref = db\n .collection(\"venues\")\n .where(\"finalized\", \"==\", false)\n .withConverter(venueConverter)\n .limit(1000);\n\n let snapshot = await ref.get();\n\n let sum = 0;\n while (!snapshot.empty) {\n const finalized = await Promise.all(\n snapshot.docs.map(async (doc) => {\n const venue = doc.data();\n if (venue.finalized) {\n await db.doc(doc.ref.path).update({ finalized: false });\n return 1;\n }\n return 0;\n }),\n );\n\n sum += sumBy(finalized);\n info(`Definalized ${sum} venues.`);\n\n const last = snapshot.docs.at(-1);\n snapshot = await ref.startAfter(last).get();\n }\n\n success(`${sum} venues were successfully definalized.`);\n}\n"],"names":["definalize","db","initializeFirebase","info","ref","venueConverter","snapshot","sum","finalized","doc","sumBy","last","success"],"mappings":";;;;AAMA,eAA8BA,IAAa;AACzC,QAAM,EAAE,IAAAC,MAAO,MAAMC,EAAA;AAErB,EAAAC,EAAK,0BAA0B;AAE/B,QAAMC,IAAMH,EACT,WAAW,QAAQ,EACnB,MAAM,aAAa,MAAM,EAAK,EAC9B,cAAcI,CAAc,EAC5B,MAAM,GAAI;AAEb,MAAIC,IAAW,MAAMF,EAAI,IAAA,GAErBG,IAAM;AACV,SAAO,CAACD,EAAS,SAAO;AACtB,UAAME,IAAY,MAAM,QAAQ;AAAA,MAC9BF,EAAS,KAAK,IAAI,OAAOG,MACTA,EAAI,KAAA,EACR,aACR,MAAMR,EAAG,IAAIQ,EAAI,IAAI,IAAI,EAAE,OAAO,EAAE,WAAW,IAAO,GAC/C,KAEF,CACR;AAAA,IAAA;AAGH,IAAAF,KAAOG,EAAMF,CAAS,GACtBL,EAAK,eAAeI,CAAG,UAAU;AAEjC,UAAMI,IAAOL,EAAS,KAAK,GAAG,EAAE;AAChC,IAAAA,IAAW,MAAMF,EAAI,WAAWO,CAAI,EAAE,IAAA;AAAA,EACxC;AAEA,EAAAC,EAAQ,GAAGL,CAAG,wCAAwC;AACxD;"}
@@ -1,54 +1,62 @@
1
- import { createWriteStream as p } from "node:fs";
2
- import { mkdir as m } from "node:fs/promises";
1
+ import { createWriteStream as f } from "node:fs";
2
+ import { mkdir as p } from "node:fs/promises";
3
3
  import s from "node:path";
4
- import { pipeline as f } from "node:stream/promises";
5
- import { info as l, success as u } from "@olinfo/quizms/utils-node";
4
+ import { pipeline as l } from "node:stream/promises";
5
+ import { info as u, success as v } from "@olinfo/quizms/utils-node";
6
6
  import { SingleBar as w } from "cli-progress";
7
7
  import { formatDistanceStrict as d } from "date-fns";
8
- import { deleteApp as v } from "firebase-admin/app";
9
- import { capitalize as C } from "lodash-es";
10
- import { studentConverter as h, participationConverter as x, variantConverter as b, contestConverter as g } from "./utils/converters-admin.js";
11
- import { initializeFirebase as S } from "./utils/initialize.js";
12
- async function F(e) {
13
- const { app: a, db: o } = await S(), n = (/* @__PURE__ */ new Date()).toISOString(), r = s.join("export", n);
14
- if (await m(r, { recursive: !0 }), e.students) {
15
- const t = o.collectionGroup("students").withConverter(h);
16
- await i(t, "students", r);
8
+ import { deleteApp as C } from "firebase-admin/app";
9
+ import { capitalize as b } from "lodash-es";
10
+ import { studentConverter as h, venueConverter as g, variantConverter as x, contestConverter as y, submissionConverter as S } from "./utils/converters-admin.js";
11
+ import { initializeFirebase as A } from "./utils/initialize.js";
12
+ async function G(o) {
13
+ const { app: i, db: r } = await A(), n = (/* @__PURE__ */ new Date()).toISOString(), e = s.join("export", n);
14
+ if (await p(e, { recursive: !0 }), o.students) {
15
+ const t = r.collectionGroup("students").withConverter(h);
16
+ await a(t, "students", e);
17
17
  }
18
- if (e.participations) {
19
- const t = o.collection("participations").withConverter(x);
20
- await i(t, "participations", r);
18
+ if (o.venues) {
19
+ const t = r.collection("venues").withConverter(g);
20
+ await a(t, "venues", e);
21
21
  }
22
- if (e.variants) {
23
- const t = o.collection("variants").withConverter(b);
24
- await i(t, "variants", r);
22
+ if (o.variants) {
23
+ const t = r.collection("variants").withConverter(x);
24
+ await a(t, "variants", e);
25
25
  }
26
- if (e.contests) {
27
- const t = o.collection("contests").withConverter(g);
28
- await i(t, "contests", r);
26
+ if (o.contests) {
27
+ const t = r.collection("contests").withConverter(y);
28
+ await a(t, "contests", e);
29
29
  }
30
- await v(a);
30
+ if (o.submissions) {
31
+ const t = r.collection("submissions").withConverter(S);
32
+ await a(t, "submissions", e);
33
+ }
34
+ await C(i);
31
35
  }
32
- async function i(e, a, o) {
33
- l(`Exporting ${a}.`);
34
- const n = await e.count().get(), r = new w({
36
+ async function a(o, i, r) {
37
+ u(`Exporting ${i}.`);
38
+ const n = await o.count().get(), e = new w({
35
39
  format: " {bar} {percentage}% | ETA: {eta_formatted} | {value}/{total}",
36
40
  barCompleteChar: "█",
37
41
  barIncompleteChar: "▂",
38
42
  etaBuffer: 1e4,
39
43
  formatTime: (t) => d(0, t * 1e3)
40
44
  });
41
- r.start(n.data().count, 0), await f(
42
- e.stream(),
43
- async function* (t) {
44
- for await (const c of t)
45
- r.increment(), yield `${JSON.stringify(c.data())}
45
+ e.start(n.data().count, 0), await l(
46
+ async function* () {
47
+ let t = await o.limit(1e3).get();
48
+ for (; !t.empty; ) {
49
+ for (const m of t.docs)
50
+ e.increment(), yield `${JSON.stringify(m.data())}
46
51
  `;
52
+ const c = t.docs.at(-1);
53
+ t = await o.startAfter(c).limit(1e3).get();
54
+ }
47
55
  },
48
- p(s.format({ dir: o, name: a, ext: ".jsonl" }))
49
- ), r.stop(), u(`${C(a)} export completed.`);
56
+ f(s.format({ dir: r, name: i, ext: ".jsonl" }))
57
+ ), e.stop(), v(`${b(i)} export completed.`);
50
58
  }
51
59
  export {
52
- F as default
60
+ G as default
53
61
  };
54
62
  //# sourceMappingURL=export.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"export.js","sources":["../../../src/cli/export.ts"],"sourcesContent":["import { createWriteStream } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\n\nimport { info, success } from \"@olinfo/quizms/utils-node\";\nimport { SingleBar } from \"cli-progress\";\nimport { formatDistanceStrict } from \"date-fns\";\nimport { deleteApp } from \"firebase-admin/app\";\nimport type { Query, QueryDocumentSnapshot } from \"firebase-admin/firestore\";\nimport { capitalize } from \"lodash-es\";\n\nimport {\n contestConverter,\n participationConverter,\n studentConverter,\n variantConverter,\n} from \"./utils/converters-admin\";\nimport { initializeFirebase } from \"./utils/initialize\";\n\ntype ExportOptions = {\n contests?: true;\n participations?: true;\n students?: true;\n variants?: true;\n};\n\nexport default async function exportContests(options: ExportOptions) {\n const { app, db } = await initializeFirebase();\n\n const timestamp = new Date().toISOString();\n const outDir = path.join(\"export\", timestamp);\n await mkdir(outDir, { recursive: true });\n\n if (options.students) {\n const ref = db.collectionGroup(\"students\").withConverter(studentConverter);\n await exportCollection(ref, \"students\", outDir);\n }\n if (options.participations) {\n const ref = db.collection(\"participations\").withConverter(participationConverter);\n await exportCollection(ref, \"participations\", outDir);\n }\n if (options.variants) {\n const ref = db.collection(\"variants\").withConverter(variantConverter);\n await exportCollection(ref, \"variants\", outDir);\n }\n if (options.contests) {\n const ref = db.collection(\"contests\").withConverter(contestConverter);\n await exportCollection(ref, \"contests\", outDir);\n }\n\n await deleteApp(app);\n}\n\nasync function exportCollection(ref: Query, collection: string, dir: string) {\n info(`Exporting ${collection}.`);\n\n const count = await ref.count().get();\n const bar = new SingleBar({\n format: \" {bar} {percentage}% | ETA: {eta_formatted} | {value}/{total}\",\n barCompleteChar: \"\\u2588\",\n barIncompleteChar: \"\\u2582\",\n etaBuffer: 10000,\n formatTime: (t) => formatDistanceStrict(0, t * 1000),\n });\n bar.start(count.data().count, 0);\n\n await pipeline(\n ref.stream(),\n async function* (stream) {\n for await (const data of stream) {\n bar.increment();\n const snapshot = data as unknown as QueryDocumentSnapshot;\n yield `${JSON.stringify(snapshot.data())}\\n`;\n }\n },\n createWriteStream(path.format({ dir, name: collection, ext: \".jsonl\" })),\n );\n\n bar.stop();\n success(`${capitalize(collection)} export completed.`);\n}\n"],"names":["exportContests","options","app","db","initializeFirebase","timestamp","outDir","path","mkdir","ref","studentConverter","exportCollection","participationConverter","variantConverter","contestConverter","deleteApp","collection","dir","info","count","bar","SingleBar","formatDistanceStrict","pipeline","stream","data","createWriteStream","success","capitalize"],"mappings":";;;;;;;;;;;AA2BA,eAA8BA,EAAeC,GAAwB;AACnE,QAAM,EAAE,KAAAC,GAAK,IAAAC,EAAA,IAAO,MAAMC,EAAA,GAEpBC,KAAY,oBAAI,KAAA,GAAO,YAAA,GACvBC,IAASC,EAAK,KAAK,UAAUF,CAAS;AAG5C,MAFA,MAAMG,EAAMF,GAAQ,EAAE,WAAW,IAAM,GAEnCL,EAAQ,UAAU;AACpB,UAAMQ,IAAMN,EAAG,gBAAgB,UAAU,EAAE,cAAcO,CAAgB;AACzE,UAAMC,EAAiBF,GAAK,YAAYH,CAAM;AAAA,EAChD;AACA,MAAIL,EAAQ,gBAAgB;AAC1B,UAAMQ,IAAMN,EAAG,WAAW,gBAAgB,EAAE,cAAcS,CAAsB;AAChF,UAAMD,EAAiBF,GAAK,kBAAkBH,CAAM;AAAA,EACtD;AACA,MAAIL,EAAQ,UAAU;AACpB,UAAMQ,IAAMN,EAAG,WAAW,UAAU,EAAE,cAAcU,CAAgB;AACpE,UAAMF,EAAiBF,GAAK,YAAYH,CAAM;AAAA,EAChD;AACA,MAAIL,EAAQ,UAAU;AACpB,UAAMQ,IAAMN,EAAG,WAAW,UAAU,EAAE,cAAcW,CAAgB;AACpE,UAAMH,EAAiBF,GAAK,YAAYH,CAAM;AAAA,EAChD;AAEA,QAAMS,EAAUb,CAAG;AACrB;AAEA,eAAeS,EAAiBF,GAAYO,GAAoBC,GAAa;AAC3E,EAAAC,EAAK,aAAaF,CAAU,GAAG;AAE/B,QAAMG,IAAQ,MAAMV,EAAI,MAAA,EAAQ,IAAA,GAC1BW,IAAM,IAAIC,EAAU;AAAA,IACxB,QAAQ;AAAA,IACR,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,WAAW;AAAA,IACX,YAAY,CAAC,MAAMC,EAAqB,GAAG,IAAI,GAAI;AAAA,EAAA,CACpD;AACD,EAAAF,EAAI,MAAMD,EAAM,KAAA,EAAO,OAAO,CAAC,GAE/B,MAAMI;AAAA,IACJd,EAAI,OAAA;AAAA,IACJ,iBAAiBe,GAAQ;AACvB,uBAAiBC,KAAQD;AACvB,QAAAJ,EAAI,UAAA,GAEJ,MAAM,GAAG,KAAK,UADGK,EACgB,KAAA,CAAM,CAAC;AAAA;AAAA,IAE5C;AAAA,IACAC,EAAkBnB,EAAK,OAAO,EAAE,KAAAU,GAAK,MAAMD,GAAY,KAAK,UAAU,CAAC;AAAA,EAAA,GAGzEI,EAAI,KAAA,GACJO,EAAQ,GAAGC,EAAWZ,CAAU,CAAC,oBAAoB;AACvD;"}
1
+ {"version":3,"file":"export.js","sources":["../../../src/cli/export.ts"],"sourcesContent":["import { createWriteStream } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { pipeline } from \"node:stream/promises\";\n\nimport { info, success } from \"@olinfo/quizms/utils-node\";\nimport { SingleBar } from \"cli-progress\";\nimport { formatDistanceStrict } from \"date-fns\";\nimport { deleteApp } from \"firebase-admin/app\";\nimport type { Query } from \"firebase-admin/firestore\";\nimport { capitalize } from \"lodash-es\";\n\nimport {\n contestConverter,\n studentConverter,\n submissionConverter,\n variantConverter,\n venueConverter,\n} from \"./utils/converters-admin\";\nimport { initializeFirebase } from \"./utils/initialize\";\n\ntype ExportOptions = {\n contests?: true;\n venues?: true;\n students?: true;\n submissions?: true;\n variants?: true;\n};\n\nexport default async function exportContests(options: ExportOptions) {\n const { app, db } = await initializeFirebase();\n\n const timestamp = new Date().toISOString();\n const outDir = path.join(\"export\", timestamp);\n await mkdir(outDir, { recursive: true });\n\n if (options.students) {\n const ref = db.collectionGroup(\"students\").withConverter(studentConverter);\n await exportCollection(ref, \"students\", outDir);\n }\n if (options.venues) {\n const ref = db.collection(\"venues\").withConverter(venueConverter);\n await exportCollection(ref, \"venues\", outDir);\n }\n if (options.variants) {\n const ref = db.collection(\"variants\").withConverter(variantConverter);\n await exportCollection(ref, \"variants\", outDir);\n }\n if (options.contests) {\n const ref = db.collection(\"contests\").withConverter(contestConverter);\n await exportCollection(ref, \"contests\", outDir);\n }\n if (options.submissions) {\n const ref = db.collection(\"submissions\").withConverter(submissionConverter);\n await exportCollection(ref, \"submissions\", outDir);\n }\n\n await deleteApp(app);\n}\n\nasync function exportCollection(ref: Query, collection: string, dir: string) {\n info(`Exporting ${collection}.`);\n\n const count = await ref.count().get();\n const bar = new SingleBar({\n format: \" {bar} {percentage}% | ETA: {eta_formatted} | {value}/{total}\",\n barCompleteChar: \"\\u2588\",\n barIncompleteChar: \"\\u2582\",\n etaBuffer: 10000,\n formatTime: (t) => formatDistanceStrict(0, t * 1000),\n });\n bar.start(count.data().count, 0);\n\n await pipeline(\n async function* () {\n let snapshot = await ref.limit(1000).get();\n\n while (!snapshot.empty) {\n for (const doc of snapshot.docs) {\n bar.increment();\n yield `${JSON.stringify(doc.data())}\\n`;\n }\n const last = snapshot.docs.at(-1);\n snapshot = await ref.startAfter(last).limit(1000).get();\n }\n },\n createWriteStream(path.format({ dir, name: collection, ext: \".jsonl\" })),\n );\n\n bar.stop();\n success(`${capitalize(collection)} export completed.`);\n}\n"],"names":["exportContests","options","app","db","initializeFirebase","timestamp","outDir","path","mkdir","ref","studentConverter","exportCollection","venueConverter","variantConverter","contestConverter","submissionConverter","deleteApp","collection","dir","info","count","bar","SingleBar","formatDistanceStrict","pipeline","snapshot","doc","last","createWriteStream","success","capitalize"],"mappings":";;;;;;;;;;;AA6BA,eAA8BA,EAAeC,GAAwB;AACnE,QAAM,EAAE,KAAAC,GAAK,IAAAC,EAAA,IAAO,MAAMC,EAAA,GAEpBC,KAAY,oBAAI,KAAA,GAAO,YAAA,GACvBC,IAASC,EAAK,KAAK,UAAUF,CAAS;AAG5C,MAFA,MAAMG,EAAMF,GAAQ,EAAE,WAAW,IAAM,GAEnCL,EAAQ,UAAU;AACpB,UAAMQ,IAAMN,EAAG,gBAAgB,UAAU,EAAE,cAAcO,CAAgB;AACzE,UAAMC,EAAiBF,GAAK,YAAYH,CAAM;AAAA,EAChD;AACA,MAAIL,EAAQ,QAAQ;AAClB,UAAMQ,IAAMN,EAAG,WAAW,QAAQ,EAAE,cAAcS,CAAc;AAChE,UAAMD,EAAiBF,GAAK,UAAUH,CAAM;AAAA,EAC9C;AACA,MAAIL,EAAQ,UAAU;AACpB,UAAMQ,IAAMN,EAAG,WAAW,UAAU,EAAE,cAAcU,CAAgB;AACpE,UAAMF,EAAiBF,GAAK,YAAYH,CAAM;AAAA,EAChD;AACA,MAAIL,EAAQ,UAAU;AACpB,UAAMQ,IAAMN,EAAG,WAAW,UAAU,EAAE,cAAcW,CAAgB;AACpE,UAAMH,EAAiBF,GAAK,YAAYH,CAAM;AAAA,EAChD;AACA,MAAIL,EAAQ,aAAa;AACvB,UAAMQ,IAAMN,EAAG,WAAW,aAAa,EAAE,cAAcY,CAAmB;AAC1E,UAAMJ,EAAiBF,GAAK,eAAeH,CAAM;AAAA,EACnD;AAEA,QAAMU,EAAUd,CAAG;AACrB;AAEA,eAAeS,EAAiBF,GAAYQ,GAAoBC,GAAa;AAC3E,EAAAC,EAAK,aAAaF,CAAU,GAAG;AAE/B,QAAMG,IAAQ,MAAMX,EAAI,MAAA,EAAQ,IAAA,GAC1BY,IAAM,IAAIC,EAAU;AAAA,IACxB,QAAQ;AAAA,IACR,iBAAiB;AAAA,IACjB,mBAAmB;AAAA,IACnB,WAAW;AAAA,IACX,YAAY,CAAC,MAAMC,EAAqB,GAAG,IAAI,GAAI;AAAA,EAAA,CACpD;AACD,EAAAF,EAAI,MAAMD,EAAM,KAAA,EAAO,OAAO,CAAC,GAE/B,MAAMI;AAAA,IACJ,mBAAmB;AACjB,UAAIC,IAAW,MAAMhB,EAAI,MAAM,GAAI,EAAE,IAAA;AAErC,aAAO,CAACgB,EAAS,SAAO;AACtB,mBAAWC,KAAOD,EAAS;AACzB,UAAAJ,EAAI,UAAA,GACJ,MAAM,GAAG,KAAK,UAAUK,EAAI,KAAA,CAAM,CAAC;AAAA;AAErC,cAAMC,IAAOF,EAAS,KAAK,GAAG,EAAE;AAChC,QAAAA,IAAW,MAAMhB,EAAI,WAAWkB,CAAI,EAAE,MAAM,GAAI,EAAE,IAAA;AAAA,MACpD;AAAA,IACF;AAAA,IACAC,EAAkBrB,EAAK,OAAO,EAAE,KAAAW,GAAK,MAAMD,GAAY,KAAK,UAAU,CAAC;AAAA,EAAA,GAGzEI,EAAI,KAAA,GACJQ,EAAQ,GAAGC,EAAWb,CAAU,CAAC,oBAAoB;AACvD;"}
@@ -1,4 +1,4 @@
1
- const e = [{ collectionGroup: "announcements", queryScope: "COLLECTION", fields: [{ fieldPath: "contestIds", arrayConfig: "CONTAINS" }, { fieldPath: "createdAt", order: "DESCENDING" }] }, { collectionGroup: "studentRestores", queryScope: "COLLECTION", fields: [{ fieldPath: "participationId", order: "ASCENDING" }, { fieldPath: "status", order: "ASCENDING" }, { fieldPath: "token", order: "ASCENDING" }, { fieldPath: "createdAt", order: "ASCENDING" }] }, { collectionGroup: "students", queryScope: "COLLECTION_GROUP", fields: [{ fieldPath: "contestId", order: "ASCENDING" }, { fieldPath: "disabled", order: "ASCENDING" }] }, { collectionGroup: "students", queryScope: "COLLECTION", fields: [{ fieldPath: "userDataHash", order: "ASCENDING" }, { fieldPath: "disabled", order: "ASCENDING" }] }, { collectionGroup: "variants", queryScope: "COLLECTION", fields: [{ fieldPath: "contestId", order: "ASCENDING" }, { fieldPath: "isOnline", order: "ASCENDING" }] }], t = [{ collectionGroup: "students", fieldPath: "answers", indexes: [] }, { collectionGroup: "students", fieldPath: "extraData", indexes: [] }, { collectionGroup: "students", fieldPath: "updatedAt", indexes: [] }, { collectionGroup: "submissions", fieldPath: "student", indexes: [] }, { collectionGroup: "submissions", fieldPath: "submittedAt", indexes: [] }], d = {
1
+ const e = [{ collectionGroup: "announcements", queryScope: "COLLECTION", fields: [{ fieldPath: "contestIds", arrayConfig: "CONTAINS" }, { fieldPath: "createdAt", order: "DESCENDING" }] }, { collectionGroup: "studentRestores", queryScope: "COLLECTION", fields: [{ fieldPath: "venueId", order: "ASCENDING" }, { fieldPath: "status", order: "ASCENDING" }, { fieldPath: "token", order: "ASCENDING" }, { fieldPath: "createdAt", order: "ASCENDING" }] }, { collectionGroup: "students", queryScope: "COLLECTION_GROUP", fields: [{ fieldPath: "contestId", order: "ASCENDING" }, { fieldPath: "disabled", order: "ASCENDING" }] }, { collectionGroup: "students", queryScope: "COLLECTION", fields: [{ fieldPath: "userDataHash", order: "ASCENDING" }, { fieldPath: "disabled", order: "ASCENDING" }] }, { collectionGroup: "variants", queryScope: "COLLECTION", fields: [{ fieldPath: "contestId", order: "ASCENDING" }, { fieldPath: "isOnline", order: "ASCENDING" }] }], t = [{ collectionGroup: "students", fieldPath: "answers", indexes: [] }, { collectionGroup: "students", fieldPath: "extraData", indexes: [] }, { collectionGroup: "students", fieldPath: "updatedAt", indexes: [] }, { collectionGroup: "submissions", fieldPath: "student", indexes: [] }, { collectionGroup: "submissions", fieldPath: "submittedAt", indexes: [] }], d = {
2
2
  indexes: e,
3
3
  fieldOverrides: t
4
4
  };
@@ -1,4 +1,4 @@
1
- const t = `rules_version = '2';
1
+ const e = `rules_version = '2';
2
2
  service cloud.firestore {
3
3
  match /databases/{database}/documents {
4
4
  function hasTimestamp(field) {
@@ -17,13 +17,13 @@ service cloud.firestore {
17
17
  return request.auth != null && request.auth.token.role == 'teacher';
18
18
  }
19
19
 
20
- function getParticipation(id) {
21
- return get(/databases/$(database)/documents/participations/$(id));
20
+ function getVenue(id) {
21
+ return get(/databases/$(database)/documents/venues/$(id));
22
22
  }
23
23
 
24
- function isTeacherParticipation(participationId) {
25
- let participation = getParticipation(participationId);
26
- return participation != null && participation.data.schoolId == request.auth.token.schoolId;
24
+ function isTeacherVenue(venueId) {
25
+ let venue = getVenue(venueId);
26
+ return venue != null && venue.data.schoolId == request.auth.token.schoolId;
27
27
  }
28
28
 
29
29
  match /announcements/{announcement} {
@@ -34,47 +34,47 @@ service cloud.firestore {
34
34
  allow read: if true;
35
35
  }
36
36
 
37
- match /participations/{participationId} {
38
- allow read: if participationId == request.auth.token.participationId || resource.data.schoolId == request.auth.token.schoolId;
37
+ match /venues/{venueId} {
38
+ allow read: if venueId == request.auth.token.venueId || resource.data.schoolId == request.auth.token.schoolId;
39
39
  }
40
40
 
41
- match /participations/{participationId}/students/{student} {
41
+ match /venues/{venueId}/students/{student} {
42
42
  function checkToken() {
43
- let participation = getParticipation(participationId);
44
- return participation != null
45
- && participation.data.token != null
46
- && participation.data.token == request.resource.data.token
47
- && participation.data.startingTime == request.resource.data.startedAt
48
- && participation.data.endingTime >= request.resource.data.finishedAt
49
- && participation.data.contestId == request.resource.data.contestId
50
- && request.time <= resource.data.finishedAt + duration.value(10, 's');
43
+ let venue = getVenue(venueId);
44
+ return venue != null
45
+ && venue.data.token != null
46
+ && venue.data.token == request.resource.data.token
47
+ && venue.data.participationWindow.start == request.resource.data.participationWindow.start
48
+ && venue.data.participationWindow.end >= request.resource.data.participationWindow.end
49
+ && venue.data.contestId == request.resource.data.contestId
50
+ && request.time <= resource.data.participationWindow.end + duration.value(10, 's');
51
51
  }
52
52
 
53
53
  function canStudentUpdate() {
54
54
  let keys = affectedKeys();
55
55
  return isAuthenticated(resource.data.uid)
56
56
  && checkToken()
57
- && keys.hasOnly(['answers', 'updatedAt', 'finishedAt', 'extraData'])
57
+ && keys.hasOnly(['answers', 'updatedAt', 'participationWindow', 'extraData'])
58
58
  && hasTimestamp('updatedAt');
59
59
  }
60
60
 
61
61
  function canTeacherUpdate() {
62
62
  let keys = affectedKeys();
63
- return isTeacherParticipation(participationId)
64
- && !keys.hasAny(['participationId', 'createdAt'])
63
+ return isTeacherVenue(venueId)
64
+ && !keys.hasAny(['venueId', 'createdAt'])
65
65
  && hasTimestamp('updatedAt');
66
66
  }
67
67
 
68
- allow read: if isAuthenticated(resource.data.uid) || isTeacherParticipation(participationId);
69
- allow create: if isTeacherParticipation(participationId) && request.resource.data.participationId == participationId;
68
+ allow read: if isAuthenticated(resource.data.uid) || isTeacherVenue(venueId);
69
+ allow create: if isTeacherVenue(venueId) && request.resource.data.venueId == venueId;
70
70
  allow update: if canStudentUpdate() || canTeacherUpdate();
71
- allow delete: if isTeacherParticipation(participationId);
71
+ allow delete: if isTeacherVenue(venueId);
72
72
  }
73
73
 
74
74
  match /studentRestores/{uid} {
75
- allow read: if isAuthenticated(uid) || isTeacherParticipation(resource.data.participationId);
76
- allow update: if isTeacherParticipation(resource.data.participationId);
77
- allow delete: if isTeacherParticipation(resource.data.participationId);
75
+ allow read: if isAuthenticated(uid) || isTeacherVenue(resource.data.venueId);
76
+ allow update: if isTeacherVenue(resource.data.venueId);
77
+ allow delete: if isTeacherVenue(resource.data.venueId);
78
78
  }
79
79
 
80
80
  match /variants/{variant} {
@@ -92,6 +92,6 @@ service cloud.firestore {
92
92
  }
93
93
  `;
94
94
  export {
95
- t as default
95
+ e as default
96
96
  };
97
97
  //# sourceMappingURL=firestore.rules.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"firestore.rules.js","sources":["../../../../src/cli/files/firestore.rules?raw"],"sourcesContent":["export default \"rules_version = '2';\\nservice cloud.firestore {\\n match /databases/{database}/documents {\\n function hasTimestamp(field) {\\n return request.resource.data[field] == request.time;\\n }\\n\\n function isAuthenticated(id) {\\n return request.auth != null && request.auth.uid == id;\\n }\\n\\n function affectedKeys() {\\n return request.resource.data.diff(resource.data).affectedKeys();\\n }\\n\\n function isTeacher() {\\n return request.auth != null && request.auth.token.role == 'teacher';\\n }\\n\\n function getParticipation(id) {\\n return get(/databases/$(database)/documents/participations/$(id));\\n }\\n\\n function isTeacherParticipation(participationId) {\\n let participation = getParticipation(participationId);\\n return participation != null && participation.data.schoolId == request.auth.token.schoolId;\\n }\\n\\n match /announcements/{announcement} {\\n allow read: if isTeacher();\\n }\\n\\n match /contests/{contest} {\\n allow read: if true;\\n }\\n\\n match /participations/{participationId} {\\n allow read: if participationId == request.auth.token.participationId || resource.data.schoolId == request.auth.token.schoolId;\\n }\\n\\n match /participations/{participationId}/students/{student} {\\n function checkToken() {\\n let participation = getParticipation(participationId);\\n return participation != null\\n && participation.data.token != null\\n && participation.data.token == request.resource.data.token\\n && participation.data.startingTime == request.resource.data.startedAt\\n && participation.data.endingTime >= request.resource.data.finishedAt\\n && participation.data.contestId == request.resource.data.contestId\\n && request.time <= resource.data.finishedAt + duration.value(10, 's');\\n }\\n\\n function canStudentUpdate() {\\n let keys = affectedKeys();\\n return isAuthenticated(resource.data.uid)\\n && checkToken()\\n && keys.hasOnly(['answers', 'updatedAt', 'finishedAt', 'extraData'])\\n && hasTimestamp('updatedAt');\\n }\\n\\n function canTeacherUpdate() {\\n let keys = affectedKeys();\\n return isTeacherParticipation(participationId)\\n && !keys.hasAny(['participationId', 'createdAt'])\\n && hasTimestamp('updatedAt');\\n }\\n\\n allow read: if isAuthenticated(resource.data.uid) || isTeacherParticipation(participationId);\\n allow create: if isTeacherParticipation(participationId) && request.resource.data.participationId == participationId;\\n allow update: if canStudentUpdate() || canTeacherUpdate();\\n allow delete: if isTeacherParticipation(participationId);\\n }\\n\\n match /studentRestores/{uid} {\\n allow read: if isAuthenticated(uid) || isTeacherParticipation(resource.data.participationId);\\n allow update: if isTeacherParticipation(resource.data.participationId);\\n allow delete: if isTeacherParticipation(resource.data.participationId);\\n }\\n\\n match /variants/{variant} {\\n allow read: if isTeacher();\\n }\\n\\n match /websites/{website} {\\n allow read: if true;\\n }\\n\\n match /{document=**} {\\n allow read, write: if request.auth.token.role == 'admin';\\n }\\n }\\n}\\n\""],"names":["firestoreRules"],"mappings":"AAAA,MAAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;"}
1
+ {"version":3,"file":"firestore.rules.js","sources":["../../../../src/cli/files/firestore.rules?raw"],"sourcesContent":["export default \"rules_version = '2';\\nservice cloud.firestore {\\n match /databases/{database}/documents {\\n function hasTimestamp(field) {\\n return request.resource.data[field] == request.time;\\n }\\n\\n function isAuthenticated(id) {\\n return request.auth != null && request.auth.uid == id;\\n }\\n\\n function affectedKeys() {\\n return request.resource.data.diff(resource.data).affectedKeys();\\n }\\n\\n function isTeacher() {\\n return request.auth != null && request.auth.token.role == 'teacher';\\n }\\n\\n function getVenue(id) {\\n return get(/databases/$(database)/documents/venues/$(id));\\n }\\n\\n function isTeacherVenue(venueId) {\\n let venue = getVenue(venueId);\\n return venue != null && venue.data.schoolId == request.auth.token.schoolId;\\n }\\n\\n match /announcements/{announcement} {\\n allow read: if isTeacher();\\n }\\n\\n match /contests/{contest} {\\n allow read: if true;\\n }\\n\\n match /venues/{venueId} {\\n allow read: if venueId == request.auth.token.venueId || resource.data.schoolId == request.auth.token.schoolId;\\n }\\n\\n match /venues/{venueId}/students/{student} {\\n function checkToken() {\\n let venue = getVenue(venueId);\\n return venue != null\\n && venue.data.token != null\\n && venue.data.token == request.resource.data.token\\n && venue.data.participationWindow.start == request.resource.data.participationWindow.start\\n && venue.data.participationWindow.end >= request.resource.data.participationWindow.end\\n && venue.data.contestId == request.resource.data.contestId\\n && request.time <= resource.data.participationWindow.end + duration.value(10, 's');\\n }\\n\\n function canStudentUpdate() {\\n let keys = affectedKeys();\\n return isAuthenticated(resource.data.uid)\\n && checkToken()\\n && keys.hasOnly(['answers', 'updatedAt', 'participationWindow', 'extraData'])\\n && hasTimestamp('updatedAt');\\n }\\n\\n function canTeacherUpdate() {\\n let keys = affectedKeys();\\n return isTeacherVenue(venueId)\\n && !keys.hasAny(['venueId', 'createdAt'])\\n && hasTimestamp('updatedAt');\\n }\\n\\n allow read: if isAuthenticated(resource.data.uid) || isTeacherVenue(venueId);\\n allow create: if isTeacherVenue(venueId) && request.resource.data.venueId == venueId;\\n allow update: if canStudentUpdate() || canTeacherUpdate();\\n allow delete: if isTeacherVenue(venueId);\\n }\\n\\n match /studentRestores/{uid} {\\n allow read: if isAuthenticated(uid) || isTeacherVenue(resource.data.venueId);\\n allow update: if isTeacherVenue(resource.data.venueId);\\n allow delete: if isTeacherVenue(resource.data.venueId);\\n }\\n\\n match /variants/{variant} {\\n allow read: if isTeacher();\\n }\\n\\n match /websites/{website} {\\n allow read: if true;\\n }\\n\\n match /{document=**} {\\n allow read, write: if request.auth.token.role == 'admin';\\n }\\n }\\n}\\n\""],"names":["firestoreRules"],"mappings":"AAAA,MAAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;"}
@@ -1,4 +1,4 @@
1
- const t = `import {
1
+ const n = `import {
2
2
  apiHandler,
3
3
  studentUpdatedHandler,
4
4
  updateScoresHandler,
@@ -13,13 +13,13 @@ setGlobalOptions({ region: "QUIZMS_REGION" });
13
13
  export const api = onRequest({ cors: ["http://localhost:1234"] }, apiHandler);
14
14
 
15
15
  export const submissions = onDocumentWrittenWithAuthContext(
16
- "participations/{participationId}/students/{studentId}",
16
+ "venues/{venueId}/students/{studentId}",
17
17
  studentUpdatedHandler,
18
18
  );
19
19
 
20
20
  export const updateScores = onTaskDispatched({ timeoutSeconds: 540 }, updateScoresHandler);
21
21
  `;
22
22
  export {
23
- t as default
23
+ n as default
24
24
  };
25
25
  //# sourceMappingURL=functions-index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"functions-index.js","sources":["../../../../src/cli/files/functions-index.js?raw"],"sourcesContent":["export default \"import {\\n apiHandler,\\n studentUpdatedHandler,\\n updateScoresHandler,\\n} from \\\"@olinfo/quizms-firebase/functions\\\";\\nimport { setGlobalOptions } from \\\"firebase-functions\\\";\\nimport { onDocumentWrittenWithAuthContext } from \\\"firebase-functions/firestore\\\";\\nimport { onRequest } from \\\"firebase-functions/https\\\";\\nimport { onTaskDispatched } from \\\"firebase-functions/tasks\\\";\\n\\nsetGlobalOptions({ region: \\\"QUIZMS_REGION\\\" });\\n\\nexport const api = onRequest({ cors: [\\\"http://localhost:1234\\\"] }, apiHandler);\\n\\nexport const submissions = onDocumentWrittenWithAuthContext(\\n \\\"participations/{participationId}/students/{studentId}\\\",\\n studentUpdatedHandler,\\n);\\n\\nexport const updateScores = onTaskDispatched({ timeoutSeconds: 540 }, updateScoresHandler);\\n\""],"names":["functionsIndex"],"mappings":"AAAA,MAAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;"}
1
+ {"version":3,"file":"functions-index.js","sources":["../../../../src/cli/files/functions-index.js?raw"],"sourcesContent":["export default \"import {\\n apiHandler,\\n studentUpdatedHandler,\\n updateScoresHandler,\\n} from \\\"@olinfo/quizms-firebase/functions\\\";\\nimport { setGlobalOptions } from \\\"firebase-functions\\\";\\nimport { onDocumentWrittenWithAuthContext } from \\\"firebase-functions/firestore\\\";\\nimport { onRequest } from \\\"firebase-functions/https\\\";\\nimport { onTaskDispatched } from \\\"firebase-functions/tasks\\\";\\n\\nsetGlobalOptions({ region: \\\"QUIZMS_REGION\\\" });\\n\\nexport const api = onRequest({ cors: [\\\"http://localhost:1234\\\"] }, apiHandler);\\n\\nexport const submissions = onDocumentWrittenWithAuthContext(\\n \\\"venues/{venueId}/students/{studentId}\\\",\\n studentUpdatedHandler,\\n);\\n\\nexport const updateScores = onTaskDispatched({ timeoutSeconds: 540 }, updateScoresHandler);\\n\""],"names":["functionsIndex"],"mappings":"AAAA,MAAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;"}
@@ -10,7 +10,7 @@ service firebase.storage {
10
10
  }
11
11
 
12
12
  function getStudent() {
13
- return firestore.get(/databases/(default)/documents/participations/$(request.auth.token.participationId)/students/$(request.auth.token.studentId));
13
+ return firestore.get(/databases/(default)/documents/venues/$(request.auth.token.venueId)/students/$(request.auth.token.studentId));
14
14
  }
15
15
 
16
16
  match /statements/{contestId}/{variantId}/{filename} {
@@ -19,8 +19,8 @@ service firebase.storage {
19
19
  return student != null
20
20
  && isAuthenticated(student.data.uid)
21
21
  && student.data.contestId == contestId
22
- && student.data.variant == variantId
23
- && student.data.startedAt <= request.time;
22
+ && student.data.variantId == variantId
23
+ && student.data.participationWindow.start <= request.time;
24
24
  }
25
25
 
26
26
  allow read: if canStudentReadVariant() || isTeacher();
@@ -1 +1 @@
1
- {"version":3,"file":"storage.rules.js","sources":["../../../../src/cli/files/storage.rules?raw"],"sourcesContent":["export default \"rules_version = '2';\\nservice firebase.storage {\\n match /b/{bucket}/o {\\n function isAuthenticated(id) {\\n return request.auth != null && request.auth.uid == id;\\n }\\n\\n function isTeacher() {\\n return request.auth != null && request.auth.token.role == 'teacher';\\n }\\n\\n function getStudent() {\\n return firestore.get(/databases/(default)/documents/participations/$(request.auth.token.participationId)/students/$(request.auth.token.studentId));\\n }\\n\\n match /statements/{contestId}/{variantId}/{filename} {\\n function canStudentReadVariant() {\\n let student = getStudent();\\n return student != null\\n && isAuthenticated(student.data.uid)\\n && student.data.contestId == contestId\\n && student.data.variant == variantId\\n && student.data.startedAt <= request.time;\\n }\\n\\n allow read: if canStudentReadVariant() || isTeacher();\\n }\\n }\\n}\\n\""],"names":["storageRules"],"mappings":"AAAA,MAAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;"}
1
+ {"version":3,"file":"storage.rules.js","sources":["../../../../src/cli/files/storage.rules?raw"],"sourcesContent":["export default \"rules_version = '2';\\nservice firebase.storage {\\n match /b/{bucket}/o {\\n function isAuthenticated(id) {\\n return request.auth != null && request.auth.uid == id;\\n }\\n\\n function isTeacher() {\\n return request.auth != null && request.auth.token.role == 'teacher';\\n }\\n\\n function getStudent() {\\n return firestore.get(/databases/(default)/documents/venues/$(request.auth.token.venueId)/students/$(request.auth.token.studentId));\\n }\\n\\n match /statements/{contestId}/{variantId}/{filename} {\\n function canStudentReadVariant() {\\n let student = getStudent();\\n return student != null\\n && isAuthenticated(student.data.uid)\\n && student.data.contestId == contestId\\n && student.data.variantId == variantId\\n && student.data.participationWindow.start <= request.time;\\n }\\n\\n allow read: if canStudentReadVariant() || isTeacher();\\n }\\n }\\n}\\n\""],"names":["storageRules"],"mappings":"AAAA,MAAAA,IAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;"}