@mxpicture/print-backlog 0.0.4 → 0.0.6

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 (38) hide show
  1. package/dist/public/assets/index-BnhoKdrs.css +1 -0
  2. package/dist/public/assets/index-j0LpYaft.js +1 -0
  3. package/dist/public/index.html +2 -2
  4. package/dist/server/api/items.d.ts +49 -0
  5. package/dist/server/api/items.d.ts.map +1 -0
  6. package/dist/server/api/items.js +552 -0
  7. package/dist/server/api/items.js.map +1 -0
  8. package/dist/server/api/queues.d.ts +12 -0
  9. package/dist/server/api/queues.d.ts.map +1 -1
  10. package/dist/server/api/queues.js +88 -0
  11. package/dist/server/api/queues.js.map +1 -1
  12. package/dist/server/app.d.ts +3 -1
  13. package/dist/server/app.d.ts.map +1 -1
  14. package/dist/server/app.js +30 -6
  15. package/dist/server/app.js.map +1 -1
  16. package/dist/server/cli.js +4 -1
  17. package/dist/server/cli.js.map +1 -1
  18. package/dist/server/multipart.d.ts +17 -0
  19. package/dist/server/multipart.d.ts.map +1 -0
  20. package/dist/server/multipart.js +72 -0
  21. package/dist/server/multipart.js.map +1 -0
  22. package/dist/server/positions.d.ts +21 -0
  23. package/dist/server/positions.d.ts.map +1 -0
  24. package/dist/server/positions.js +79 -0
  25. package/dist/server/positions.js.map +1 -0
  26. package/dist/server/storage.d.ts +79 -0
  27. package/dist/server/storage.d.ts.map +1 -0
  28. package/dist/server/storage.js +199 -0
  29. package/dist/server/storage.js.map +1 -0
  30. package/dist/server/zip.d.ts +35 -0
  31. package/dist/server/zip.d.ts.map +1 -0
  32. package/dist/server/zip.js +190 -0
  33. package/dist/server/zip.js.map +1 -0
  34. package/dist/shared/types.d.ts +99 -0
  35. package/dist/shared/types.d.ts.map +1 -1
  36. package/package.json +9 -1
  37. package/dist/public/assets/index-CLP1jBWd.js +0 -1
  38. package/dist/public/assets/index-DxpzozYw.css +0 -1
@@ -1,4 +1,7 @@
1
+ import { z } from "zod";
1
2
  import { ITEM_STATUSES, } from "../../shared/types.js";
3
+ import { HttpError } from "../http.js";
4
+ import { queuePosition } from "../positions.js";
2
5
  /** GET /api/v1/queues: every queue with its item counts per status (deleted items excluded). */
