Improve admin chat controls

This commit is contained in:
rajchales-monito
2026-07-23 22:35:36 +02:00
parent f2b388779d
commit 8c71dbf1bf
4 changed files with 211 additions and 36 deletions
+25 -2
View File
@@ -77,6 +77,7 @@ async function adminApi(req, res, url) {
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\/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);
if (url.pathname.match(/^\/api\/admin\/conversations\/[^/]+\/status$/) && req.method === "PATCH") return updateConversationStatus(req, res, url);
if (url.pathname.match(/^\/api\/admin\/conversations\/[^/]+\/typing$/) && req.method === "POST") return adminTyping(req, res, url);
@@ -256,7 +257,9 @@ function adminConversations(res) {
const rows = db.prepare(`
SELECT c.*, s.site_key,
(SELECT body FROM messages WHERE conversation_id = c.id ORDER BY id DESC LIMIT 1) AS last_body,
(SELECT COUNT(*) FROM messages WHERE conversation_id = c.id) AS message_count
(SELECT COUNT(*) FROM messages WHERE conversation_id = c.id) AS message_count,
(SELECT COUNT(*) FROM attachments WHERE conversation_id = c.id AND mime_type LIKE 'image/%') AS image_count,
(SELECT public_url FROM attachments WHERE conversation_id = c.id AND mime_type LIKE 'image/%' ORDER BY id DESC LIMIT 1) AS first_image_url
FROM conversations c
JOIN sites s ON s.id = c.site_id
ORDER BY datetime(c.last_message_at) DESC, c.id DESC
@@ -297,6 +300,24 @@ async function updateConversationStatus(req, res, url) {
return json(res, 200, { conversation: formatConversationSummary(updated) });
}
function deleteConversation(res, url) {
const conversation = findConversation(publicIdFrom(url));
if (!conversation) return json(res, 404, { error: "conversation_not_found" });
const attachments = db.prepare("SELECT storage_path FROM attachments WHERE conversation_id = ?").all(conversation.id);
db.transaction(() => {
db.prepare("DELETE FROM attachments WHERE conversation_id = ?").run(conversation.id);
db.prepare("DELETE FROM messages WHERE conversation_id = ?").run(conversation.id);
db.prepare("DELETE FROM conversations WHERE id = ?").run(conversation.id);
})();
for (const attachment of attachments) {
if (attachment.storage_path && path.resolve(attachment.storage_path).startsWith(path.resolve(UPLOAD_DIR))) {
fs.rmSync(attachment.storage_path, { force: true });
}
}
publish("admin", { type: "conversation:delete", conversationId: conversation.public_id });
return json(res, 200, { ok: true });
}
function widgetConfig(res, url) {
const siteKey = url.searchParams.get("siteKey") || process.env.DEFAULT_SITE_KEY || "9b-plus";
const site = db.prepare("SELECT * FROM sites WHERE site_key = ?").get(siteKey);
@@ -501,6 +522,8 @@ function formatConversationSummary(row) {
vpnRisk: Boolean(row.vpn_risk),
lastBody: row.last_body,
messageCount: row.message_count,
imageCount: row.image_count || 0,
firstImageUrl: row.first_image_url,
lastMessageAt: row.last_message_at,
createdAt: row.created_at
};
@@ -582,7 +605,7 @@ function json(res, status, payload) {
function setCommonHeaders(res) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PATCH,OPTIONS");
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PATCH,DELETE,OPTIONS");
}
function endCors(res) {