supersam/supabase/functions/send-chatbot-message/index.ts

153 lines
3.9 KiB
TypeScript

import {
channelFromProvider,
createServiceClient,
json,
type ProviderName,
} from "../_shared/chatbot.ts";
import { getOrderUpdateForOutboundDispatch, type OutboundWorkflowAction } from "../_shared/workflow.ts";
import {
getCorsHeaders,
readJsonBody,
requireRateLimit,
verifyInternalRequest,
} from "../_shared/security.ts";
const providerTokens: Record<ProviderName, string | undefined> = {
telegram: Deno.env.get("TELEGRAM_BOT_TOKEN"),
vk: Deno.env.get("VK_BOT_TOKEN"),
messenger_max: Deno.env.get("MESSENGER_MAX_TOKEN"),
};
const MAX_BODY_BYTES = 16 * 1024;
const sendToProvider = async ({
provider,
recipientId,
text,
buttons,
}: {
provider: ProviderName;
recipientId: string;
text: string;
buttons?: Array<{ title: string; action: string }>;
}) => {
const token = providerTokens[provider];
if (!token) {
throw new Error(`Missing token for ${provider}`);
}
return {
provider,
recipientId,
text,
buttons: buttons || [],
accepted: true,
};
};
Deno.serve(async (request) => {
if (request.method === "OPTIONS") {
const corsHeaders = getCorsHeaders(request, "integration");
return corsHeaders ? new Response("ok", { headers: corsHeaders }) : json({ error: "Origin not allowed" }, 403);
}
if (request.method !== "POST") {
return json({ error: "Method not allowed" }, 405);
}
const corsHeaders = getCorsHeaders(request, "integration") || {};
try {
const { body, rawBody } = await readJsonBody<{
provider: ProviderName;
orderId: string;
recipientId: string;
text: string;
buttons?: Array<{ title: string; action: string }>;
workflowAction?: OutboundWorkflowAction;
}>(request, {
maxBytes: MAX_BODY_BYTES,
});
await verifyInternalRequest(request, rawBody, { rawBody });
const supabase = createServiceClient();
await requireRateLimit(supabase, {
scope: "chatbot-dispatch",
key: body.orderId,
maxCount: 10,
windowSeconds: 600,
blockSeconds: 1800,
});
const dispatchResult = await sendToProvider(body);
const { error } = await supabase.from("chat_messages").insert({
order_id: body.orderId,
sender_name: "dispatch-function",
sender_type: "bot",
channel: channelFromProvider(body.provider),
text: body.text,
payload: {
buttons: body.buttons || [],
dispatch_result: dispatchResult,
},
});
if (error) {
throw error;
}
const orderUpdate = getOrderUpdateForOutboundDispatch(body.workflowAction || "custom_message");
if (orderUpdate) {
const { data: currentOrder, error: orderError } = await supabase
.from("orders")
.select("id, status, delivery_agreement_status")
.eq("id", body.orderId)
.single();
if (orderError) {
throw orderError;
}
const { error: updateError } = await supabase
.from("orders")
.update({
status: orderUpdate.status,
delivery_agreement_status: orderUpdate.deliveryAgreementStatus,
})
.eq("id", body.orderId);
if (updateError) {
throw updateError;
}
const { error: historyError } = await supabase.from("order_history").insert({
order_id: body.orderId,
action: `Dispatch ${body.provider}: ${body.workflowAction || "custom_message"}`,
old_status: currentOrder.status,
new_status: orderUpdate.status,
metadata: {
old_delivery_agreement_status: currentOrder.delivery_agreement_status,
new_delivery_agreement_status: orderUpdate.deliveryAgreementStatus,
buttons: body.buttons || [],
},
});
if (historyError) {
throw historyError;
}
}
return json({ ok: true, dispatchResult });
} catch (error) {
return json(
{
ok: false,
error: error instanceof Error ? error.message : "Unexpected error",
},
500,
);
}
});