@mxpicture/print-backlog 0.0.4 → 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.
@@ -0,0 +1,39 @@
1
+ import { type HistoryEntry, type Item, type ItemDetail, type UploadResult, type VersionUploadResult } from "../../shared/types.js";
2
+ import type { Sql } from "../db.js";
3
+ import { type Ctx, HANDLED } from "../http.js";
4
+ import { FileStore } from "../storage.js";
5
+ export interface ItemsDeps {
6
+ sql: Sql;
7
+ store: FileStore;
8
+ }
9
+ /** GET /api/v1/items?queue=&status=&q=&trash=1 */
10
+ export declare const listItems: ({ sql }: ItemsDeps) => ({ url }: Ctx) => Promise<Item[]>;
11
+ /** GET /api/v1/items/:id */
12
+ export declare const getItem: ({ sql }: ItemsDeps) => ({ params }: Ctx) => Promise<ItemDetail>;
13
+ /** GET /api/v1/items/:id/history: status changes, versions and matched prints, oldest first. */
14
+ export declare const itemHistory: ({ sql }: ItemsDeps) => ({ params }: Ctx) => Promise<HistoryEntry[]>;
15
+ /**
16
+ * POST /api/v1/queues/:id/items (multipart, field `file`, 1..50 files): one
17
+ * new `queued` item per file, appended in drop order, each with version 1.
18
+ * All or nothing: one rejected file rejects the whole upload.
19
+ */
20
+ export declare const uploadItems: ({ sql, store }: ItemsDeps) => ({ req, params, user }: Ctx) => Promise<UploadResult>;
21
+ /**
22
+ * POST /api/v1/items/:id/versions (multipart, exactly one `file`, optional
23
+ * field `comment`): a new version that becomes current. The same content as
24
+ * an existing version of the item stores nothing (`duplicate: true`).
25
+ */
26
+ export declare const uploadVersion: ({ sql, store }: ItemsDeps) => ({ req, params, user }: Ctx) => Promise<VersionUploadResult>;
27
+ /** PATCH /api/v1/items/:id/versions/:n: the comment, or make it the current version. */
28
+ export declare const patchVersion: ({ sql }: ItemsDeps) => ({ params, body }: Ctx) => Promise<ItemDetail>;
29
+ /** GET /api/v1/items/:id/versions/:n/download: the file under its original name. */
30
+ export declare const downloadVersion: ({ sql, store }: ItemsDeps) => ({ params, res, req }: Ctx) => Promise<typeof HANDLED>;
31
+ /** attachment; an ASCII fallback plus the RFC 5987 UTF-8 name. */
32
+ export declare const contentDisposition: (filename: string) => string;
33
+ /** PATCH /api/v1/items/:id (see ItemPatch). A status change is recorded in the history. */
34
+ export declare const patchItem: ({ sql }: ItemsDeps) => ({ params, body, user }: Ctx) => Promise<ItemDetail>;
35
+ /** DELETE /api/v1/items/:id: to the trash (rows and files stay until the purge). */
36
+ export declare const deleteItem: ({ sql }: ItemsDeps) => ({ params }: Ctx) => Promise<Item>;
37
+ /** POST /api/v1/items/:id/restore: back from the trash, at the end of its queue. */
38
+ export declare const restoreItem: ({ sql }: ItemsDeps) => ({ params }: Ctx) => Promise<Item>;
39
+ //# sourceMappingURL=items.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"items.d.ts","sourceRoot":"","sources":["../../../src/server/api/items.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,IAAI,EACT,KAAK,UAAU,EAEf,KAAK,YAAY,EAEjB,KAAK,mBAAmB,EACzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,KAAK,GAAG,EAAE,OAAO,EAAa,MAAM,YAAY,CAAC;AAG1D,OAAO,EAEL,SAAS,EAGV,MAAM,eAAe,CAAC;AAKvB,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,GAAG,CAAC;IACT,KAAK,EAAE,SAAS,CAAC;CAClB;AAwJD,kDAAkD;AAClD,eAAO,MAAM,SAAS,GACnB,SAAS,SAAS,MACZ,SAAS,GAAG,KAAG,OAAO,CAAC,IAAI,EAAE,CAgBnC,CAAC;AAEJ,4BAA4B;AAC5B,eAAO,MAAM,OAAO,GACjB,SAAS,SAAS,MAClB,YAAY,GAAG,KAAG,OAAO,CAAC,UAAU,CACA,CAAC;AAExC,gGAAgG;AAChG,eAAO,MAAM,WAAW,GACrB,SAAS,SAAS,MACZ,YAAY,GAAG,KAAG,OAAO,CAAC,YAAY,EAAE,CAkE9C,CAAC;AAuDJ;;;;GAIG;AACH,eAAO,MAAM,WAAW,GACrB,gBAAgB,SAAS,MACnB,uBAAuB,GAAG,KAAG,OAAO,CAAC,YAAY,CAiDvD,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACvB,gBAAgB,SAAS,MACnB,uBAAuB,GAAG,KAAG,OAAO,CAAC,mBAAmB,CA4C9D,CAAC;AASJ,wFAAwF;AACxF,eAAO,MAAM,YAAY,GACtB,SAAS,SAAS,MACZ,kBAAkB,GAAG,KAAG,OAAO,CAAC,UAAU,CAgBhD,CAAC;AAEJ,oFAAoF;AACpF,eAAO,MAAM,eAAe,GACzB,gBAAgB,SAAS,MACnB,sBAAsB,GAAG,KAAG,OAAO,CAAC,OAAO,OAAO,CAgCxD,CAAC;AAEJ,kEAAkE;AAClE,eAAO,MAAM,kBAAkB,GAAI,UAAU,MAAM,KAAG,MAGrD,CAAC;AAmBF,2FAA2F;AAC3F,eAAO,MAAM,SAAS,GACnB,SAAS,SAAS,MACZ,wBAAwB,GAAG,KAAG,OAAO,CAAC,UAAU,CAuCtD,CAAC;AAEJ,oFAAoF;AACpF,eAAO,MAAM,UAAU,GACpB,SAAS,SAAS,MACZ,YAAY,GAAG,KAAG,OAAO,CAAC,IAAI,CAOpC,CAAC;AAEJ,oFAAoF;AACpF,eAAO,MAAM,WAAW,GACrB,SAAS,SAAS,MACZ,YAAY,GAAG,KAAG,OAAO,CAAC,IAAI,CAmBpC,CAAC"}
@@ -0,0 +1,428 @@
1
+ import { z } from "zod";
2
+ import { ITEM_STATUSES, } from "../../shared/types.js";
3
+ import { HANDLED, HttpError } from "../http.js";
4
+ import { readMultipart } from "../multipart.js";
5
+ import { itemPosition } from "../positions.js";
6
+ import { BAMBUDDY_EXTENSIONS, FileStore, titleFromFilename, } from "../storage.js";
7
+ const idParam = (v, what = "id") => {
8
+ const n = Number(v);
9
+ if (!Number.isSafeInteger(n) || n <= 0)
10
+ throw new HttpError(400, `bad ${what}`);
11
+ return n;
12
+ };
13
+ const toVersion = (r, currentId) => ({
14
+ n: r.n,
15
+ filename: r.filename,
16
+ ext: r.ext,
17
+ size: Number(r.size),
18
+ sha256: r.sha256,
19
+ comment: r.comment,
20
+ uploadedAt: r.uploaded_at.toISOString(),
21
+ uploadedBy: r.uploaded_by,
22
+ current: r.id === currentId,
23
+ });
24
+ const toItem = (r) => {
25
+ const current = r.v_n === null
26
+ ? null
27
+ : {
28
+ n: r.v_n,
29
+ filename: r.v_filename,
30
+ ext: r.v_ext,
31
+ size: Number(r.v_size),
32
+ sha256: r.v_sha256,
33
+ comment: r.v_comment,
34
+ uploadedAt: r.v_uploaded_at.toISOString(),
35
+ uploadedBy: r.v_uploaded_by,
36
+ current: true,
37
+ };
38
+ return {
39
+ id: Number(r.id),
40
+ queueId: Number(r.queue_id),
41
+ position: Number(r.position),
42
+ title: r.title,
43
+ status: r.status,
44
+ statusChangedAt: r.status_changed_at.toISOString(),
45
+ needsConfirm: r.needs_confirm,
46
+ notes: r.notes,
47
+ sourceUrl: r.source_url,
48
+ versionCount: r.version_count,
49
+ current,
50
+ sendable: current !== null && BAMBUDDY_EXTENSIONS.has(current.ext),
51
+ bambuddyFileId: r.bambuddy_file_id,
52
+ bambuddySentAt: r.bambuddy_sent_at?.toISOString() ?? null,
53
+ deletedAt: r.deleted_at?.toISOString() ?? null,
54
+ createdAt: r.created_at.toISOString(),
55
+ createdBy: r.created_by,
56
+ };
57
+ };
58
+ /** Items matching `where` (a fragment over alias i), in list order. */
59
+ const selectItems = (db, where) => db `
60
+ SELECT i.*,
61
+ (SELECT count(*)::int FROM backlog.version vc WHERE vc.item_id = i.id) AS version_count,
62
+ v.n AS v_n, v.filename AS v_filename, v.ext AS v_ext, v.size AS v_size, v.sha256 AS v_sha256,
63
+ v.comment AS v_comment, v.uploaded_at AS v_uploaded_at, v.uploaded_by AS v_uploaded_by
64
+ FROM backlog.item i
65
+ LEFT JOIN backlog.version v ON v.id = i.current_version_id
66
+ WHERE ${where}
67
+ ORDER BY i.position, i.id
68
+ `;
69
+ const loadItem = async (db, id) => {
70
+ const [row] = await selectItems(db, db `i.id = ${id}`);
71
+ if (!row)
72
+ throw new HttpError(404, `item ${id} not found`);
73
+ return toItem(row);
74
+ };
75
+ const loadDetail = async (db, id) => {
76
+ const item = await loadItem(db, id);
77
+ const [{ current_version_id: currentId } = { current_version_id: null }] = await db `
78
+ SELECT current_version_id FROM backlog.item WHERE id = ${id}
79
+ `;
80
+ const versions = await db `
81
+ SELECT * FROM backlog.version WHERE item_id = ${id} ORDER BY n DESC
82
+ `;
83
+ const alsoIn = item.current
84
+ ? await db `
85
+ SELECT DISTINCT i.id, i.title FROM backlog.version v JOIN backlog.item i ON i.id = v.item_id
86
+ WHERE v.sha256 = ${item.current.sha256} AND i.id <> ${id} AND i.deleted_at IS NULL
87
+ ORDER BY i.id
88
+ `
89
+ : [];
90
+ return {
91
+ ...item,
92
+ versions: versions.map((v) => toVersion(v, currentId)),
93
+ alsoIn: alsoIn.map((r) => ({ id: Number(r.id), title: r.title })),
94
+ };
95
+ };
96
+ const listQuery = z.object({
97
+ queue: z.coerce.number().int().positive().optional(),
98
+ status: z.enum(ITEM_STATUSES).optional(),
99
+ q: z.string().trim().max(200).optional(),
100
+ trash: z.enum(["0", "1"]).optional(),
101
+ });
102
+ /** GET /api/v1/items?queue=&status=&q=&trash=1 */
103
+ export const listItems = ({ sql }) => async ({ url }) => {
104
+ const p = listQuery.parse(Object.fromEntries(url.searchParams));
105
+ const conds = [
106
+ p.trash === "1"
107
+ ? sql `i.deleted_at IS NOT NULL`
108
+ : sql `i.deleted_at IS NULL`,
109
+ ];
110
+ if (p.queue)
111
+ conds.push(sql `i.queue_id = ${p.queue}`);
112
+ if (p.status)
113
+ conds.push(sql `i.status = ${p.status}::text::backlog.item_status`);
114
+ if (p.q)
115
+ conds.push(sql `i.title ILIKE ${"%" + p.q.replace(/[\\%_]/g, (c) => "\\" + c) + "%"}`);
116
+ const where = conds.reduce((acc, c) => sql `${acc} AND ${c}`);
117
+ return (await selectItems(sql, where)).map(toItem);
118
+ };
119
+ /** GET /api/v1/items/:id */
120
+ export const getItem = ({ sql }) => ({ params }) => loadDetail(sql, idParam(params.id));
121
+ /** GET /api/v1/items/:id/history: status changes, versions and matched prints, oldest first. */
122
+ export const itemHistory = ({ sql }) => async ({ params }) => {
123
+ const id = idParam(params.id);
124
+ await loadItem(sql, id);
125
+ const [statuses, versions, prints] = await Promise.all([
126
+ sql `
127
+ SELECT at, by, source, from_status::text AS from_status, to_status::text AS to_status, detail
128
+ FROM backlog.status_event WHERE item_id = ${id}
129
+ `,
130
+ sql `
131
+ SELECT uploaded_at, uploaded_by, n, filename FROM backlog.version WHERE item_id = ${id}
132
+ `,
133
+ sql `
134
+ SELECT received_at, event, printer, filename, resolution FROM backlog.print_event WHERE matched_item_id = ${id}
135
+ `,
136
+ ]);
137
+ const entries = [
138
+ ...statuses.map((s) => ({
139
+ kind: "status",
140
+ at: s.at.toISOString(),
141
+ by: s.by,
142
+ source: s.source,
143
+ from: s.from_status,
144
+ to: s.to_status,
145
+ detail: s.detail,
146
+ })),
147
+ ...versions.map((v) => ({
148
+ kind: "version",
149
+ at: v.uploaded_at.toISOString(),
150
+ by: v.uploaded_by,
151
+ n: v.n,
152
+ filename: v.filename,
153
+ })),
154
+ ...prints.map((p) => ({
155
+ kind: "print",
156
+ at: p.received_at.toISOString(),
157
+ event: p.event,
158
+ printer: p.printer,
159
+ filename: p.filename,
160
+ resolution: p.resolution,
161
+ })),
162
+ ];
163
+ return entries.sort((a, b) => a.at.localeCompare(b.at));
164
+ };
165
+ // --- Writing ---------------------------------------------------------------------
166
+ /** Lock a queue row for the transaction (serialises positions in it). */
167
+ const lockQueue = async (tx, queueId) => {
168
+ const [q] = await tx `SELECT id FROM backlog.queue WHERE id = ${queueId} FOR UPDATE`;
169
+ if (!q)
170
+ throw new HttpError(404, `queue ${queueId} not found`);
171
+ };
172
+ /** Lock an item row; refuses a deleted item unless `allowDeleted`. */
173
+ const lockItem = async (tx, id, allowDeleted = false) => {
174
+ const [row] = await tx `
175
+ SELECT id, queue_id, status::text AS status, deleted_at, current_version_id FROM backlog.item WHERE id = ${id} FOR UPDATE
176
+ `;
177
+ if (!row)
178
+ throw new HttpError(404, `item ${id} not found`);
179
+ if (row.deleted_at && !allowDeleted)
180
+ throw new HttpError(409, `item ${id} is in the trash; restore it first`);
181
+ return {
182
+ queueId: Number(row.queue_id),
183
+ status: row.status,
184
+ deletedAt: row.deleted_at,
185
+ currentVersionId: row.current_version_id,
186
+ };
187
+ };
188
+ const insertVersion = async (tx, store, itemId, n, f, user, comment) => {
189
+ const rel = FileStore.relativePath(itemId, n, f.sha256, f.filename);
190
+ const [v] = await tx `
191
+ INSERT INTO backlog.version (item_id, n, filename, ext, size, sha256, stored_path, comment, uploaded_by)
192
+ VALUES (${itemId}, ${n}, ${f.filename}, ${f.ext}, ${f.size}, ${f.sha256}, ${rel}, ${comment}, ${user})
193
+ RETURNING id
194
+ `;
195
+ // Placed inside the transaction: if it fails, the rows roll back with it.
196
+ await store.place(f, rel);
197
+ return v.id;
198
+ };
199
+ /**
200
+ * POST /api/v1/queues/:id/items (multipart, field `file`, 1..50 files): one
201
+ * new `queued` item per file, appended in drop order, each with version 1.
202
+ * All or nothing: one rejected file rejects the whole upload.
203
+ */
204
+ export const uploadItems = ({ sql, store }) => async ({ req, params, user }) => {
205
+ const queueId = idParam(params.id, "queue id");
206
+ const { files } = await readMultipart(req, store);
207
+ if (files.length === 0)
208
+ throw new HttpError(400, "no file in the upload");
209
+ try {
210
+ const ids = await sql.begin(async (tx) => {
211
+ await lockQueue(tx, queueId);
212
+ const out = [];
213
+ for (const f of files) {
214
+ const [last] = await tx `
215
+ SELECT id FROM backlog.item WHERE queue_id = ${queueId} AND deleted_at IS NULL
216
+ ORDER BY position DESC, id DESC LIMIT 1
217
+ `;
218
+ const position = await itemPosition(tx, queueId, last ? Number(last.id) : null, null);
219
+ const [item] = await tx `
220
+ INSERT INTO backlog.item (queue_id, position, title, created_by)
221
+ VALUES (${queueId}, ${position}, ${titleFromFilename(f.filename)}, ${user})
222
+ RETURNING id
223
+ `;
224
+ const itemId = Number(item.id);
225
+ const versionId = await insertVersion(tx, store, itemId, 1, f, user, null);
226
+ await tx `UPDATE backlog.item SET current_version_id = ${versionId} WHERE id = ${itemId}`;
227
+ await tx `
228
+ INSERT INTO backlog.status_event (item_id, from_status, to_status, by, source, detail)
229
+ VALUES (${itemId}, NULL, 'queued', ${user}, 'ui', ${tx.json({ created: f.filename })})
230
+ `;
231
+ out.push(itemId);
232
+ }
233
+ return out;
234
+ });
235
+ const items = await Promise.all(ids.map((id) => loadItem(sql, id)));
236
+ return { items };
237
+ }
238
+ catch (e) {
239
+ await Promise.all(files.map((f) => store.discard(f)));
240
+ throw e;
241
+ }
242
+ };
243
+ /**
244
+ * POST /api/v1/items/:id/versions (multipart, exactly one `file`, optional
245
+ * field `comment`): a new version that becomes current. The same content as
246
+ * an existing version of the item stores nothing (`duplicate: true`).
247
+ */
248
+ export const uploadVersion = ({ sql, store }) => async ({ req, params, user }) => {
249
+ const id = idParam(params.id);
250
+ const { files, fields } = await readMultipart(req, store);
251
+ if (files.length !== 1) {
252
+ await Promise.all(files.map((f) => store.discard(f)));
253
+ throw new HttpError(400, "a new version is exactly one file");
254
+ }
255
+ const f = files[0];
256
+ const comment = fields.comment?.trim()
257
+ ? fields.comment.trim().slice(0, 2000)
258
+ : null;
259
+ try {
260
+ const { duplicate, n } = await sql.begin(async (tx) => {
261
+ await lockItem(tx, id);
262
+ const [same] = await tx `SELECT n FROM backlog.version WHERE item_id = ${id} AND sha256 = ${f.sha256}`;
263
+ if (same)
264
+ return { duplicate: true, n: same.n };
265
+ const [{ next } = { next: 1 }] = await tx `
266
+ SELECT coalesce(max(n), 0) + 1 AS next FROM backlog.version WHERE item_id = ${id}
267
+ `;
268
+ const versionId = await insertVersion(tx, store, id, next, f, user, comment);
269
+ await tx `UPDATE backlog.item SET current_version_id = ${versionId} WHERE id = ${id}`;
270
+ return { duplicate: false, n: next };
271
+ });
272
+ if (duplicate)
273
+ await store.discard(f);
274
+ const item = await loadDetail(sql, id);
275
+ return {
276
+ item,
277
+ duplicate,
278
+ version: item.versions.find((v) => v.n === n),
279
+ };
280
+ }
281
+ catch (e) {
282
+ await store.discard(f);
283
+ throw e;
284
+ }
285
+ };
286
+ const versionPatch = z
287
+ .object({
288
+ comment: z.string().max(2000).nullable().optional(),
289
+ current: z.literal(true).optional(),
290
+ })
291
+ .strict();
292
+ /** PATCH /api/v1/items/:id/versions/:n: the comment, or make it the current version. */
293
+ export const patchVersion = ({ sql }) => async ({ params, body }) => {
294
+ const id = idParam(params.id);
295
+ const n = idParam(params.n, "version");
296
+ const p = versionPatch.parse(body);
297
+ await sql.begin(async (tx) => {
298
+ await lockItem(tx, id);
299
+ const [v] = await tx `SELECT id FROM backlog.version WHERE item_id = ${id} AND n = ${n}`;
300
+ if (!v)
301
+ throw new HttpError(404, `version ${n} of item ${id} not found`);
302
+ if (p.comment !== undefined)
303
+ await tx `UPDATE backlog.version SET comment = ${p.comment?.trim() || null} WHERE id = ${v.id}`;
304
+ if (p.current)
305
+ await tx `UPDATE backlog.item SET current_version_id = ${v.id} WHERE id = ${id}`;
306
+ });
307
+ return loadDetail(sql, id);
308
+ };
309
+ /** GET /api/v1/items/:id/versions/:n/download: the file under its original name. */
310
+ export const downloadVersion = ({ sql, store }) => async ({ params, res, req }) => {
311
+ const id = idParam(params.id);
312
+ const n = idParam(params.n, "version");
313
+ const [v] = await sql `
314
+ SELECT filename, size, stored_path FROM backlog.version WHERE item_id = ${id} AND n = ${n}
315
+ `;
316
+ if (!v)
317
+ throw new HttpError(404, `version ${n} of item ${id} not found`);
318
+ if (!(await store.exists(v.stored_path)))
319
+ throw new HttpError(410, `the file of version ${n} is missing on disk`);
320
+ res.writeHead(200, {
321
+ "Content-Type": "application/octet-stream",
322
+ "Content-Length": v.size,
323
+ "Content-Disposition": contentDisposition(v.filename),
324
+ "Cache-Control": "private, max-age=31536000, immutable",
325
+ "X-Content-Type-Options": "nosniff",
326
+ });
327
+ if (req.method === "HEAD") {
328
+ res.end();
329
+ return HANDLED;
330
+ }
331
+ await new Promise((resolve) => {
332
+ const s = store.read(v.stored_path);
333
+ s.on("error", () => {
334
+ res.destroy();
335
+ resolve();
336
+ });
337
+ res.on("close", resolve);
338
+ s.pipe(res);
339
+ });
340
+ return HANDLED;
341
+ };
342
+ /** attachment; an ASCII fallback plus the RFC 5987 UTF-8 name. */
343
+ export const contentDisposition = (filename) => {
344
+ const ascii = filename.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
345
+ return `attachment; filename="${ascii}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
346
+ };
347
+ const itemPatch = z
348
+ .object({
349
+ title: z.string().trim().min(1).max(300).optional(),
350
+ notes: z.string().max(20_000).nullable().optional(),
351
+ sourceUrl: z.url().max(2000).nullable().optional(),
352
+ status: z.enum(ITEM_STATUSES).optional(),
353
+ reason: z.string().trim().max(2000).optional(),
354
+ move: z
355
+ .object({
356
+ queueId: z.number().int().positive().optional(),
357
+ afterId: z.number().int().positive().nullable(),
358
+ })
359
+ .strict()
360
+ .optional(),
361
+ })
362
+ .strict();
363
+ /** PATCH /api/v1/items/:id (see ItemPatch). A status change is recorded in the history. */
364
+ export const patchItem = ({ sql }) => async ({ params, body, user }) => {
365
+ const id = idParam(params.id);
366
+ const p = itemPatch.parse(body);
367
+ if (p.reason !== undefined && p.status === undefined)
368
+ throw new HttpError(400, "reason: only with a status change");
369
+ await sql.begin(async (tx) => {
370
+ const item = await lockItem(tx, id);
371
+ if (p.title !== undefined)
372
+ await tx `UPDATE backlog.item SET title = ${p.title} WHERE id = ${id}`;
373
+ if (p.notes !== undefined)
374
+ await tx `UPDATE backlog.item SET notes = ${p.notes?.trim() || null} WHERE id = ${id}`;
375
+ if (p.sourceUrl !== undefined)
376
+ await tx `UPDATE backlog.item SET source_url = ${p.sourceUrl} WHERE id = ${id}`;
377
+ if (p.status !== undefined && p.status !== item.status) {
378
+ await tx `
379
+ UPDATE backlog.item
380
+ SET status = ${p.status}::text::backlog.item_status, status_changed_at = now(), needs_confirm = false
381
+ WHERE id = ${id}
382
+ `;
383
+ await tx `
384
+ INSERT INTO backlog.status_event (item_id, from_status, to_status, by, source, detail)
385
+ VALUES (${id}, ${item.status}::text::backlog.item_status, ${p.status}::text::backlog.item_status, ${user}, 'ui',
386
+ ${p.reason ? tx.json({ reason: p.reason }) : null})
387
+ `;
388
+ }
389
+ if (p.move) {
390
+ const target = p.move.queueId ?? item.queueId;
391
+ if (p.move.afterId === id)
392
+ throw new HttpError(400, "an item cannot follow itself");
393
+ // Lock in id order, so two moves between the same queues cannot deadlock.
394
+ for (const q of [...new Set([item.queueId, target])].sort((a, b) => a - b))
395
+ await lockQueue(tx, q);
396
+ const position = await itemPosition(tx, target, p.move.afterId, id);
397
+ await tx `UPDATE backlog.item SET queue_id = ${target}, position = ${position} WHERE id = ${id}`;
398
+ }
399
+ });
400
+ return loadDetail(sql, id);
401
+ };
402
+ /** DELETE /api/v1/items/:id: to the trash (rows and files stay until the purge). */
403
+ export const deleteItem = ({ sql }) => async ({ params }) => {
404
+ const id = idParam(params.id);
405
+ await sql.begin(async (tx) => {
406
+ await lockItem(tx, id);
407
+ await tx `UPDATE backlog.item SET deleted_at = now() WHERE id = ${id}`;
408
+ });
409
+ return loadItem(sql, id);
410
+ };
411
+ /** POST /api/v1/items/:id/restore: back from the trash, at the end of its queue. */
412
+ export const restoreItem = ({ sql }) => async ({ params }) => {
413
+ const id = idParam(params.id);
414
+ await sql.begin(async (tx) => {
415
+ const item = await lockItem(tx, id, true);
416
+ if (!item.deletedAt)
417
+ return;
418
+ await lockQueue(tx, item.queueId);
419
+ const [last] = await tx `
420
+ SELECT id FROM backlog.item WHERE queue_id = ${item.queueId} AND deleted_at IS NULL
421
+ ORDER BY position DESC, id DESC LIMIT 1
422
+ `;
423
+ const position = await itemPosition(tx, item.queueId, last ? Number(last.id) : null, id);
424
+ await tx `UPDATE backlog.item SET deleted_at = NULL, position = ${position} WHERE id = ${id}`;
425
+ });
426
+ return loadItem(sql, id);
427
+ };
428
+ //# sourceMappingURL=items.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"items.js","sourceRoot":"","sources":["../../../src/server/api/items.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,17 @@
1
1
  import { type QueueSummary } from "../../shared/types.js";
2
2
  import type { Sql } from "../db.js";
3
+ import { type Ctx } from "../http.js";
3
4
  /** GET /api/v1/queues: every queue with its item counts per status (deleted items excluded). */
4
5
  export declare const listQueues: (sql: Sql) => () => Promise<QueueSummary[]>;
6
+ /** POST /api/v1/queues {name, autoStatus?, afterId?}: a new queue, last unless afterId says otherwise. */
7
+ export declare const createQueue: (sql: Sql) => ({ body }: Ctx) => Promise<QueueSummary>;
8
+ /** PATCH /api/v1/queues/:id {name?, autoStatus?, afterId?} */
9
+ export declare const patchQueue: (sql: Sql) => ({ params, body }: Ctx) => Promise<QueueSummary>;
10
+ /**
11
+ * DELETE /api/v1/queues/:id: only an empty queue (its trash included), and
12
+ * never the last one.
13
+ */
14
+ export declare const deleteQueue: (sql: Sql) => ({ params }: Ctx) => Promise<{
15
+ deleted: number;
16
+ }>;
5
17
  //# sourceMappingURL=queues.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"queues.d.ts","sourceRoot":"","sources":["../../../src/server/api/queues.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,gGAAgG;AAChG,eAAO,MAAM,UAAU,GAAI,KAAK,GAAG,WAAe,OAAO,CAAC,YAAY,EAAE,CAqCvE,CAAC"}
1
+ {"version":3,"file":"queues.d.ts","sourceRoot":"","sources":["../../../src/server/api/queues.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,KAAK,GAAG,EAAa,MAAM,YAAY,CAAC;AAGjD,gGAAgG;AAChG,eAAO,MAAM,UAAU,GAAI,KAAK,GAAG,WAAe,OAAO,CAAC,YAAY,EAAE,CAqCvE,CAAC;AAuBF,0GAA0G;AAC1G,eAAO,MAAM,WAAW,GACrB,KAAK,GAAG,MACF,UAAU,GAAG,KAAG,OAAO,CAAC,YAAY,CAoB1C,CAAC;AAEJ,8DAA8D;AAC9D,eAAO,MAAM,UAAU,GACpB,KAAK,GAAG,MACF,kBAAkB,GAAG,KAAG,OAAO,CAAC,YAAY,CAmBlD,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,WAAW,GACrB,KAAK,GAAG,MACF,YAAY,GAAG,KAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAsBnD,CAAC"}
@@ -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