modules/0-ui
This commit is contained in:
47
src/modules/0-ui/controllers/admin.js
Normal file
47
src/modules/0-ui/controllers/admin.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { handleDeleteConversation, handleDeleteUser, handleListUsers, handleRetryLast } from "../handlers/admin.js";
|
||||
|
||||
export const makeDeleteConversation = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const result = await handleDeleteConversation({ tenantId, chat_id: req.params.chat_id });
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error" });
|
||||
}
|
||||
};
|
||||
|
||||
export const makeListUsers = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const result = await handleListUsers({ tenantId, q: req.query.q || "", limit: req.query.limit || "200" });
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error" });
|
||||
}
|
||||
};
|
||||
|
||||
export const makeDeleteUser = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const deleteWoo = String(req.query.deleteWoo || "0") === "1" || String(req.query.deleteWoo || "").toLowerCase() === "true";
|
||||
const result = await handleDeleteUser({ tenantId, chat_id: req.params.chat_id, deleteWoo });
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error" });
|
||||
}
|
||||
};
|
||||
|
||||
export const makeRetryLast = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const result = await handleRetryLast({ tenantId, chat_id: req.params.chat_id });
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error" });
|
||||
}
|
||||
};
|
||||
|
||||
16
src/modules/0-ui/controllers/conversationState.js
Normal file
16
src/modules/0-ui/controllers/conversationState.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { handleGetConversationState } from "../handlers/conversationState.js";
|
||||
|
||||
export const makeGetConversationState = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const { status, payload } = await handleGetConversationState({
|
||||
tenantId,
|
||||
chat_id: req.query.chat_id || null,
|
||||
});
|
||||
res.status(status).json(payload);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error" });
|
||||
}
|
||||
};
|
||||
|
||||
13
src/modules/0-ui/controllers/conversations.js
Normal file
13
src/modules/0-ui/controllers/conversations.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { handleListConversations } from "../handlers/conversations.js";
|
||||
|
||||
export const makeGetConversations = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const items = await handleListConversations({ tenantId, query: req.query });
|
||||
res.json({ items });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error" });
|
||||
}
|
||||
};
|
||||
|
||||
17
src/modules/0-ui/controllers/messages.js
Normal file
17
src/modules/0-ui/controllers/messages.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { handleListMessages } from "../handlers/messages.js";
|
||||
|
||||
export const makeListMessages = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const items = await handleListMessages({
|
||||
tenantId,
|
||||
chat_id: req.query.chat_id || null,
|
||||
limit: req.query.limit || "200",
|
||||
});
|
||||
res.json({ items });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error" });
|
||||
}
|
||||
};
|
||||
|
||||
16
src/modules/0-ui/controllers/products.js
Normal file
16
src/modules/0-ui/controllers/products.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { handleSearchProducts } from "../handlers/products.js";
|
||||
|
||||
export const makeSearchProducts = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const q = req.query.q || "";
|
||||
const limit = req.query.limit || "10";
|
||||
const forceWoo = req.query.forceWoo || "0";
|
||||
const result = await handleSearchProducts({ tenantId, q, limit, forceWoo });
|
||||
res.json(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error" });
|
||||
}
|
||||
};
|
||||
|
||||
29
src/modules/0-ui/controllers/runs.js
Normal file
29
src/modules/0-ui/controllers/runs.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { handleListRuns, handleGetRun } from "../handlers/runs.js";
|
||||
|
||||
export const makeListRuns = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const items = await handleListRuns({
|
||||
tenantId,
|
||||
chat_id: req.query.chat_id || null,
|
||||
limit: req.query.limit || "50",
|
||||
});
|
||||
res.json({ items });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error" });
|
||||
}
|
||||
};
|
||||
|
||||
export const makeGetRunById = (tenantIdOrFn) => async (req, res) => {
|
||||
try {
|
||||
const tenantId = typeof tenantIdOrFn === "function" ? tenantIdOrFn() : tenantIdOrFn;
|
||||
const run = await handleGetRun({ tenantId, run_id: req.params.run_id });
|
||||
if (!run) return res.status(404).json({ ok: false, error: "not_found" });
|
||||
res.json(run);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
res.status(500).json({ ok: false, error: "internal_error" });
|
||||
}
|
||||
};
|
||||
|
||||
90
src/modules/0-ui/handlers/admin.js
Normal file
90
src/modules/0-ui/handlers/admin.js
Normal file
@@ -0,0 +1,90 @@
|
||||
import crypto from "crypto";
|
||||
import {
|
||||
cleanupLastRunForRetry,
|
||||
deleteConversationData,
|
||||
deleteIdentityMapByChat,
|
||||
getIdentityMapByChat,
|
||||
getLastInboundMessage,
|
||||
listUsers,
|
||||
} from "../../2-identity/db/repo.js";
|
||||
import { deleteWooCustomer } from "../../2-identity/services/woo.js";
|
||||
import { processMessage } from "../../2-identity/services/pipeline.js";
|
||||
|
||||
export async function handleDeleteConversation({ tenantId, chat_id }) {
|
||||
if (!chat_id) return { ok: false, error: "chat_id_required" };
|
||||
const result = await deleteConversationData({ tenant_id: tenantId, wa_chat_id: String(chat_id) });
|
||||
return { ok: true, ...result };
|
||||
}
|
||||
|
||||
export async function handleListUsers({ tenantId, q = "", limit = "200" }) {
|
||||
const items = await listUsers({ tenant_id: tenantId, q: String(q || ""), limit: parseInt(limit, 10) || 200 });
|
||||
return { ok: true, items };
|
||||
}
|
||||
|
||||
export async function handleDeleteUser({ tenantId, chat_id, deleteWoo = false }) {
|
||||
const wa_chat_id = String(chat_id || "");
|
||||
if (!wa_chat_id) return { ok: false, error: "chat_id_required" };
|
||||
|
||||
const mapping = await getIdentityMapByChat({ tenant_id: tenantId, wa_chat_id, provider: "woo" });
|
||||
const external_customer_id = mapping?.external_customer_id || null;
|
||||
|
||||
// borrar conversaciones (mensajes + runs + state)
|
||||
const convo = await deleteConversationData({ tenant_id: tenantId, wa_chat_id });
|
||||
|
||||
// borrar mapping
|
||||
const deletedMap = await deleteIdentityMapByChat({ tenant_id: tenantId, wa_chat_id, provider: "woo" });
|
||||
|
||||
// borrar customer en woo (best-effort)
|
||||
let woo = null;
|
||||
if (deleteWoo && external_customer_id) {
|
||||
try {
|
||||
woo = await deleteWooCustomer({ tenantId, id: external_customer_id, force: true });
|
||||
} catch (e) {
|
||||
woo = { ok: false, error: String(e?.message || e), status: e?.status || e?.cause?.status || null };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
chat_id: wa_chat_id,
|
||||
external_customer_id,
|
||||
deleted: {
|
||||
conversations: convo?.deleted || null,
|
||||
identity_map: deletedMap,
|
||||
woo,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function handleRetryLast({ tenantId, chat_id }) {
|
||||
const wa_chat_id = String(chat_id || "");
|
||||
if (!wa_chat_id) return { ok: false, error: "chat_id_required" };
|
||||
|
||||
const lastIn = await getLastInboundMessage({ tenant_id: tenantId, wa_chat_id });
|
||||
if (!lastIn) return { ok: false, error: "no_inbound_message" };
|
||||
|
||||
// 1) borrar la última respuesta/run
|
||||
const cleanup = await cleanupLastRunForRetry({ tenant_id: tenantId, wa_chat_id });
|
||||
|
||||
// 2) reinyectar el último mensaje del usuario como un nuevo message_id
|
||||
const raw = lastIn.payload?.raw || {};
|
||||
const text = String(lastIn.text || raw.text || "").trim();
|
||||
if (!text) return { ok: false, error: "last_inbound_text_empty", cleanup };
|
||||
|
||||
const from = String(raw.from || wa_chat_id.replace(/@.+$/, ""));
|
||||
const pushName = raw?.meta?.pushName || raw?.meta?.pushname || null;
|
||||
|
||||
const pm = await processMessage({
|
||||
tenantId,
|
||||
chat_id: wa_chat_id,
|
||||
from,
|
||||
text,
|
||||
provider: lastIn.provider || "evolution",
|
||||
message_id: crypto.randomUUID(),
|
||||
displayName: pushName,
|
||||
meta: { ...(raw.meta || {}), source: "retry_last" },
|
||||
});
|
||||
|
||||
return { ok: true, cleanup, run_id: pm?.run_id || null };
|
||||
}
|
||||
|
||||
21
src/modules/0-ui/handlers/conversationState.js
Normal file
21
src/modules/0-ui/handlers/conversationState.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { getConversationState } from "../../2-identity/db/repo.js";
|
||||
|
||||
export async function handleGetConversationState({ tenantId, chat_id }) {
|
||||
if (!chat_id) {
|
||||
return { status: 400, payload: { ok: false, error: "chat_id required" } };
|
||||
}
|
||||
const row = await getConversationState(tenantId, chat_id);
|
||||
if (!row) return { status: 404, payload: { ok: false, error: "not_found" } };
|
||||
return {
|
||||
status: 200,
|
||||
payload: {
|
||||
ok: true,
|
||||
state: row.state,
|
||||
last_intent: row.last_intent,
|
||||
last_order_id: row.last_order_id,
|
||||
context: row.context,
|
||||
state_updated_at: row.state_updated_at,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
13
src/modules/0-ui/handlers/conversations.js
Normal file
13
src/modules/0-ui/handlers/conversations.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { listConversations } from "../../2-identity/db/repo.js";
|
||||
|
||||
export async function handleListConversations({ tenantId, query }) {
|
||||
const { q = "", status = "", state = "", limit = "50" } = query || {};
|
||||
return listConversations({
|
||||
tenant_id: tenantId,
|
||||
q: String(q || ""),
|
||||
status: String(status || ""),
|
||||
state: String(state || ""),
|
||||
limit: parseInt(limit, 10) || 50,
|
||||
});
|
||||
}
|
||||
|
||||
11
src/modules/0-ui/handlers/messages.js
Normal file
11
src/modules/0-ui/handlers/messages.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { listMessages } from "../../2-identity/db/repo.js";
|
||||
|
||||
export async function handleListMessages({ tenantId, chat_id, limit = "200" }) {
|
||||
if (!chat_id) return [];
|
||||
return listMessages({
|
||||
tenant_id: tenantId,
|
||||
wa_chat_id: String(chat_id),
|
||||
limit: parseInt(limit, 10) || 200,
|
||||
});
|
||||
}
|
||||
|
||||
11
src/modules/0-ui/handlers/products.js
Normal file
11
src/modules/0-ui/handlers/products.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { searchSnapshotItems } from "../../shared/wooSnapshot.js";
|
||||
|
||||
export async function handleSearchProducts({ tenantId, q = "", limit = "10", forceWoo = "0" }) {
|
||||
const { items, source } = await searchSnapshotItems({
|
||||
tenantId,
|
||||
q,
|
||||
limit: parseInt(limit, 10) || 10,
|
||||
});
|
||||
return { items, source };
|
||||
}
|
||||
|
||||
14
src/modules/0-ui/handlers/runs.js
Normal file
14
src/modules/0-ui/handlers/runs.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { listRuns, getRunById } from "../../2-identity/db/repo.js";
|
||||
|
||||
export async function handleListRuns({ tenantId, chat_id = null, limit = "50" }) {
|
||||
return listRuns({
|
||||
tenant_id: tenantId,
|
||||
wa_chat_id: chat_id ? String(chat_id) : null,
|
||||
limit: parseInt(limit, 10) || 50,
|
||||
});
|
||||
}
|
||||
|
||||
export async function handleGetRun({ tenantId, run_id }) {
|
||||
return getRunById({ tenant_id: tenantId, run_id });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user