Add admin image replies and attachment removal

This commit is contained in:
rajchales-monito
2026-07-23 23:01:53 +02:00
parent 7b6d855fec
commit bb2c267252
4 changed files with 228 additions and 21 deletions
+24 -3
View File
@@ -77,6 +77,7 @@ async function adminApi(req, res, url) {
if (url.pathname === "/api/admin/events" && req.method === "GET") return eventStream(req, res, url, "admin");
if (url.pathname === "/api/admin/site" && req.method === "PATCH") return updateSite(req, res);
if (url.pathname === "/api/admin/conversations" && req.method === "GET") return adminConversations(res);
if (url.pathname.match(/^\/api\/admin\/attachments\/\d+$/) && req.method === "DELETE") return deleteAttachment(res, url);
if (url.pathname.match(/^\/api\/admin\/conversations\/[^/]+$/) && req.method === "GET") return adminConversation(res, url);
if (url.pathname.match(/^\/api\/admin\/conversations\/[^/]+$/) && req.method === "DELETE") return deleteConversation(res, url);
if (url.pathname.match(/^\/api\/admin\/conversations\/[^/]+\/messages$/) && req.method === "POST") return createAdminMessage(req, res, url);
@@ -398,17 +399,37 @@ async function createVisitorMessage(req, res, url) {
}
async function createAdminMessage(req, res, url) {
const body = await readJson(req, 2_000_000);
if (!body.message || !String(body.message).trim()) return json(res, 400, { error: "message_required" });
const body = await readJson(req, 8_000_000);
if ((!body.message || !String(body.message).trim()) && !(body.attachments || []).length) return json(res, 400, { error: "message_or_image_required" });
if (!attachmentsAreAllowed(body.attachments || [])) return json(res, 400, { error: "only_images_allowed" });
const conversation = findConversation(publicIdFrom(url));
if (!conversation) return json(res, 404, { error: "conversation_not_found" });
insertMessage(conversation.id, "admin", req.adminUser.username, String(body.message).trim());
const messageId = insertMessage(conversation.id, "admin", req.adminUser.username, String(body.message || "").trim());
saveAttachments(conversation.id, messageId, body.attachments || []);
bumpConversation(conversation.id, conversation.status === "new" ? "open" : conversation.status);
const updated = findConversation(conversation.public_id);
publishConversation(updated, { type: "message:new", payload: conversationDetails(updated) });
return json(res, 201, conversationDetails(updated));
}
function deleteAttachment(res, url) {
const attachmentId = Number(url.pathname.split("/").pop());
const attachment = db.prepare(`
SELECT a.*, c.public_id
FROM attachments a
JOIN conversations c ON c.id = a.conversation_id
WHERE a.id = ?
`).get(attachmentId);
if (!attachment) return json(res, 404, { error: "attachment_not_found" });
db.prepare("DELETE FROM attachments WHERE id = ?").run(attachmentId);
if (attachment.storage_path && path.resolve(attachment.storage_path).startsWith(path.resolve(UPLOAD_DIR))) {
fs.rmSync(attachment.storage_path, { force: true });
}
const conversation = findConversation(attachment.public_id);
if (conversation) publishConversation(conversation, { type: "message:new", payload: conversationDetails(conversation) });
return json(res, 200, { ok: true });
}
async function visitorTyping(req, res, url) {
const conversation = findConversation(publicIdFrom(url));
if (!conversation) return json(res, 404, { error: "conversation_not_found" });