111 lines
3.5 KiB
TypeScript
111 lines
3.5 KiB
TypeScript
// Setup type definitions for built-in Supabase Runtime APIs
|
|
import "@supabase/functions-js/edge-runtime.d.ts";
|
|
import { createClient } from "npm:@supabase/supabase-js@2";
|
|
type WebhookPayload = {
|
|
type: "INSERT" | "UPDATE" | "DELETE";
|
|
table: string;
|
|
schema: string;
|
|
record: Record<string, unknown> | null;
|
|
old_record: Record<string, unknown> | null;
|
|
};
|
|
const SUPABASE_URL = Deno.env.get("SUPABASE_URL") ?? "";
|
|
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ??
|
|
"";
|
|
const OPENAI_API_KEY = Deno.env.get("OPENAI_API_KEY") ?? "";
|
|
const OPENAI_BASE_URL = Deno.env.get("OPENAI_BASE_URL") ??
|
|
"https://api.openai.com/v1";
|
|
const ALLOWED_SCHEMA = "public";
|
|
const ALLOWED_TABLES = new Set(["planes_estudio", "asignaturas"]);
|
|
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, {
|
|
auth: { persistSession: false },
|
|
});
|
|
function jsonResponse(status: number, body: Record<string, unknown>) {
|
|
return new Response(JSON.stringify(body), {
|
|
status,
|
|
headers: { "Content-Type": "application/json" },
|
|
});
|
|
}
|
|
async function createConversationId(metadata: Record<string, string>) {
|
|
const response = await fetch(`${OPENAI_BASE_URL}/conversations`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${OPENAI_API_KEY}`,
|
|
},
|
|
body: JSON.stringify({ metadata }),
|
|
});
|
|
if (!response.ok) {
|
|
const errorText = await response.text();
|
|
throw new Error(`OpenAI error: ${response.status} ${errorText}`);
|
|
}
|
|
const data = await response.json();
|
|
const conversationId = data?.id as string | undefined;
|
|
if (!conversationId) {
|
|
throw new Error("OpenAI response missing conversation id");
|
|
}
|
|
return conversationId;
|
|
}
|
|
Deno.serve(async (req) => {
|
|
if (req.method !== "POST") {
|
|
return jsonResponse(405, { error: "Method not allowed" });
|
|
}
|
|
if (!SUPABASE_URL || !SUPABASE_SERVICE_ROLE_KEY) {
|
|
return jsonResponse(500, { error: "Supabase env vars missing" });
|
|
}
|
|
if (!OPENAI_API_KEY) {
|
|
return jsonResponse(500, { error: "OPENAI_API_KEY missing" });
|
|
}
|
|
let payload: WebhookPayload;
|
|
try {
|
|
payload = await req.json();
|
|
} catch {
|
|
return jsonResponse(400, { error: "Invalid JSON body" });
|
|
}
|
|
const { type, table, schema, record } = payload;
|
|
if (schema !== ALLOWED_SCHEMA || !ALLOWED_TABLES.has(table)) {
|
|
return jsonResponse(400, { error: "Table not allowed" });
|
|
}
|
|
if (type !== "INSERT") {
|
|
return jsonResponse(200, { status: "ignored", reason: "event_type" });
|
|
}
|
|
if (!record || typeof record !== "object") {
|
|
return jsonResponse(400, { error: "Missing record" });
|
|
}
|
|
const recordId = record["id"] as string | number | undefined;
|
|
if (!recordId) {
|
|
return jsonResponse(400, { error: "Record id missing" });
|
|
}
|
|
const existingConversationId = record["conversation_id"] as
|
|
| string
|
|
| null
|
|
| undefined;
|
|
if (existingConversationId) {
|
|
return jsonResponse(200, {
|
|
status: "skipped",
|
|
conversation_id: existingConversationId,
|
|
});
|
|
}
|
|
let conversationId: string;
|
|
try {
|
|
conversationId = await createConversationId({
|
|
table,
|
|
record_id: String(recordId),
|
|
});
|
|
} catch (error) {
|
|
return jsonResponse(502, { error: (error as Error).message });
|
|
}
|
|
const { error } = await supabase
|
|
.from(table)
|
|
.update({ conversation_id: conversationId })
|
|
.eq("id", recordId);
|
|
if (error) {
|
|
return jsonResponse(500, { error: error.message });
|
|
}
|
|
return jsonResponse(200, {
|
|
status: "updated",
|
|
table,
|
|
record_id: recordId,
|
|
conversation_id: conversationId,
|
|
});
|
|
});
|