hotsheet 0.17.0-beta.16 → 0.17.0-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/channel.js +1 -1
- package/dist/cli.js +192 -17
- package/dist/client/app.global.js +106 -106
- package/package.json +2 -1
package/dist/channel.js
CHANGED
|
@@ -34369,7 +34369,7 @@ async function proxyRequest(settings, path, init, fetchFn) {
|
|
|
34369
34369
|
var UpdateTicketInputSchema = external_exports3.object({
|
|
34370
34370
|
id: external_exports3.number().int().describe("Ticket id (numeric, e.g. 42)"),
|
|
34371
34371
|
status: TicketStatusSchema.optional(),
|
|
34372
|
-
notes: external_exports3.string().optional().describe(
|
|
34372
|
+
notes: external_exports3.string().optional().describe('Append a new note. Pass the markdown body as a plain string (NOT a JSON array \u2014 the server wraps the text in `{id, text, created_at}` automatically). HS-8427 \u2014 if you mistakenly pass a JSON-stringified note array like `[{"text":"..."}]`, the server unwraps it defensively so the body renders correctly, but plain text is the documented + preferred shape.'),
|
|
34373
34373
|
priority: TicketPrioritySchema.optional(),
|
|
34374
34374
|
category: external_exports3.string().optional().describe('Category id (e.g. "bug", "feature", "task", "issue")'),
|
|
34375
34375
|
up_next: external_exports3.boolean().optional(),
|
package/dist/cli.js
CHANGED
|
@@ -575,6 +575,7 @@ __export(connection_exports, {
|
|
|
575
575
|
getDataDir: () => getDataDir,
|
|
576
576
|
getDb: () => getDb,
|
|
577
577
|
getDbForDir: () => getDbForDir,
|
|
578
|
+
isRecoverableOpenError: () => isRecoverableOpenError,
|
|
578
579
|
readRecoveryMarker: () => readRecoveryMarker,
|
|
579
580
|
runWithDataDir: () => runWithDataDir,
|
|
580
581
|
setDataDir: () => setDataDir
|
|
@@ -583,6 +584,20 @@ import { AsyncLocalStorage } from "async_hooks";
|
|
|
583
584
|
import { PGlite } from "@electric-sql/pglite";
|
|
584
585
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync, rmSync as rmSync2, writeFileSync } from "fs";
|
|
585
586
|
import { join as join3 } from "path";
|
|
587
|
+
function isRecoverableOpenError(err) {
|
|
588
|
+
if (err === null || err === void 0) return false;
|
|
589
|
+
let message;
|
|
590
|
+
if (err instanceof Error) message = err.message;
|
|
591
|
+
else if (typeof err === "string") message = err;
|
|
592
|
+
else if (typeof err === "number" || typeof err === "boolean") message = String(err);
|
|
593
|
+
else return false;
|
|
594
|
+
const errName = err instanceof Error ? err.name : "";
|
|
595
|
+
if (message.includes("Aborted")) return true;
|
|
596
|
+
if (message.includes("RuntimeError")) return true;
|
|
597
|
+
if (errName === "RuntimeError") return true;
|
|
598
|
+
if (message.includes("catalog is missing")) return true;
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
586
601
|
function recoveryMarkerPath(dataDir) {
|
|
587
602
|
return join3(dataDir, RECOVERY_MARKER_FILENAME);
|
|
588
603
|
}
|
|
@@ -710,9 +725,7 @@ async function openAndCacheDb(dbPath) {
|
|
|
710
725
|
async function recoverFromOpenFailure(dbPath, err) {
|
|
711
726
|
const message = err instanceof Error ? err.message : String(err);
|
|
712
727
|
const stack = err instanceof Error ? err.stack : void 0;
|
|
713
|
-
|
|
714
|
-
const isRuntimeFailure = message.includes("Aborted") || message.includes("RuntimeError") || errName === "RuntimeError";
|
|
715
|
-
if (!isRuntimeFailure) throw err;
|
|
728
|
+
if (!isRecoverableOpenError(err)) throw err;
|
|
716
729
|
console.error("Failed to open database:", message);
|
|
717
730
|
if (stack !== void 0) console.error(stack);
|
|
718
731
|
if (tryRemoveStalePostmasterPid(dbPath)) {
|
|
@@ -824,6 +837,12 @@ async function initSchema(db) {
|
|
|
824
837
|
`).catch((e) => {
|
|
825
838
|
if (e instanceof Error && !e.message.includes("already exists")) console.error("Migration error (columns):", e.message);
|
|
826
839
|
});
|
|
840
|
+
await db.exec(`
|
|
841
|
+
ALTER TABLE attachments ADD COLUMN IF NOT EXISTS draft_id TEXT;
|
|
842
|
+
CREATE INDEX IF NOT EXISTS idx_attachments_draft ON attachments(draft_id);
|
|
843
|
+
`).catch((e) => {
|
|
844
|
+
if (e instanceof Error && !e.message.includes("already exists")) console.error("Migration error (attachments.draft_id):", e.message);
|
|
845
|
+
});
|
|
827
846
|
await db.exec(`
|
|
828
847
|
CREATE TABLE IF NOT EXISTS ticket_sync (
|
|
829
848
|
id SERIAL PRIMARY KEY,
|
|
@@ -921,7 +940,7 @@ var SCHEMA_VERSION, RECOVERY_MARKER_FILENAME, databases, defaultDbPath, requestD
|
|
|
921
940
|
var init_connection = __esm({
|
|
922
941
|
"src/db/connection.ts"() {
|
|
923
942
|
"use strict";
|
|
924
|
-
SCHEMA_VERSION =
|
|
943
|
+
SCHEMA_VERSION = 2;
|
|
925
944
|
RECOVERY_MARKER_FILENAME = ".db-recovery-marker.json";
|
|
926
945
|
databases = /* @__PURE__ */ new Map();
|
|
927
946
|
defaultDbPath = null;
|
|
@@ -16022,6 +16041,18 @@ var init_backup = __esm({
|
|
|
16022
16041
|
});
|
|
16023
16042
|
|
|
16024
16043
|
// src/db/attachments.ts
|
|
16044
|
+
var attachments_exports = {};
|
|
16045
|
+
__export(attachments_exports, {
|
|
16046
|
+
addAttachment: () => addAttachment,
|
|
16047
|
+
addDraftAttachment: () => addDraftAttachment,
|
|
16048
|
+
deleteAttachment: () => deleteAttachment,
|
|
16049
|
+
deleteDraftAttachments: () => deleteDraftAttachments,
|
|
16050
|
+
getAttachment: () => getAttachment,
|
|
16051
|
+
getAttachments: () => getAttachments,
|
|
16052
|
+
getDraftAttachments: () => getDraftAttachments,
|
|
16053
|
+
listOrphanDraftAttachments: () => listOrphanDraftAttachments,
|
|
16054
|
+
promoteDraftAttachments: () => promoteDraftAttachments
|
|
16055
|
+
});
|
|
16025
16056
|
async function addAttachment(ticketId, originalFilename, storedPath) {
|
|
16026
16057
|
const db = await getDb();
|
|
16027
16058
|
const result = await db.query(
|
|
@@ -16030,14 +16061,30 @@ async function addAttachment(ticketId, originalFilename, storedPath) {
|
|
|
16030
16061
|
);
|
|
16031
16062
|
return result.rows[0];
|
|
16032
16063
|
}
|
|
16064
|
+
async function addDraftAttachment(ticketId, draftId, originalFilename, storedPath) {
|
|
16065
|
+
const db = await getDb();
|
|
16066
|
+
const result = await db.query(
|
|
16067
|
+
`INSERT INTO attachments (ticket_id, draft_id, original_filename, stored_path) VALUES ($1, $2, $3, $4) RETURNING *`,
|
|
16068
|
+
[ticketId, draftId, originalFilename, storedPath]
|
|
16069
|
+
);
|
|
16070
|
+
return result.rows[0];
|
|
16071
|
+
}
|
|
16033
16072
|
async function getAttachments(ticketId) {
|
|
16034
16073
|
const db = await getDb();
|
|
16035
16074
|
const result = await db.query(
|
|
16036
|
-
`SELECT * FROM attachments WHERE ticket_id = $1 ORDER BY created_at ASC`,
|
|
16075
|
+
`SELECT * FROM attachments WHERE ticket_id = $1 AND draft_id IS NULL ORDER BY created_at ASC`,
|
|
16037
16076
|
[ticketId]
|
|
16038
16077
|
);
|
|
16039
16078
|
return result.rows;
|
|
16040
16079
|
}
|
|
16080
|
+
async function getDraftAttachments(draftId) {
|
|
16081
|
+
const db = await getDb();
|
|
16082
|
+
const result = await db.query(
|
|
16083
|
+
`SELECT * FROM attachments WHERE draft_id = $1 ORDER BY created_at ASC`,
|
|
16084
|
+
[draftId]
|
|
16085
|
+
);
|
|
16086
|
+
return result.rows;
|
|
16087
|
+
}
|
|
16041
16088
|
async function getAttachment(id) {
|
|
16042
16089
|
const db = await getDb();
|
|
16043
16090
|
const result = await db.query(
|
|
@@ -16054,6 +16101,36 @@ async function deleteAttachment(id) {
|
|
|
16054
16101
|
);
|
|
16055
16102
|
return result.rows[0] ?? null;
|
|
16056
16103
|
}
|
|
16104
|
+
async function promoteDraftAttachments(draftId) {
|
|
16105
|
+
const db = await getDb();
|
|
16106
|
+
const result = await db.query(
|
|
16107
|
+
`UPDATE attachments SET draft_id = NULL WHERE draft_id = $1 RETURNING *`,
|
|
16108
|
+
[draftId]
|
|
16109
|
+
);
|
|
16110
|
+
return result.rows;
|
|
16111
|
+
}
|
|
16112
|
+
async function deleteDraftAttachments(draftId) {
|
|
16113
|
+
const db = await getDb();
|
|
16114
|
+
const result = await db.query(
|
|
16115
|
+
`DELETE FROM attachments WHERE draft_id = $1 RETURNING *`,
|
|
16116
|
+
[draftId]
|
|
16117
|
+
);
|
|
16118
|
+
return result.rows;
|
|
16119
|
+
}
|
|
16120
|
+
async function listOrphanDraftAttachments(olderThanMs) {
|
|
16121
|
+
const db = await getDb();
|
|
16122
|
+
const cutoff = new Date(Date.now() - olderThanMs).toISOString();
|
|
16123
|
+
const result = await db.query(
|
|
16124
|
+
`SELECT a.* FROM attachments a
|
|
16125
|
+
LEFT JOIN feedback_drafts d ON d.id = a.draft_id
|
|
16126
|
+
WHERE a.draft_id IS NOT NULL
|
|
16127
|
+
AND d.id IS NULL
|
|
16128
|
+
AND a.created_at < $1
|
|
16129
|
+
ORDER BY a.created_at ASC`,
|
|
16130
|
+
[cutoff]
|
|
16131
|
+
);
|
|
16132
|
+
return result.rows;
|
|
16133
|
+
}
|
|
16057
16134
|
var init_attachments = __esm({
|
|
16058
16135
|
"src/db/attachments.ts"() {
|
|
16059
16136
|
"use strict";
|
|
@@ -16176,6 +16253,31 @@ function parseNotes(raw) {
|
|
|
16176
16253
|
}
|
|
16177
16254
|
return [{ id: generateNoteId(), text: raw, created_at: (/* @__PURE__ */ new Date()).toISOString() }];
|
|
16178
16255
|
}
|
|
16256
|
+
function normalizeNotesAppend(raw) {
|
|
16257
|
+
if (raw === "") return [];
|
|
16258
|
+
const trimmed = raw.trimStart();
|
|
16259
|
+
if (!trimmed.startsWith("[")) return [raw];
|
|
16260
|
+
let parsed;
|
|
16261
|
+
try {
|
|
16262
|
+
parsed = JSON.parse(raw);
|
|
16263
|
+
} catch {
|
|
16264
|
+
return [raw];
|
|
16265
|
+
}
|
|
16266
|
+
if (!Array.isArray(parsed)) return [raw];
|
|
16267
|
+
if (parsed.length === 0) return [raw];
|
|
16268
|
+
const ALLOWED_KEYS = /* @__PURE__ */ new Set(["text", "id", "created_at"]);
|
|
16269
|
+
const texts = [];
|
|
16270
|
+
for (const entry of parsed) {
|
|
16271
|
+
if (entry === null || typeof entry !== "object") return [raw];
|
|
16272
|
+
const obj = entry;
|
|
16273
|
+
if (typeof obj.text !== "string") return [raw];
|
|
16274
|
+
for (const key of Object.keys(obj)) {
|
|
16275
|
+
if (!ALLOWED_KEYS.has(key)) return [raw];
|
|
16276
|
+
}
|
|
16277
|
+
texts.push(obj.text);
|
|
16278
|
+
}
|
|
16279
|
+
return texts;
|
|
16280
|
+
}
|
|
16179
16281
|
async function editNote(ticketId, noteId2, text) {
|
|
16180
16282
|
const db = await getDb();
|
|
16181
16283
|
const result = await db.query(`SELECT notes FROM tickets WHERE id = $1`, [ticketId]);
|
|
@@ -16485,12 +16587,18 @@ async function updateTicket(id, updates, options) {
|
|
|
16485
16587
|
paramIdx++;
|
|
16486
16588
|
}
|
|
16487
16589
|
if (updates.notes !== void 0 && updates.notes !== "") {
|
|
16488
|
-
const
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
16492
|
-
|
|
16493
|
-
|
|
16590
|
+
const bodies = normalizeNotesAppend(updates.notes);
|
|
16591
|
+
if (bodies.length > 0) {
|
|
16592
|
+
const current = await db.query(`SELECT notes FROM tickets WHERE id = $1`, [id]);
|
|
16593
|
+
const existing = parseNotes(current.rows[0]?.notes || "");
|
|
16594
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
16595
|
+
for (const body of bodies) {
|
|
16596
|
+
existing.push({ id: generateNoteId(), text: body, created_at: now });
|
|
16597
|
+
}
|
|
16598
|
+
sets.push(`notes = $${paramIdx}`);
|
|
16599
|
+
values.push(JSON.stringify(existing));
|
|
16600
|
+
paramIdx++;
|
|
16601
|
+
}
|
|
16494
16602
|
}
|
|
16495
16603
|
if (updates.status === "completed") {
|
|
16496
16604
|
sets.push("completed_at = NOW()");
|
|
@@ -16871,6 +16979,7 @@ var init_tickets = __esm({
|
|
|
16871
16979
|
var queries_exports = {};
|
|
16872
16980
|
__export(queries_exports, {
|
|
16873
16981
|
addAttachment: () => addAttachment,
|
|
16982
|
+
addDraftAttachment: () => addDraftAttachment,
|
|
16874
16983
|
addLogEntry: () => addLogEntry,
|
|
16875
16984
|
batchDeleteTickets: () => batchDeleteTickets,
|
|
16876
16985
|
batchRestoreTickets: () => batchRestoreTickets,
|
|
@@ -16879,6 +16988,7 @@ __export(queries_exports, {
|
|
|
16879
16988
|
countSearchMatchesInExcludedStatuses: () => countSearchMatchesInExcludedStatuses,
|
|
16880
16989
|
createTicket: () => createTicket,
|
|
16881
16990
|
deleteAttachment: () => deleteAttachment,
|
|
16991
|
+
deleteDraftAttachments: () => deleteDraftAttachments,
|
|
16882
16992
|
deleteNote: () => deleteNote,
|
|
16883
16993
|
deleteTicket: () => deleteTicket,
|
|
16884
16994
|
duplicateTickets: () => duplicateTickets,
|
|
@@ -16890,6 +17000,7 @@ __export(queries_exports, {
|
|
|
16890
17000
|
getAttachment: () => getAttachment,
|
|
16891
17001
|
getAttachments: () => getAttachments,
|
|
16892
17002
|
getCategories: () => getCategories,
|
|
17003
|
+
getDraftAttachments: () => getDraftAttachments,
|
|
16893
17004
|
getLogCount: () => getLogCount,
|
|
16894
17005
|
getLogEntries: () => getLogEntries,
|
|
16895
17006
|
getSettings: () => getSettings,
|
|
@@ -16901,8 +17012,11 @@ __export(queries_exports, {
|
|
|
16901
17012
|
hardDeleteTicket: () => hardDeleteTicket,
|
|
16902
17013
|
isExactTicketIdSearch: () => isExactTicketIdSearch,
|
|
16903
17014
|
listKnownTicketPrefixes: () => listKnownTicketPrefixes,
|
|
17015
|
+
listOrphanDraftAttachments: () => listOrphanDraftAttachments,
|
|
16904
17016
|
nextTicketNumber: () => nextTicketNumber,
|
|
17017
|
+
normalizeNotesAppend: () => normalizeNotesAppend,
|
|
16905
17018
|
parseNotes: () => parseNotes,
|
|
17019
|
+
promoteDraftAttachments: () => promoteDraftAttachments,
|
|
16906
17020
|
pruneLog: () => pruneLog,
|
|
16907
17021
|
queryTickets: () => queryTickets,
|
|
16908
17022
|
restoreTicket: () => restoreTicket,
|
|
@@ -24274,13 +24388,13 @@ import { pathToFileURL as pathToFileURL2 } from "url";
|
|
|
24274
24388
|
// src/cleanup.ts
|
|
24275
24389
|
init_queries();
|
|
24276
24390
|
import { rmSync as rmSync4 } from "fs";
|
|
24391
|
+
var ORPHAN_DRAFT_ATTACHMENT_HORIZON_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
24277
24392
|
async function cleanupAttachments() {
|
|
24278
24393
|
try {
|
|
24279
24394
|
const settings = await getSettings();
|
|
24280
24395
|
const verifiedDays = parseInt(settings.verified_cleanup_days, 10) || 30;
|
|
24281
24396
|
const trashDays = parseInt(settings.trash_cleanup_days, 10) || 3;
|
|
24282
24397
|
const tickets = await getTicketsForCleanup(verifiedDays, trashDays);
|
|
24283
|
-
if (tickets.length === 0) return;
|
|
24284
24398
|
let archived = 0;
|
|
24285
24399
|
let deleted = 0;
|
|
24286
24400
|
for (const ticket of tickets) {
|
|
@@ -24299,10 +24413,21 @@ async function cleanupAttachments() {
|
|
|
24299
24413
|
deleted++;
|
|
24300
24414
|
}
|
|
24301
24415
|
}
|
|
24302
|
-
|
|
24416
|
+
let orphans = 0;
|
|
24417
|
+
const orphanList = await listOrphanDraftAttachments(ORPHAN_DRAFT_ATTACHMENT_HORIZON_MS);
|
|
24418
|
+
for (const att of orphanList) {
|
|
24419
|
+
try {
|
|
24420
|
+
rmSync4(att.stored_path, { force: true });
|
|
24421
|
+
} catch {
|
|
24422
|
+
}
|
|
24423
|
+
await deleteAttachment(att.id);
|
|
24424
|
+
orphans++;
|
|
24425
|
+
}
|
|
24426
|
+
if (archived > 0 || deleted > 0 || orphans > 0) {
|
|
24303
24427
|
const parts = [];
|
|
24304
24428
|
if (archived > 0) parts.push(`archived ${archived} verified ticket(s)`);
|
|
24305
24429
|
if (deleted > 0) parts.push(`deleted ${deleted} trashed ticket(s)`);
|
|
24430
|
+
if (orphans > 0) parts.push(`GC'd ${orphans} orphan draft attachment(s)`);
|
|
24306
24431
|
console.log(` Cleanup: ${parts.join(", ")}.`);
|
|
24307
24432
|
}
|
|
24308
24433
|
} catch (err) {
|
|
@@ -24668,6 +24793,43 @@ attachmentRoutes.post("/tickets/:id/attachments", async (c) => {
|
|
|
24668
24793
|
notifyMutation(c.get("dataDir"));
|
|
24669
24794
|
return c.json(attachment, 201);
|
|
24670
24795
|
});
|
|
24796
|
+
attachmentRoutes.post("/tickets/:id/feedback-drafts/:draftId/attachments", async (c) => {
|
|
24797
|
+
const id = parseIntParam(c, "id");
|
|
24798
|
+
if (id === null) return c.json({ error: "Invalid attachment ticket ID" }, 400);
|
|
24799
|
+
const draftId = c.req.param("draftId");
|
|
24800
|
+
if (draftId === "") return c.json({ error: "Invalid draft ID" }, 400);
|
|
24801
|
+
const ticket = await getTicket(id);
|
|
24802
|
+
if (!ticket) return c.json({ error: "Ticket not found" }, 404);
|
|
24803
|
+
const dataDir = c.get("dataDir");
|
|
24804
|
+
const body = await c.req.parseBody();
|
|
24805
|
+
const file2 = body["file"];
|
|
24806
|
+
if (typeof file2 === "string") return c.json({ error: "No file uploaded" }, 400);
|
|
24807
|
+
const originalName = file2.name;
|
|
24808
|
+
const ext = extname(originalName);
|
|
24809
|
+
const baseName = basename3(originalName, ext);
|
|
24810
|
+
const storedName = `${ticket.ticket_number}_draft_${draftId}_${baseName}${ext}`;
|
|
24811
|
+
const attachDir = join22(dataDir, "attachments");
|
|
24812
|
+
mkdirSync10(attachDir, { recursive: true });
|
|
24813
|
+
const storedPath = join22(attachDir, storedName);
|
|
24814
|
+
const buffer = Buffer.from(await file2.arrayBuffer());
|
|
24815
|
+
const { writeFileSync: writeFileSync17 } = await import("fs");
|
|
24816
|
+
writeFileSync17(storedPath, buffer);
|
|
24817
|
+
const attachment = await addDraftAttachment(id, draftId, originalName, storedPath);
|
|
24818
|
+
notifyMutation(dataDir);
|
|
24819
|
+
return c.json(attachment, 201);
|
|
24820
|
+
});
|
|
24821
|
+
attachmentRoutes.post("/tickets/:id/feedback-drafts/:draftId/promote-attachments", async (c) => {
|
|
24822
|
+
const id = parseIntParam(c, "id");
|
|
24823
|
+
if (id === null) return c.json({ error: "Invalid ticket ID" }, 400);
|
|
24824
|
+
const draftId = c.req.param("draftId");
|
|
24825
|
+
if (draftId === "") return c.json({ error: "Invalid draft ID" }, 400);
|
|
24826
|
+
const ticket = await getTicket(id);
|
|
24827
|
+
if (!ticket) return c.json({ error: "Ticket not found" }, 404);
|
|
24828
|
+
const { promoteDraftAttachments: promoteDraftAttachments2 } = await Promise.resolve().then(() => (init_attachments(), attachments_exports));
|
|
24829
|
+
const promoted = await promoteDraftAttachments2(draftId);
|
|
24830
|
+
notifyMutation(c.get("dataDir"));
|
|
24831
|
+
return c.json({ promoted: promoted.length, attachments: promoted });
|
|
24832
|
+
});
|
|
24671
24833
|
attachmentRoutes.delete("/attachments/:id", async (c) => {
|
|
24672
24834
|
const id = parseIntParam(c, "id");
|
|
24673
24835
|
if (id === null) return c.json({ error: "Invalid attachment ID" }, 400);
|
|
@@ -25238,7 +25400,7 @@ function coerceFreezeEntry(raw) {
|
|
|
25238
25400
|
if (raw === null || typeof raw !== "object") return null;
|
|
25239
25401
|
const r = raw;
|
|
25240
25402
|
const source = r.source;
|
|
25241
|
-
if (source !== "client-observer" && source !== "client-heartbeat") return null;
|
|
25403
|
+
if (source !== "client-observer" && source !== "client-heartbeat" && source !== "client-server-busy-banner") return null;
|
|
25242
25404
|
const durationMs = r.durationMs;
|
|
25243
25405
|
if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0) return null;
|
|
25244
25406
|
const context = typeof r.context === "string" ? r.context : "";
|
|
@@ -25562,7 +25724,12 @@ ticketRoutes.get("/tickets/:id/feedback-drafts", async (c) => {
|
|
|
25562
25724
|
if (id === null) return c.json({ error: "Invalid ticket ID" }, 400);
|
|
25563
25725
|
const { listFeedbackDrafts: listFeedbackDrafts2 } = await Promise.resolve().then(() => (init_feedbackDrafts(), feedbackDrafts_exports));
|
|
25564
25726
|
const drafts = await listFeedbackDrafts2(id);
|
|
25565
|
-
|
|
25727
|
+
const { getDraftAttachments: getDraftAttachments2 } = await Promise.resolve().then(() => (init_attachments(), attachments_exports));
|
|
25728
|
+
const hydrated = await Promise.all(drafts.map(async (d) => ({
|
|
25729
|
+
...d,
|
|
25730
|
+
attachments: await getDraftAttachments2(d.id)
|
|
25731
|
+
})));
|
|
25732
|
+
return c.json(hydrated);
|
|
25566
25733
|
});
|
|
25567
25734
|
ticketRoutes.post("/tickets/:id/feedback-drafts", async (c) => {
|
|
25568
25735
|
const id = parseIntParam(c);
|
|
@@ -25599,10 +25766,18 @@ ticketRoutes.delete("/tickets/:id/feedback-drafts/:draftId", async (c) => {
|
|
|
25599
25766
|
if (id === null) return c.json({ error: "Invalid ticket ID" }, 400);
|
|
25600
25767
|
const draftId = c.req.param("draftId");
|
|
25601
25768
|
const { deleteFeedbackDraft: deleteFeedbackDraft2 } = await Promise.resolve().then(() => (init_feedbackDrafts(), feedbackDrafts_exports));
|
|
25769
|
+
const { deleteDraftAttachments: deleteDraftAttachments2 } = await Promise.resolve().then(() => (init_attachments(), attachments_exports));
|
|
25770
|
+
const droppedAttachments = await deleteDraftAttachments2(draftId);
|
|
25771
|
+
for (const att of droppedAttachments) {
|
|
25772
|
+
try {
|
|
25773
|
+
rmSync10(att.stored_path, { force: true });
|
|
25774
|
+
} catch {
|
|
25775
|
+
}
|
|
25776
|
+
}
|
|
25602
25777
|
const deleted = await deleteFeedbackDraft2(draftId);
|
|
25603
|
-
if (!deleted) return c.json({ error: "Not found" }, 404);
|
|
25778
|
+
if (!deleted && droppedAttachments.length === 0) return c.json({ error: "Not found" }, 404);
|
|
25604
25779
|
notifyMutation(c.get("dataDir"));
|
|
25605
|
-
return c.json({ ok: true });
|
|
25780
|
+
return c.json({ ok: true, droppedAttachments: droppedAttachments.length });
|
|
25606
25781
|
});
|
|
25607
25782
|
ticketRoutes.delete("/tickets/:id/hard", async (c) => {
|
|
25608
25783
|
const id = parseIntParam(c);
|