153 lines
3.9 KiB
TypeScript
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,
|
|
);
|
|
}
|
|
});
|