@mxpicture/print-backlog 0.0.2 → 0.0.5

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.
@@ -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;AAmBjE,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,MA2B/D,CAAC"}
@@ -1,11 +1,33 @@
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, getItem, itemHistory, listItems, patchItem, patchVersion, restoreItem, uploadItems, uploadVersion, } 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
+ // items
20
+ .get("/api/v1/items", listItems(deps))
21
+ .get("/api/v1/items/:id", getItem(deps))
22
+ .patch("/api/v1/items/:id", patchItem(deps))
23
+ .delete("/api/v1/items/:id", deleteItem(deps))
24
+ .post("/api/v1/items/:id/restore", restoreItem(deps))
25
+ .get("/api/v1/items/:id/history", itemHistory(deps))
26
+ // versions
27
+ .post("/api/v1/items/:id/versions", uploadVersion(deps), {
28
+ body: "stream",
29
+ })
30
+ .patch("/api/v1/items/:id/versions/:n", patchVersion(deps))
31
+ .get("/api/v1/items/:id/versions/:n/download", downloadVersion(deps)));
32
+ };
11
33
  //# 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,OAAO,EACP,WAAW,EACX,SAAS,EACT,SAAS,EACT,YAAY,EACZ,WAAW,EACX,WAAW,EACX,aAAa,GACd,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;QACxE,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,CACxE,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":";AACA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACnC,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,mEAAmE;AACnE,MAAM,iBAAiB,GAAG,CAAC,GAAG,MAAM,CAAC;AAErC,MAAM,KAAK,GAAG,KAAK,IAAmB,EAAE;IACtC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,MAAM,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;IAE5C,MAAM,QAAQ,GAAkB;QAC9B,UAAU,EAAE,MAAM,CAAC,QAAQ,KAAK,IAAI;QACpC,EAAE,EAAE,IAAI;KACT,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ;QAC5B,CAAC,CAAC,oBAAoB,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QACnE,CAAC,CAAC,IAAI,CAAC;IACT,MAAM,aAAa,GAAG,KAAK,IAAI,EAAE;QAC/B,IAAI,CAAC,MAAM;YAAE,OAAO;QACpB,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,EAAE;YACtB,GAAG,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACrE,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;IACrB,CAAC,CAAC;IACF,KAAK,aAAa,EAAE,CAAC;IACrB,MAAM,KAAK,GAAG,MAAM;QAClB,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,aAAa,EAAE,EAAE,iBAAiB,CAAC;QAC5D,CAAC,CAAC,IAAI,CAAC;IAET,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,YAAY,CACzB,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CACX,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,EAAE;QACpB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,MAAM;QACN,SAAS;QACT,GAAG;KACJ,CAAC,CACL,CAAC;IACF,+DAA+D;IAC/D,MAAM,CAAC,cAAc,GAAG,EAAE,GAAG,MAAM,CAAC;IACpC,MAAM,CAAC,gBAAgB,GAAG,MAAM,CAAC;IACjC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,CAC9B,GAAG,CACD,iBAAiB,OAAO,kBAAkB,MAAM,CAAC,IAAI,QAAQ,MAAM,CAAC,MAAM,GAAG;QAC3E,aAAa,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CACvH,CACF,CAAC;IAEF,MAAM,IAAI,GAAG,GAAG,EAAE;QAChB,GAAG,CAAC,UAAU,CAAC,CAAC;QAChB,IAAI,KAAK;YAAE,aAAa,CAAC,KAAK,CAAC,CAAC;QAChC,MAAM,CAAC,mBAAmB,EAAE,CAAC;QAC7B,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,CAAC,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC9B,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC/B,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,KAAK,IAAmB,EAAE;IACtC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,YAAY,CAAC,CAAC;QAC5C,GAAG,CAAC,mBAAmB,CAAC,CAAC;IAC3B,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC;IAClB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,KAAK,GAAG,KAAK,IAAmB,EAAE;IACtC,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,MAAM,GAAG,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,CAAA,UAAU,CAAC;QACpB,GAAG,CAAC,cAAc,CAAC,CAAC;IACtB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,GAAG,IAAI,CAAC;QACd,GAAG,CAAC,aAAa,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACtC,CAAC;YAAS,CAAC;QACT,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC;IAClB,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,MAAM,CAAC,GAAG,MAAM,oBAAoB,CAClC,MAAM,CAAC,QAAQ,CAAC,GAAG,EACnB,MAAM,CAAC,QAAQ,CAAC,MAAM,CACvB,CAAC,IAAI,EAAE,CAAC;QACT,IAAI,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM,GAAG,IAAI,CAAC;QACzB,GAAG,CAAC,YAAY,MAAM,CAAC,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,MAAM;QAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACnC,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAwC,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;AAC9E,MAAM,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACxC,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACpD,IAAI,CAAC,GAAG,EAAE,CAAC;IACT,OAAO,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AACD,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE;IACzB,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/server/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
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) => Promise<Multipart>;
15
+ //# 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,KACf,OAAO,CAAC,SAAS,CAyEnB,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) => {
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);
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":"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"}
@@ -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,65 @@
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. ZIP import is planned (unpacks into these). */
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. */
36
+ export interface StagedFile {
37
+ tmpPath: string;
38
+ filename: string;
39
+ ext: ModelExt;
40
+ size: number;
41
+ sha256: string;
42
+ }
43
+ export declare class FileStore {
44
+ readonly maxBytes: number;
45
+ readonly root: string;
46
+ readonly tmpDir: string;
47
+ constructor(dataDir: string, maxBytes: number);
48
+ init(): Promise<void>;
49
+ /**
50
+ * Stream one upload into .tmp. Rejects (and removes the partial file) when
51
+ * the name is not a model file or the stream exceeds maxBytes; `truncated`
52
+ * is busboy's flag for a file cut off at its fileSize limit.
53
+ */
54
+ stage(stream: Readable, rawName: string, truncated: () => boolean): Promise<StagedFile>;
55
+ /** The path, relative to the store root, a version is stored at. */
56
+ static relativePath(itemId: number, n: number, sha256: string, filename: string): string;
57
+ /** Place a staged file at its final path, write-once (EEXIST if anything is there). */
58
+ place(staged: StagedFile, relPath: string): Promise<void>;
59
+ /** Drop a staged file that will not be placed (duplicate, rejected batch, failed transaction). */
60
+ discard(staged: StagedFile): Promise<void>;
61
+ absolute(relPath: string): string;
62
+ exists(relPath: string): Promise<boolean>;
63
+ read(relPath: string): Readable;
64
+ }
65
+ //# 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,yEAAyE;AACzE,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,gEAAgE;AAChE,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,QAAQ,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,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,GACvB,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,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"}
@@ -0,0 +1,170 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { createReadStream } from "node:fs";
3
+ import { link, mkdir, open, stat, unlink } from "node:fs/promises";
4
+ import { dirname, join } from "node:path";
5
+ /**
6
+ * The write-once file store under DATA_DIR/files (docs/plan.md §3.5).
7
+ *
8
+ * - An upload streams into files/.tmp/<uuid>, hashed and counted on the way,
9
+ * fsync'd, and only then placed at files/<item_id>/<n>-<sha12>-<name>.
10
+ * - Placing is link() + unlink(), never rename(): link() fails with EEXIST
11
+ * instead of replacing a file, so a stored version can never be
12
+ * overwritten. The VPS backs files/ up with `rclone --immutable`, which
13
+ * fails loudly on a rewritten file; this keeps it from ever happening.
14
+ * - .tmp sits inside files/ so the link stays on one filesystem.
15
+ */
16
+ /** Model formats we keep. ZIP import is planned (unpacks into these). */
17
+ export const MODEL_EXTENSIONS = ["stl", "3mf", "step", "stp", "obj"];
18
+ /** Formats Bambuddy can take (Send to Bambuddy); STEP it cannot slice. */
19
+ export const BAMBUDDY_EXTENSIONS = new Set([
20
+ "stl",
21
+ "3mf",
22
+ "obj",
23
+ ]);
24
+ export class UploadRejected extends Error {
25
+ status;
26
+ constructor(status, message) {
27
+ super(message);
28
+ this.status = status;
29
+ }
30
+ }
31
+ const MAX_NAME_CHARS = 180;
32
+ /**
33
+ * A file name that is safe on disk and in a Content-Disposition header:
34
+ * NFC, no path separators or control characters, no leading dots, at most
35
+ * 180 characters with the extension kept. Never empty.
36
+ */
37
+ export const safeFilename = (name) => {
38
+ let s = name.normalize("NFC");
39
+ // Only the last path segment (some browsers send a path).
40
+ s = s.split(/[/\\]/).pop() ?? "";
41
+ s = s.replace(/[\u0000-\u001f\u007f"<>:|?*]/g, "_").trim();
42
+ s = s.replace(/^\.+/, "");
43
+ if (s.length > MAX_NAME_CHARS) {
44
+ const dot = s.lastIndexOf(".");
45
+ const ext = dot > 0 && s.length - dot <= 10 ? s.slice(dot) : "";
46
+ s = s.slice(0, MAX_NAME_CHARS - ext.length) + ext;
47
+ }
48
+ return s || "file";
49
+ };
50
+ /**
51
+ * The model extension of a file name, or an UploadRejected that says why
52
+ * not. Sliced files belong in Bambuddy, not here.
53
+ */
54
+ export const modelExt = (filename) => {
55
+ const lower = filename.toLowerCase();
56
+ if (lower.endsWith(".gcode.3mf") || lower.endsWith(".gcode"))
57
+ throw new UploadRejected(415, `${filename}: a sliced file; upload it to Bambuddy instead`);
58
+ const dot = lower.lastIndexOf(".");
59
+ const ext = dot >= 0 ? lower.slice(dot + 1) : "";
60
+ if (MODEL_EXTENSIONS.includes(ext))
61
+ return ext;
62
+ throw new UploadRejected(415, `${filename}: not a supported model file (${MODEL_EXTENSIONS.map((e) => `.${e}`).join(", ")})`);
63
+ };
64
+ /** The title a new item gets from its first file: the name without the extension, separators as spaces. */
65
+ export const titleFromFilename = (filename) => {
66
+ const dot = filename.lastIndexOf(".");
67
+ const stem = dot > 0 ? filename.slice(0, dot) : filename;
68
+ const title = stem.replace(/[_]+/g, " ").replace(/\s+/g, " ").trim();
69
+ return title || filename;
70
+ };
71
+ export class FileStore {
72
+ maxBytes;
73
+ root;
74
+ tmpDir;
75
+ constructor(dataDir, maxBytes) {
76
+ this.maxBytes = maxBytes;
77
+ this.root = join(dataDir, "files");
78
+ this.tmpDir = join(this.root, ".tmp");
79
+ }
80
+ async init() {
81
+ await mkdir(this.tmpDir, { recursive: true });
82
+ }
83
+ /**
84
+ * Stream one upload into .tmp. Rejects (and removes the partial file) when
85
+ * the name is not a model file or the stream exceeds maxBytes; `truncated`
86
+ * is busboy's flag for a file cut off at its fileSize limit.
87
+ */
88
+ async stage(stream, rawName, truncated) {
89
+ const filename = safeFilename(rawName);
90
+ let ext;
91
+ try {
92
+ ext = modelExt(filename);
93
+ }
94
+ catch (e) {
95
+ stream.resume(); // drain, so busboy moves on to the next part
96
+ throw e;
97
+ }
98
+ const tmpPath = join(this.tmpDir, randomUUID());
99
+ const hash = createHash("sha256");
100
+ let size = 0;
101
+ const fh = await open(tmpPath, "wx", 0o640);
102
+ try {
103
+ // Never break out of this loop: leaving a `for await` destroys the
104
+ // stream, and busboy then never finishes the request. Past the cap the
105
+ // rest is read and dropped, and the error comes at the end.
106
+ let tooBig = false;
107
+ for await (const chunk of stream) {
108
+ size += chunk.length;
109
+ if (tooBig || size > this.maxBytes) {
110
+ tooBig = true;
111
+ continue;
112
+ }
113
+ hash.update(chunk);
114
+ await fh.write(chunk);
115
+ }
116
+ if (tooBig || truncated())
117
+ throw new UploadRejected(413, `${filename}: larger than ${this.maxBytes} bytes`);
118
+ await fh.sync();
119
+ }
120
+ catch (e) {
121
+ stream.resume();
122
+ await fh.close();
123
+ await unlink(tmpPath).catch(() => { });
124
+ throw e;
125
+ }
126
+ await fh.close();
127
+ return { tmpPath, filename, ext, size, sha256: hash.digest("hex") };
128
+ }
129
+ /** The path, relative to the store root, a version is stored at. */
130
+ static relativePath(itemId, n, sha256, filename) {
131
+ return join(String(itemId), `${n}-${sha256.slice(0, 12)}-${filename}`);
132
+ }
133
+ /** Place a staged file at its final path, write-once (EEXIST if anything is there). */
134
+ async place(staged, relPath) {
135
+ const target = this.absolute(relPath);
136
+ await mkdir(dirname(target), { recursive: true });
137
+ await link(staged.tmpPath, target);
138
+ await unlink(staged.tmpPath);
139
+ // Make the new directory entry durable as well.
140
+ const dir = await open(dirname(target), "r");
141
+ try {
142
+ await dir.sync();
143
+ }
144
+ finally {
145
+ await dir.close();
146
+ }
147
+ }
148
+ /** Drop a staged file that will not be placed (duplicate, rejected batch, failed transaction). */
149
+ async discard(staged) {
150
+ await unlink(staged.tmpPath).catch(() => { });
151
+ }
152
+ absolute(relPath) {
153
+ const abs = join(this.root, relPath);
154
+ if (!abs.startsWith(this.root + "/"))
155
+ throw new Error(`path outside the store: ${relPath}`);
156
+ return abs;
157
+ }
158
+ async exists(relPath) {
159
+ try {
160
+ return (await stat(this.absolute(relPath))).isFile();
161
+ }
162
+ catch {
163
+ return false;
164
+ }
165
+ }
166
+ read(relPath) {
167
+ return createReadStream(this.absolute(relPath));
168
+ }
169
+ }
170
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/server/storage.ts"],"names":[],"mappings":"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"}