3
6
  export const listQueues = (sql) => async () => {
4
7
  const rows = await sql `
@@ -27,4 +30,89 @@ export const listQueues = (sql) => async () => {
27
30
  }
28
31
  return [...byId.values()];
29
32
  };
33
+ const queueWrite = z
34
+ .object({
35
+ name: z.string().trim().min(1).max(100).optional(),
36
+ autoStatus: z.boolean().optional(),
37
+ afterId: z.number().int().positive().nullable().optional(),
38
+ })
39
+ .strict();
40
+ const loadQueue = async (sql, id) => {
41
+ const q = (await listQueues(sql)()).find((x) => x.id === id);
42
+ if (!q)
43
+ throw new HttpError(404, `queue ${id} not found`);
44
+ return q;
45
+ };
46
+ const queueId = (v) => {
47
+ const n = Number(v);
48
+ if (!Number.isSafeInteger(n) || n <= 0)
49
+ throw new HttpError(400, "bad queue id");
50
+ return n;
51
+ };
52
+ /** POST /api/v1/queues {name, autoStatus?, afterId?}: a new queue, last unless afterId says otherwise. */
53
+ export const createQueue = (sql) => async ({ body }) => {
54
+ const p = queueWrite.parse(body);
55
+ if (!p.name)
56
+ throw new HttpError(400, "name: required");
57
+ const id = await sql.begin(async (tx) => {
58
+ await tx `LOCK TABLE backlog.queue IN SHARE ROW EXCLUSIVE MODE`;
59
+ let afterId = p.afterId;
60
+ if (afterId === undefined) {
61
+ const [last] = await tx `SELECT id FROM backlog.queue ORDER BY position DESC, id DESC LIMIT 1`;
62
+ afterId = last ? Number(last.id) : null;
63
+ }
64
+ const position = await queuePosition(tx, afterId, null);
65
+ const [row] = await tx `
66
+ INSERT INTO backlog.queue (name, position, auto_status) VALUES (${p.name}, ${position}, ${p.autoStatus ?? true})
67
+ RETURNING id
68
+ `;
69
+ return Number(row.id);
70
+ });
71
+ return loadQueue(sql, id);
72
+ };
73
+ /** PATCH /api/v1/queues/:id {name?, autoStatus?, afterId?} */
74
+ export const patchQueue = (sql) => async ({ params, body }) => {
75
+ const id = queueId(params.id);
76
+ const p = queueWrite.parse(body);
77
+ await sql.begin(async (tx) => {
78
+ await tx `LOCK TABLE backlog.queue IN SHARE ROW EXCLUSIVE MODE`;
79
+ const [q] = await tx `SELECT id FROM backlog.queue WHERE id = ${id}`;
80
+ if (!q)
81
+ throw new HttpError(404, `queue ${id} not found`);
82
+ if (p.name !== undefined)
83
+ await tx `UPDATE backlog.queue SET name = ${p.name} WHERE id = ${id}`;
84
+ if (p.autoStatus !== undefined)
85
+ await tx `UPDATE backlog.queue SET auto_status = ${p.autoStatus} WHERE id = ${id}`;
86
+ if (p.afterId !== undefined) {
87
+ if (p.afterId === id)
88
+ throw new HttpError(400, "a queue cannot follow itself");
89
+ const position = await queuePosition(tx, p.afterId, id);
90
+ await tx `UPDATE backlog.queue SET position = ${position} WHERE id = ${id}`;
91
+ }
92
+ });
93
+ return loadQueue(sql, id);
94
+ };
95
+ /**
96
+ * DELETE /api/v1/queues/:id: only an empty queue (its trash included), and
97
+ * never the last one.
98
+ */
99
+ export const deleteQueue = (sql) => async ({ params }) => {
100
+ const id = queueId(params.id);
101
+ await sql.begin(async (tx) => {
102
+ await tx `LOCK TABLE backlog.queue IN SHARE ROW EXCLUSIVE MODE`;
103
+ const [q] = await tx `SELECT id FROM backlog.queue WHERE id = ${id}`;
104
+ if (!q)
105
+ throw new HttpError(404, `queue ${id} not found`);
106
+ const [{ n: queues } = { n: 0 }] = await tx `SELECT count(*)::int AS n FROM backlog.queue`;
107
+ if (queues <= 1)
108
+ throw new HttpError(409, "the last queue cannot be deleted");
109
+ const [{ n: items } = { n: 0 }] = await tx `
110
+ SELECT count(*)::int AS n FROM backlog.item WHERE queue_id = ${id}
111
+ `;
112
+ if (items > 0)
113
+ throw new HttpError(409, `queue ${id} is not empty (${items} items, the trash included)`);
114
+ await tx `DELETE FROM backlog.queue WHERE id = ${id}`;
115
+ });
116
+ return { deleted: id };
117
+ };
30
118
  //# sourceMappingURL=queues.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"queues.js","sourceRoot":"","sources":["../../../src/server/api/queues.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,GAGd,MAAM,uBAAuB,CAAC;AAG/B,gGAAgG;AAChG,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAQ,EAAE,EAAE,CAAC,KAAK,IAA6B,EAAE;IAC1E,MAAM,IAAI,GAAG,MAAM,GAAG,CASrB;;;;;;GAMA,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrB,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG;gBACF,EAAE;gBACF,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;gBAC5B,UAAU,EAAE,CAAC,CAAC,WAAW;gBACzB,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAG1D;aACF,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,CAAC,MAAM;YAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAC5B,CAAC,CAAC"}
1
+ {"version":3,"file":"queues.js","sourceRoot":"","sources":["../../../src/server/api/queues.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,12 @@
1
1
  import { type BambuddyState } from "./api/health.js";
2
2
  import type { Sql } from "./db.js";
3
3
  import { Router } from "./http.js";
4
+ import type { FileStore } from "./storage.js";
4
5
  export interface AppDeps {
5
6
  sql: Sql;
7
+ store: FileStore;
6
8
  bambuddy: BambuddyState;
7
9
  }
8
10
  /** Every route. Paths are the contract with Caddy's vhost: only /health and /hooks/bambuddy bypass SSO. */
9
- export declare const buildRouter: ({ sql, bambuddy }: AppDeps) => Router;
11
+ export declare const buildRouter: ({ sql, store, bambuddy }: AppDeps) => Router;
10
12
  //# sourceMappingURL=app.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,iBAAiB,CAAC;AAEjE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,2GAA2G;AAC3G,eAAO,MAAM,WAAW,GAAI,mBAAmB,OAAO,KAAG,MAKd,CAAC"}
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,iBAAiB,CAAC;AAqBjE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,GAAG,CAAC;IACT,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,2GAA2G;AAC3G,eAAO,MAAM,WAAW,GAAI,0BAA0B,OAAO,KAAG,MA6B/D,CAAC"}
@@ -1,11 +1,35 @@
1
1
  import { bambuddyHook } from "./api/hooks.js";
2
2
  import { health, me } from "./api/health.js";
3
- import { listQueues } from "./api/queues.js";
3
+ import { deleteItem, downloadVersion, exportQueue, getItem, itemHistory, listItems, patchItem, patchVersion, restoreItem, uploadItems, uploadVersion, versionThumbnail, } from "./api/items.js";
4
+ import { createQueue, deleteQueue, listQueues, patchQueue, } from "./api/queues.js";
4
5
  import { Router } from "./http.js";
5
6
  /** Every route. Paths are the contract with Caddy's vhost: only /health and /hooks/bambuddy bypass SSO. */
6
- export const buildRouter = ({ sql, bambuddy }) => new Router()
7
- .get("/health", health(sql, bambuddy), { access: "public" })
8
- .post("/hooks/bambuddy", bambuddyHook(sql), { access: "webhook" })
9
- .get("/api/v1/me", me(bambuddy.configured))
10
- .get("/api/v1/queues", listQueues(sql));
7
+ export const buildRouter = ({ sql, store, bambuddy }) => {
8
+ const deps = { sql, store };
9
+ return (new Router()
10
+ .get("/health", health(sql, bambuddy), { access: "public" })
11
+ .post("/hooks/bambuddy", bambuddyHook(sql), { access: "webhook" })
12
+ .get("/api/v1/me", me(bambuddy.configured))
13
+ // queues
14
+ .get("/api/v1/queues", listQueues(sql))
15
+ .post("/api/v1/queues", createQueue(sql))
16
+ .patch("/api/v1/queues/:id", patchQueue(sql))
17
+ .delete("/api/v1/queues/:id", deleteQueue(sql))
18
+ .post("/api/v1/queues/:id/items", uploadItems(deps), { body: "stream" })
19
+ .get("/api/v1/queues/:id/export.zip", exportQueue(deps))
20
+ // items
21
+ .get("/api/v1/items", listItems(deps))
22
+ .get("/api/v1/items/:id", getItem(deps))
23
+ .patch("/api/v1/items/:id", patchItem(deps))
24
+ .delete("/api/v1/items/:id", deleteItem(deps))
25
+ .post("/api/v1/items/:id/restore", restoreItem(deps))
26
+ .get("/api/v1/items/:id/history", itemHistory(deps))
27
+ // versions
28
+ .post("/api/v1/items/:id/versions", uploadVersion(deps), {
29
+ body: "stream",
30
+ })
31
+ .patch("/api/v1/items/:id/versions/:n", patchVersion(deps))
32
+ .get("/api/v1/items/:id/versions/:n/download", downloadVersion(deps))
33
+ .get("/api/v1/items/:id/versions/:n/thumbnail", versionThumbnail(deps)));
34
+ };
11
35
  //# sourceMappingURL=app.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAsB,MAAM,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAOnC,2GAA2G;AAC3G,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAW,EAAU,EAAE,CAChE,IAAI,MAAM,EAAE;KACT,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;KAC3D,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;KACjE,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;KAC1C,GAAG,CAAC,gBAAgB,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAsB,MAAM,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EACL,UAAU,EACV,eAAe,EACf,WAAW,EACX,OAAO,EACP,WAAW,EACX,SAAS,EACT,SAAS,EACT,YAAY,EACZ,WAAW,EACX,WAAW,EACX,aAAa,EACb,gBAAgB,GACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,WAAW,EACX,WAAW,EACX,UAAU,EACV,UAAU,GACX,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AASnC,2GAA2G;AAC3G,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAW,EAAU,EAAE;IACvE,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC;IAC5B,OAAO,CACL,IAAI,MAAM,EAAE;SACT,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;SAC3D,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;SACjE,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC3C,SAAS;SACR,GAAG,CAAC,gBAAgB,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;SACtC,IAAI,CAAC,gBAAgB,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC;SACxC,KAAK,CAAC,oBAAoB,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC;SAC5C,MAAM,CAAC,oBAAoB,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC;SAC9C,IAAI,CAAC,0BAA0B,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;SACvE,GAAG,CAAC,+BAA+B,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;QACxD,QAAQ;SACP,GAAG,CAAC,eAAe,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;SACrC,GAAG,CAAC,mBAAmB,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;SACvC,KAAK,CAAC,mBAAmB,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;SAC3C,MAAM,CAAC,mBAAmB,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC;SAC7C,IAAI,CAAC,2BAA2B,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;SACpD,GAAG,CAAC,2BAA2B,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;QACpD,WAAW;SACV,IAAI,CAAC,4BAA4B,EAAE,aAAa,CAAC,IAAI,CAAC,EAAE;QACvD,IAAI,EAAE,QAAQ;KACf,CAAC;SACD,KAAK,CAAC,+BAA+B,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;SAC1D,GAAG,CAAC,wCAAwC,EAAE,eAAe,CAAC,IAAI,CAAC,CAAC;SACpE,GAAG,CAAC,yCAAyC,EAAE,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAC1E,CAAC;AACJ,CAAC,CAAC"}
@@ -19,6 +19,7 @@ import { createSql } from "./db.js";
19
19
  import { handle } from "./http.js";
20
20
  import { errorMessage, log } from "./log.js";
21
21
  import { applySchema } from "./schema.js";
22
+ import { FileStore } from "./storage.js";
22
23
  import { VERSION } from "./version.js";
23
24
  /** How often Bambuddy's reachability is re-checked for /health. */
24
25
  const BAMBUDDY_CHECK_MS = 5 * 60_000;
@@ -26,6 +27,8 @@ const serve = async () => {
26
27
  const config = loadConfig();
27
28
  const sql = createSql();
28
29
  await applySchema(sql, config.defaultQueue);
30
+ const store = new FileStore(config.dataDir, config.maxUploadBytes);
31
+ await store.init();
29
32
  const bambuddy = {
30
33
  configured: config.bambuddy !== null,
31
34
  ok: null,
@@ -46,7 +49,7 @@ const serve = async () => {
46
49
  ? setInterval(() => void checkBambuddy(), BAMBUDDY_CHECK_MS)
47
50
  : null;
48
51
  const staticDir = fileURLToPath(new URL("../public/", import.meta.url));
49
- const router = buildRouter({ sql, bambuddy });
52
+ const router = buildRouter({ sql, store, bambuddy });
50
53
  const server = createServer((req, res) => void handle(req, res, {
51
54
  origin: config.origin,
52
55
  userHeader: config.userHeader,
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/server/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/server/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ import { type FileStore, type StagedFile } from "./storage.js";
3
+ export interface Multipart {
4
+ files: StagedFile[];
5
+ fields: Record<string, string>;
6
+ }
7
+ /** At most this many files per request; a bigger drop is split by the page. */
8
+ export declare const MAX_FILES = 50;
9
+ /**
10
+ * Read a multipart/form-data upload: every file part is staged in the store
11
+ * (streamed, hashed, size-capped), every other part is a text field. On any
12
+ * error the files staged so far are discarded, so nothing is left in .tmp.
13
+ */
14
+ export declare const readMultipart: (req: IncomingMessage, store: FileStore, opts?: {
15
+ allowZip?: boolean;
16
+ }) => Promise<Multipart>;
17
+ //# sourceMappingURL=multipart.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../../src/server/multipart.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAGjD,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAkB,MAAM,cAAc,CAAC;AAE/E,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC;AAED,+EAA+E;AAC/E,eAAO,MAAM,SAAS,KAAK,CAAC;AAE5B;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,KAAK,eAAe,EACpB,OAAO,SAAS,EAChB,OAAM;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,KAChC,OAAO,CAAC,SAAS,CA8EnB,CAAC"}
@@ -0,0 +1,72 @@
1
+ import busboy from "busboy";
2
+ import { HttpError } from "./http.js";
3
+ import { UploadRejected } from "./storage.js";
4
+ /** At most this many files per request; a bigger drop is split by the page. */
5
+ export const MAX_FILES = 50;
6
+ /**
7
+ * Read a multipart/form-data upload: every file part is staged in the store
8
+ * (streamed, hashed, size-capped), every other part is a text field. On any
9
+ * error the files staged so far are discarded, so nothing is left in .tmp.
10
+ */
11
+ export const readMultipart = async (req, store, opts = {}) => {
12
+ if (!/^multipart\/form-data\s*;/i.test(req.headers["content-type"] ?? ""))
13
+ throw new HttpError(415, "an upload must be multipart/form-data");
14
+ let bb;
15
+ try {
16
+ bb = busboy({
17
+ headers: req.headers,
18
+ defParamCharset: "utf8",
19
+ limits: {
20
+ files: MAX_FILES,
21
+ fields: 20,
22
+ fieldSize: 64 * 1024,
23
+ fileSize: store.maxBytes + 1,
24
+ },
25
+ });
26
+ }
27
+ catch (e) {
28
+ throw new HttpError(400, e instanceof Error ? e.message : "bad multipart request");
29
+ }
30
+ const staging = [];
31
+ const fields = {};
32
+ let tooMany = false;
33
+ const done = new Promise((resolve, reject) => {
34
+ bb.on("file", (_name, stream, info) => {
35
+ let truncated = false;
36
+ stream.on("limit", () => (truncated = true));
37
+ const p = store.stage(stream, info.filename ?? "file", () => truncated, opts);
38
+ // Settled later with allSettled; keep an early rejection from being "unhandled".
39
+ p.catch(() => { });
40
+ staging.push(p);
41
+ });
42
+ bb.on("field", (name, value) => {
43
+ fields[name] = value;
44
+ });
45
+ bb.on("filesLimit", () => (tooMany = true));
46
+ bb.on("error", (e) => reject(new HttpError(400, e instanceof Error ? e.message : "bad multipart request")));
47
+ bb.on("close", () => resolve());
48
+ });
49
+ req.pipe(bb);
50
+ let failure = null;
51
+ try {
52
+ await done;
53
+ }
54
+ catch (e) {
55
+ failure = e;
56
+ }
57
+ const results = await Promise.allSettled(staging);
58
+ const files = results.flatMap((r) => r.status === "fulfilled" ? [r.value] : []);
59
+ const rejected = results.find((r) => r.status === "rejected");
60
+ if (!failure && rejected)
61
+ failure = rejected.reason;
62
+ if (!failure && tooMany)
63
+ failure = new HttpError(413, `at most ${MAX_FILES} files per upload`);
64
+ if (failure) {
65
+ await Promise.all(files.map((f) => store.discard(f)));
66
+ if (failure instanceof UploadRejected)
67
+ throw new HttpError(failure.status, failure.message);
68
+ throw failure;
69
+ }
70
+ return { files, fields };
71
+ };
72
+ //# sourceMappingURL=multipart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multipart.js","sourceRoot":"","sources":["../../src/server/multipart.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAmC,cAAc,EAAE,MAAM,cAAc,CAAC;AAO/E,+EAA+E;AAC/E,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAE5B;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAChC,GAAoB,EACpB,KAAgB,EAChB,OAA+B,EAAE,EACb,EAAE;IACtB,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QACvE,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,uCAAuC,CAAC,CAAC;IAEpE,IAAI,EAAiB,CAAC;IACtB,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,CAAC;YACV,OAAO,EAAE,GAAG,CAAC,OAAO;YACpB,eAAe,EAAE,MAAM;YACvB,MAAM,EAAE;gBACN,KAAK,EAAE,SAAS;gBAChB,MAAM,EAAE,EAAE;gBACV,SAAS,EAAE,EAAE,GAAG,IAAI;gBACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ,GAAG,CAAC;aAC7B;SACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,SAAS,CACjB,GAAG,EACH,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,CACzD,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAA0B,EAAE,CAAC;IAC1C,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACjD,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;YACpC,IAAI,SAAS,GAAG,KAAK,CAAC;YACtB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC;YAC7C,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CACnB,MAAM,EACN,IAAI,CAAC,QAAQ,IAAI,MAAM,EACvB,GAAG,EAAE,CAAC,SAAS,EACf,IAAI,CACL,CAAC;YACF,iFAAiF;YACjF,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAClB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;YAC7B,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,EAAE,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC;QAC5C,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CACnB,MAAM,CACJ,IAAI,SAAS,CACX,GAAG,EACH,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,CACzD,CACF,CACF,CAAC;QACF,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IACH,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEb,IAAI,OAAO,GAAY,IAAI,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,IAAI,CAAC;IACb,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,GAAG,CAAC,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAClC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAC1C,CAAC;IACF,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC;IAC9D,IAAI,CAAC,OAAO,IAAI,QAAQ;QAAE,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC;IACpD,IAAI,CAAC,OAAO,IAAI,OAAO;QACrB,OAAO,GAAG,IAAI,SAAS,CAAC,GAAG,EAAE,WAAW,SAAS,mBAAmB,CAAC,CAAC;IACxE,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtD,IAAI,OAAO,YAAY,cAAc;YACnC,MAAM,IAAI,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QACvD,MAAM,OAAO,CAAC;IAChB,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC3B,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type postgres from "postgres";
2
+ type Tx = postgres.TransactionSql;
3
+ /** Gap between neighbours after a renumber; a midpoint fits ~10 times before the next one. */
4
+ export declare const POSITION_STEP = 1024;
5
+ /**
6
+ * The position for a row placed right after `afterPos` (null = before the
7
+ * first) among `positions` (ascending, the row itself excluded). Returns null
8
+ * when there is no integer gap left: the caller renumbers and asks again.
9
+ */
10
+ export declare const positionBetween: (positions: readonly number[], afterPos: number | null) => number | null;
11
+ /**
12
+ * Where an item goes in a queue: after item `afterId` (which must be in that
13
+ * queue and not deleted), or first. Renumbers the queue in steps of 1024 when
14
+ * the gap is used up. Runs inside the caller's transaction, which holds the
15
+ * queue's row lock (FOR UPDATE), so two moves cannot interleave.
16
+ */
17
+ export declare const itemPosition: (tx: Tx, queueId: number, afterId: number | null, selfId: number | null) => Promise<number>;
18
+ /** The same for the order of the queues themselves (the caller locks the queue table). */
19
+ export declare const queuePosition: (tx: Tx, afterId: number | null, selfId: number | null) => Promise<number>;
20
+ export {};
21
+ //# sourceMappingURL=positions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"positions.d.ts","sourceRoot":"","sources":["../../src/server/positions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,UAAU,CAAC;AAGrC,KAAK,EAAE,GAAG,QAAQ,CAAC,cAAc,CAAC;AAElC,8FAA8F;AAC9F,eAAO,MAAM,aAAa,OAAO,CAAC;AAElC;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,SAAS,MAAM,EAAE,EAC5B,UAAU,MAAM,GAAG,IAAI,KACtB,MAAM,GAAG,IAQX,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GACvB,IAAI,EAAE,EACN,SAAS,MAAM,EACf,SAAS,MAAM,GAAG,IAAI,EACtB,QAAQ,MAAM,GAAG,IAAI,KACpB,OAAO,CAAC,MAAM,CAqChB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,aAAa,GACxB,IAAI,EAAE,EACN,SAAS,MAAM,GAAG,IAAI,EACtB,QAAQ,MAAM,GAAG,IAAI,KACpB,OAAO,CAAC,MAAM,CA6BhB,CAAC"}
@@ -0,0 +1,79 @@
1
+ import { HttpError } from "./http.js";
2
+ /** Gap between neighbours after a renumber; a midpoint fits ~10 times before the next one. */
3
+ export const POSITION_STEP = 1024;
4
+ /**
5
+ * The position for a row placed right after `afterPos` (null = before the
6
+ * first) among `positions` (ascending, the row itself excluded). Returns null
7
+ * when there is no integer gap left: the caller renumbers and asks again.
8
+ */
9
+ export const positionBetween = (positions, afterPos) => {
10
+ if (afterPos === null)
11
+ return positions.length === 0
12
+ ? POSITION_STEP
13
+ : positions[0] - POSITION_STEP;
14
+ const next = positions.find((p) => p > afterPos);
15
+ if (next === undefined)
16
+ return afterPos + POSITION_STEP;
17
+ return next - afterPos >= 2 ? Math.floor((afterPos + next) / 2) : null;
18
+ };
19
+ /**
20
+ * Where an item goes in a queue: after item `afterId` (which must be in that
21
+ * queue and not deleted), or first. Renumbers the queue in steps of 1024 when
22
+ * the gap is used up. Runs inside the caller's transaction, which holds the
23
+ * queue's row lock (FOR UPDATE), so two moves cannot interleave.
24
+ */
25
+ export const itemPosition = async (tx, queueId, afterId, selfId) => {
26
+ const load = async () => (await tx `
27
+ SELECT id, position FROM backlog.item
28
+ WHERE queue_id = ${queueId} AND deleted_at IS NULL AND id IS DISTINCT FROM ${selfId}
29
+ ORDER BY position, id
30
+ `).map((r) => ({ id: Number(r.id), position: Number(r.position) }));
31
+ const afterPos = (rows) => {
32
+ if (afterId === null)
33
+ return null;
34
+ const after = rows.find((r) => r.id === afterId);
35
+ if (!after)
36
+ throw new HttpError(400, `item ${afterId} is not in queue ${queueId}`);
37
+ return after.position;
38
+ };
39
+ let rows = await load();
40
+ const pos = positionBetween(rows.map((r) => r.position), afterPos(rows));
41
+ if (pos !== null)
42
+ return pos;
43
+ await tx `
44
+ UPDATE backlog.item i SET position = r.rn * ${POSITION_STEP}
45
+ FROM (
46
+ SELECT id, row_number() OVER (ORDER BY position, id) AS rn FROM backlog.item
47
+ WHERE queue_id = ${queueId} AND deleted_at IS NULL AND id IS DISTINCT FROM ${selfId}
48
+ ) r
49
+ WHERE i.id = r.id
50
+ `;
51
+ rows = await load();
52
+ return positionBetween(rows.map((r) => r.position), afterPos(rows));
53
+ };
54
+ /** The same for the order of the queues themselves (the caller locks the queue table). */
55
+ export const queuePosition = async (tx, afterId, selfId) => {
56
+ const load = async () => (await tx `
57
+ SELECT id, position FROM backlog.queue WHERE id IS DISTINCT FROM ${selfId} ORDER BY position, id
58
+ `).map((r) => ({ id: Number(r.id), position: Number(r.position) }));
59
+ const afterPos = (rows) => {
60
+ if (afterId === null)
61
+ return null;
62
+ const after = rows.find((r) => r.id === afterId);
63
+ if (!after)
64
+ throw new HttpError(400, `queue ${afterId} does not exist`);
65
+ return after.position;
66
+ };
67
+ let rows = await load();
68
+ const pos = positionBetween(rows.map((r) => r.position), afterPos(rows));
69
+ if (pos !== null)
70
+ return pos;
71
+ await tx `
72
+ UPDATE backlog.queue q SET position = r.rn * ${POSITION_STEP}
73
+ FROM (SELECT id, row_number() OVER (ORDER BY position, id) AS rn FROM backlog.queue WHERE id IS DISTINCT FROM ${selfId}) r
74
+ WHERE q.id = r.id
75
+ `;
76
+ rows = await load();
77
+ return positionBetween(rows.map((r) => r.position), afterPos(rows));
78
+ };
79
+ //# sourceMappingURL=positions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"positions.js","sourceRoot":"","sources":["../../src/server/positions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,79 @@
1
+ import type { Readable } from "node:stream";
2
+ /**
3
+ * The write-once file store under DATA_DIR/files (docs/plan.md §3.5).
4
+ *
5
+ * - An upload streams into files/.tmp/<uuid>, hashed and counted on the way,
6
+ * fsync'd, and only then placed at files/<item_id>/<n>-<sha12>-<name>.
7
+ * - Placing is link() + unlink(), never rename(): link() fails with EEXIST
8
+ * instead of replacing a file, so a stored version can never be
9
+ * overwritten. The VPS backs files/ up with `rclone --immutable`, which
10
+ * fails loudly on a rewritten file; this keeps it from ever happening.
11
+ * - .tmp sits inside files/ so the link stays on one filesystem.
12
+ */
13
+ /** Model formats we keep. A ZIP upload is unpacked into these (zip.ts). */
14
+ export declare const MODEL_EXTENSIONS: readonly ["stl", "3mf", "step", "stp", "obj"];
15
+ export type ModelExt = (typeof MODEL_EXTENSIONS)[number];
16
+ /** Formats Bambuddy can take (Send to Bambuddy); STEP it cannot slice. */
17
+ export declare const BAMBUDDY_EXTENSIONS: ReadonlySet<string>;
18
+ export declare class UploadRejected extends Error {
19
+ readonly status: 413 | 415;
20
+ constructor(status: 413 | 415, message: string);
21
+ }
22
+ /**
23
+ * A file name that is safe on disk and in a Content-Disposition header:
24
+ * NFC, no path separators or control characters, no leading dots, at most
25
+ * 180 characters with the extension kept. Never empty.
26
+ */
27
+ export declare const safeFilename: (name: string) => string;
28
+ /**
29
+ * The model extension of a file name, or an UploadRejected that says why
30
+ * not. Sliced files belong in Bambuddy, not here.
31
+ */
32
+ export declare const modelExt: (filename: string) => ModelExt;
33
+ /** The title a new item gets from its first file: the name without the extension, separators as spaces. */
34
+ export declare const titleFromFilename: (filename: string) => string;
35
+ /** A file received and hashed, waiting in .tmp to be placed. `zip` only where the caller allowed it (a new item upload). */
36
+ export interface StagedFile {
37
+ tmpPath: string;
38
+ filename: string;
39
+ ext: ModelExt | "zip";
40
+ size: number;
41
+ sha256: string;
42
+ }
43
+ /** A staged file that is a model, not a ZIP still to unpack. */
44
+ export type StagedModel = StagedFile & {
45
+ ext: ModelExt;
46
+ };
47
+ export declare const isModel: (f: StagedFile) => f is StagedModel;
48
+ /** modelExt, plus `.zip` when allowed. */
49
+ export declare const uploadExt: (filename: string, allowZip: boolean) => ModelExt | "zip";
50
+ export declare class FileStore {
51
+ readonly maxBytes: number;
52
+ readonly root: string;
53
+ readonly tmpDir: string;
54
+ constructor(dataDir: string, maxBytes: number);
55
+ init(): Promise<void>;
56
+ /**
57
+ * Stream one upload into .tmp. Rejects (and removes the partial file) when
58
+ * the name is not a model file or the stream exceeds maxBytes; `truncated`
59
+ * is busboy's flag for a file cut off at its fileSize limit.
60
+ */
61
+ stage(stream: Readable, rawName: string, truncated: () => boolean, opts?: {
62
+ allowZip?: boolean;
63
+ }): Promise<StagedFile>;
64
+ /** The path, relative to the store root, a version is stored at. */
65
+ static relativePath(itemId: number, n: number, sha256: string, filename: string): string;
66
+ /** Place a staged file at its final path, write-once (EEXIST if anything is there). */
67
+ place(staged: StagedFile, relPath: string): Promise<void>;
68
+ /**
69
+ * Write a small buffer (a thumbnail) write-once at relPath: through .tmp
70
+ * and link(), like an upload, so EEXIST protects it the same way.
71
+ */
72
+ placeBuffer(data: Buffer, relPath: string): Promise<void>;
73
+ /** Drop a staged file that will not be placed (duplicate, rejected batch, failed transaction). */
74
+ discard(staged: StagedFile): Promise<void>;
75
+ absolute(relPath: string): string;
76
+ exists(relPath: string): Promise<boolean>;
77
+ read(relPath: string): Readable;
78
+ }
79
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/server/storage.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;;;;;GAUG;AAEH,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,+CAAgD,CAAC;AAC9E,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,0EAA0E;AAC1E,eAAO,MAAM,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAIlD,CAAC;AAEH,qBAAa,cAAe,SAAQ,KAAK;IAErC,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG;gBAAjB,MAAM,EAAE,GAAG,GAAG,GAAG,EAC1B,OAAO,EAAE,MAAM;CAIlB;AAID;;;;GAIG;AACH,eAAO,MAAM,YAAY,GAAI,MAAM,MAAM,KAAG,MAY3C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,QAAQ,GAAI,UAAU,MAAM,KAAG,QAe3C,CAAC;AAEF,2GAA2G;AAC3G,eAAO,MAAM,iBAAiB,GAAI,UAAU,MAAM,KAAG,MAKpD,CAAC;AAEF,4HAA4H;AAC5H,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,QAAQ,GAAG,KAAK,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,gEAAgE;AAChE,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG;IAAE,GAAG,EAAE,QAAQ,CAAA;CAAE,CAAC;AACzD,eAAO,MAAM,OAAO,GAAI,GAAG,UAAU,KAAG,CAAC,IAAI,WAA8B,CAAC;AAE5E,0CAA0C;AAC1C,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,UAAU,OAAO,KAChB,QAAQ,GAAG,KAGb,CAAC;AAEF,qBAAa,SAAS;IAMlB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IAL3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAGtB,OAAO,EAAE,MAAM,EACN,QAAQ,EAAE,MAAM;IAMrB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;;;OAIG;IACG,KAAK,CACT,MAAM,EAAE,QAAQ,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,OAAO,EACxB,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAO,GAChC,OAAO,CAAC,UAAU,CAAC;IA2CtB,oEAAoE;IACpE,MAAM,CAAC,YAAY,CACjB,MAAM,EAAE,MAAM,EACd,CAAC,EAAE,MAAM,EACT,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GACf,MAAM;IAIT,uFAAuF;IACjF,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc/D;;;OAGG;IACG,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB/D,kGAAkG;IAC5F,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAIhD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAO3B,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ/C,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ;CAGhC"}