@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.
- package/dist/cli.js +14 -14
- package/dist/cli.js.map +1 -1
- package/dist/package.json.js +1 -1
- package/dist/src/cli/definalize.js +17 -17
- package/dist/src/cli/definalize.js.map +1 -1
- package/dist/src/cli/export.js +42 -34
- package/dist/src/cli/export.js.map +1 -1
- package/dist/src/cli/files/firestore-indexes.json.js +1 -1
- package/dist/src/cli/files/firestore.rules.js +27 -27
- package/dist/src/cli/files/firestore.rules.js.map +1 -1
- package/dist/src/cli/files/functions-index.js +3 -3
- package/dist/src/cli/files/functions-index.js.map +1 -1
- package/dist/src/cli/files/storage.rules.js +3 -3
- package/dist/src/cli/files/storage.rules.js.map +1 -1
- package/dist/src/cli/import.js +65 -71
- package/dist/src/cli/import.js.map +1 -1
- package/dist/src/cli/init.js +1 -1
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/update-scores.js +7 -7
- package/dist/src/cli/update-scores.js.map +1 -1
- package/dist/src/cli/utils/converters-admin.js +23 -22
- package/dist/src/cli/utils/converters-admin.js.map +1 -1
- package/dist/src/functions/api/index.js +18 -18
- package/dist/src/functions/api/index.js.map +1 -1
- package/dist/src/functions/api/queries.js +21 -21
- package/dist/src/functions/api/queries.js.map +1 -1
- package/dist/src/functions/api/student.js +46 -47
- package/dist/src/functions/api/student.js.map +1 -1
- package/dist/src/functions/api/teacher.js +92 -95
- package/dist/src/functions/api/teacher.js.map +1 -1
- package/dist/src/functions/submissions.js +11 -7
- package/dist/src/functions/submissions.js.map +1 -1
- package/dist/src/functions/update-scores.js +14 -14
- package/dist/src/functions/update-scores.js.map +1 -1
- package/dist/src/models/submission.js +13 -0
- package/dist/src/models/submission.js.map +1 -0
- package/dist/src/web/admin/announcements.js +45 -42
- package/dist/src/web/admin/announcements.js.map +1 -1
- package/dist/src/web/admin/dashboard.js +43 -43
- package/dist/src/web/admin/dashboard.js.map +1 -1
- package/dist/src/web/admin/export.js +59 -54
- package/dist/src/web/admin/export.js.map +1 -1
- package/dist/src/web/admin/provider.js +28 -28
- package/dist/src/web/admin/provider.js.map +1 -1
- package/dist/src/web/admin/school-table.js +20 -24
- package/dist/src/web/admin/school-table.js.map +1 -1
- package/dist/src/web/common/api.js +25 -25
- package/dist/src/web/common/api.js.map +1 -1
- package/dist/src/web/common/converters.js +38 -39
- package/dist/src/web/common/converters.js.map +1 -1
- package/dist/src/web/entry/student-statement.js +1 -1
- package/dist/src/web/entry/student-statement.js.map +1 -1
- package/dist/src/web/entry/student.js +80 -75
- package/dist/src/web/entry/student.js.map +1 -1
- package/dist/src/web/entry/teacher.js +63 -62
- package/dist/src/web/entry/teacher.js.map +1 -1
- package/package.json +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
|
|
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
|
|
7
|
-
import { version as
|
|
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
|
|
13
|
-
function
|
|
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(
|
|
17
|
+
if (n(i.join(o, "package.json")) && n(i.join(o, "src"))) {
|
|
18
18
|
d(o);
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
|
-
const
|
|
22
|
-
(
|
|
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
|
|
26
|
-
t.addHelpText("beforeAll", `QuizMS-firebase cli v${
|
|
27
|
-
`), t.name("quizms-firebase"), t.version(
|
|
28
|
-
new
|
|
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
|
|
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
|
|
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(\"--
|
|
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;"}
|
package/dist/package.json.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import { info as
|
|
2
|
-
import { sumBy as
|
|
3
|
-
import {
|
|
4
|
-
import { initializeFirebase as
|
|
5
|
-
async function
|
|
6
|
-
const { db:
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
let
|
|
10
|
-
for (; !
|
|
11
|
-
const
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
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
|
-
|
|
18
|
+
o(`${i} venues were successfully definalized.`);
|
|
19
19
|
}
|
|
20
20
|
export {
|
|
21
|
-
|
|
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 {
|
|
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;"}
|
package/dist/src/cli/export.js
CHANGED
|
@@ -1,54 +1,62 @@
|
|
|
1
|
-
import { createWriteStream as
|
|
2
|
-
import { mkdir as
|
|
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
|
|
5
|
-
import { info as
|
|
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
|
|
9
|
-
import { capitalize as
|
|
10
|
-
import { studentConverter as h,
|
|
11
|
-
import { initializeFirebase as
|
|
12
|
-
async function
|
|
13
|
-
const { app:
|
|
14
|
-
if (await
|
|
15
|
-
const t =
|
|
16
|
-
await
|
|
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 (
|
|
19
|
-
const t =
|
|
20
|
-
await
|
|
18
|
+
if (o.venues) {
|
|
19
|
+
const t = r.collection("venues").withConverter(g);
|
|
20
|
+
await a(t, "venues", e);
|
|
21
21
|
}
|
|
22
|
-
if (
|
|
23
|
-
const t =
|
|
24
|
-
await
|
|
22
|
+
if (o.variants) {
|
|
23
|
+
const t = r.collection("variants").withConverter(x);
|
|
24
|
+
await a(t, "variants", e);
|
|
25
25
|
}
|
|
26
|
-
if (
|
|
27
|
-
const t =
|
|
28
|
-
await
|
|
26
|
+
if (o.contests) {
|
|
27
|
+
const t = r.collection("contests").withConverter(y);
|
|
28
|
+
await a(t, "contests", e);
|
|
29
29
|
}
|
|
30
|
-
|
|
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
|
|
33
|
-
|
|
34
|
-
const n = await
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
for
|
|
45
|
-
|
|
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
|
-
|
|
49
|
-
),
|
|
56
|
+
f(s.format({ dir: r, name: i, ext: ".jsonl" }))
|
|
57
|
+
), e.stop(), v(`${b(i)} export completed.`);
|
|
50
58
|
}
|
|
51
59
|
export {
|
|
52
|
-
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
21
|
-
return get(/databases/$(database)/documents/
|
|
20
|
+
function getVenue(id) {
|
|
21
|
+
return get(/databases/$(database)/documents/venues/$(id));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
function
|
|
25
|
-
let
|
|
26
|
-
return
|
|
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 /
|
|
38
|
-
allow read: if
|
|
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 /
|
|
41
|
+
match /venues/{venueId}/students/{student} {
|
|
42
42
|
function checkToken() {
|
|
43
|
-
let
|
|
44
|
-
return
|
|
45
|
-
&&
|
|
46
|
-
&&
|
|
47
|
-
&&
|
|
48
|
-
&&
|
|
49
|
-
&&
|
|
50
|
-
&& request.time <= resource.data.
|
|
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', '
|
|
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
|
|
64
|
-
&& !keys.hasAny(['
|
|
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) ||
|
|
69
|
-
allow create: if
|
|
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
|
|
71
|
+
allow delete: if isTeacherVenue(venueId);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
match /studentRestores/{uid} {
|
|
75
|
-
allow read: if isAuthenticated(uid) ||
|
|
76
|
-
allow update: if
|
|
77
|
-
allow delete: if
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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
|
-
|
|
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 \\\"
|
|
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/
|
|
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.
|
|
23
|
-
&& student.data.
|
|
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/
|
|
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;"}
|