Supabase support: client, auth & SQL

This commit is contained in:
Will Chen
2025-04-22 16:45:47 -07:00
parent ec43482d6c
commit 4294ce5767
15 changed files with 694 additions and 9 deletions

View File

@@ -0,0 +1,66 @@
import { getSupabaseClient } from "./supabase_management_client";
import { SUPABASE_SCHEMA_QUERY } from "./supabase_schema_query";
async function getPublishableKey({ projectId }: { projectId: string }) {
const supabase = await getSupabaseClient();
const keys = await supabase.getProjectApiKeys(projectId);
if (!keys) {
throw new Error("No keys found for project");
}
const publishableKey = keys.find((key) => (key as any)["name"] === "anon");
if (!publishableKey) {
throw new Error("No publishable key found for project");
}
return publishableKey.api_key;
}
export const getSupabaseClientCode = async function ({
projectId,
}: {
projectId: string;
}) {
const publishableKey = await getPublishableKey({ projectId });
return `
// This file is automatically generated. Do not edit it directly.
import { createClient } from '@supabase/supabase-js';
const SUPABASE_URL = "https://${projectId}.supabase.co";
const SUPABASE_PUBLISHABLE_KEY = "${publishableKey}";
// Import the supabase client like this:
// import { supabase } from "@/integrations/supabase/client";
export const supabase = createClient(SUPABASE_URL, SUPABASE_PUBLISHABLE_KEY);`;
};
export async function getSupabaseContext({
supabaseProjectId,
}: {
supabaseProjectId: string;
}) {
const supabase = await getSupabaseClient();
const publishableKey = await getPublishableKey({
projectId: supabaseProjectId,
});
const schema = await supabase.runQuery(
supabaseProjectId,
SUPABASE_SCHEMA_QUERY
);
// TODO: include EDGE FUNCTIONS and SECRETS!
const context = `
# Supabase Context
## Supabase Project ID
${supabaseProjectId}
## Publishable key (aka anon key)
${publishableKey}
## Schema
${JSON.stringify(schema)}
`;
return context;
}

View File

@@ -0,0 +1,40 @@
import { readSettings } from "../main/settings";
import { SupabaseManagementAPI } from "supabase-management-js";
// Function to get the Supabase Management API client
export async function getSupabaseClient(): Promise<SupabaseManagementAPI> {
const settings = readSettings();
// Check if Supabase token exists in settings
const supabaseAccessToken = settings.supabase?.accessToken?.value;
if (!supabaseAccessToken) {
throw new Error(
"Supabase access token not found. Please authenticate first."
);
}
return new SupabaseManagementAPI({
accessToken: supabaseAccessToken,
});
}
export async function getSupabaseProjectName(
projectId: string
): Promise<string> {
const supabase = await getSupabaseClient();
const projects = await supabase.getProjects();
const project = projects?.find((p) => p.id === projectId);
return project?.name || `<project not found for: ${projectId}>`;
}
export async function executeSupabaseSql({
supabaseProjectId,
query,
}: {
supabaseProjectId: string;
query: string;
}): Promise<string> {
const supabase = await getSupabaseClient();
const result = await supabase.runQuery(supabaseProjectId, query);
return JSON.stringify(result);
}

View File

@@ -0,0 +1,23 @@
import { writeSettings } from "../main/settings";
export function handleSupabaseOAuthReturn({
token,
refreshToken,
expiresIn,
}: {
token: string;
refreshToken: string;
expiresIn: number;
}) {
writeSettings({
supabase: {
accessToken: {
value: token,
},
refreshToken: {
value: refreshToken,
},
expiresIn,
},
});
}

View File

@@ -0,0 +1,107 @@
// Schema query based on https://github.com/jjleng/code-panda/blob/61f1fa514c647de1a8d2ad7f85102d49c6db2086/cp-agent/cp_agent/utils/supabase_utils.py#L521
// which is Apache 2.0 licensed and copyrighted to Jijun Leng
// https://github.com/jjleng/code-panda/blob/61f1fa514c647de1a8d2ad7f85102d49c6db2086/LICENSE
export const SUPABASE_SCHEMA_QUERY = `
WITH table_info AS (
SELECT
tables.table_name,
pd.description as table_description
FROM information_schema.tables tables
LEFT JOIN pg_stat_user_tables psut ON tables.table_name = psut.relname
LEFT JOIN pg_description pd ON psut.relid = pd.objoid AND pd.objsubid = 0
WHERE tables.table_schema = 'public'
),
column_info AS (
SELECT
c.table_name,
jsonb_agg(
jsonb_build_object(
'column_name', c.column_name,
'data_type', c.data_type,
'is_nullable', c.is_nullable,
'column_default', c.column_default
) ORDER BY c.ordinal_position
) as columns
FROM information_schema.columns c
WHERE c.table_schema = 'public'
GROUP BY c.table_name
),
tables_result AS (
SELECT
'tables' as result_type,
jsonb_build_object(
'name', ti.table_name::text,
'description', ti.table_description::text,
'columns', COALESCE(ci.columns, '[]'::jsonb)
)::text as data
FROM table_info ti
LEFT JOIN column_info ci ON ti.table_name = ci.table_name
),
policies_result AS (
SELECT
'policies' as result_type,
jsonb_build_object(
'name', pol.polname::text,
'table', cls.relname::text,
'command', CASE
WHEN pol.polcmd = 'r' THEN 'SELECT'
WHEN pol.polcmd = 'w' THEN 'UPDATE'
WHEN pol.polcmd = 'a' THEN 'INSERT'
WHEN pol.polcmd = 'd' THEN 'DELETE'
ELSE pol.polcmd::text
END,
'permissive', pol.polpermissive,
'definition', pg_get_expr(pol.polqual, pol.polrelid)::text
)::text as data
FROM pg_policy pol
JOIN pg_class cls ON pol.polrelid = cls.oid
WHERE cls.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
),
functions_result AS (
SELECT
'functions' as result_type,
jsonb_build_object(
'name', p.proname::text,
'description', d.description::text,
'arguments', pg_get_function_arguments(p.oid)::text,
'return_type', pg_get_function_result(p.oid)::text,
'language', l.lanname::text,
'volatility', CASE p.provolatile
WHEN 'i' THEN 'IMMUTABLE'
WHEN 's' THEN 'STABLE'
WHEN 'v' THEN 'VOLATILE'
END,
'source_code', pg_get_functiondef(p.oid)::text
)::text as data
FROM pg_proc p
LEFT JOIN pg_description d ON p.oid = d.objoid
LEFT JOIN pg_language l ON p.prolang = l.oid
WHERE p.pronamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
),
triggers_result AS (
SELECT
'triggers' as result_type,
jsonb_build_object(
'name', t.trigger_name::text,
'table', t.event_object_table::text,
'timing', t.action_timing::text,
'event', t.event_manipulation::text,
'action_statement', t.action_statement::text,
'function_name', p.proname::text
)::text as data
FROM information_schema.triggers t
LEFT JOIN pg_trigger pg_t ON t.trigger_name = pg_t.tgname
LEFT JOIN pg_proc p ON pg_t.tgfoid = p.oid
WHERE t.trigger_schema = 'public'
)
SELECT result_type, data
FROM (
SELECT * FROM tables_result
UNION ALL SELECT * FROM policies_result
UNION ALL SELECT * FROM functions_result
UNION ALL SELECT * FROM triggers_result
) combined_results
ORDER BY result_type;
`;