mirror of
https://github.com/renee-png/acmcc.git
synced 2026-06-21 09:50:01 +00:00
Add ACMCC app source, Supabase backend, and project config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.49.1";
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type, x-supabase-client-platform, x-supabase-client-platform-version, x-supabase-client-runtime, x-supabase-client-runtime-version",
|
||||
};
|
||||
|
||||
serve(async (req) => {
|
||||
if (req.method === "OPTIONS") {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!;
|
||||
const SUPABASE_SERVICE_ROLE_KEY = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
|
||||
const QBO_CLIENT_ID = Deno.env.get("QBO_CLIENT_ID");
|
||||
const QBO_CLIENT_SECRET = Deno.env.get("QBO_CLIENT_SECRET");
|
||||
|
||||
if (!QBO_CLIENT_ID || !QBO_CLIENT_SECRET) {
|
||||
return new Response(JSON.stringify({ error: "QBO credentials not configured" }), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
const supabaseAdmin = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY);
|
||||
const url = new URL(req.url);
|
||||
|
||||
try {
|
||||
if (req.method === "GET") {
|
||||
// Handle OAuth callback from QBO
|
||||
const code = url.searchParams.get("code");
|
||||
const realmId = url.searchParams.get("realmId");
|
||||
|
||||
if (!code) {
|
||||
return new Response("Missing authorization code", { status: 400 });
|
||||
}
|
||||
|
||||
// Determine the redirect URI (same as this function URL)
|
||||
const redirectUri = `${SUPABASE_URL}/functions/v1/qbo-auth`;
|
||||
|
||||
// Exchange code for tokens
|
||||
const tokenResp = await fetch("https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
"Authorization": `Basic ${btoa(`${QBO_CLIENT_ID}:${QBO_CLIENT_SECRET}`)}`,
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
grant_type: "authorization_code",
|
||||
code,
|
||||
redirect_uri: redirectUri,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!tokenResp.ok) {
|
||||
const errText = await tokenResp.text();
|
||||
console.error("Token exchange failed:", errText);
|
||||
return new Response(`Token exchange failed: ${errText}`, { status: 500 });
|
||||
}
|
||||
|
||||
const tokens = await tokenResp.json();
|
||||
const expiry = (Date.now() + tokens.expires_in * 1000).toString();
|
||||
|
||||
// Store tokens in company_settings
|
||||
const entries: Record<string, string> = {
|
||||
qbo_access_token: tokens.access_token,
|
||||
qbo_refresh_token: tokens.refresh_token,
|
||||
qbo_token_expiry: expiry,
|
||||
};
|
||||
|
||||
if (realmId) {
|
||||
entries.qbo_realm_id = realmId;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(entries)) {
|
||||
const { data: existing } = await supabaseAdmin.from("company_settings").select("id").eq("key", key).maybeSingle();
|
||||
if (existing) {
|
||||
await supabaseAdmin.from("company_settings").update({ value }).eq("key", key);
|
||||
} else {
|
||||
await supabaseAdmin.from("company_settings").insert({ key, value });
|
||||
}
|
||||
}
|
||||
|
||||
// Return a success HTML page
|
||||
return new Response(
|
||||
`<!DOCTYPE html><html><body style="font-family:sans-serif;text-align:center;padding:60px">
|
||||
<h1>✅ QuickBooks Connected!</h1>
|
||||
<p>Your QuickBooks Online account has been successfully linked.</p>
|
||||
<p>You can close this window and return to the app.</p>
|
||||
<script>setTimeout(()=>window.close(),3000)</script>
|
||||
</body></html>`,
|
||||
{ headers: { "Content-Type": "text/html" } },
|
||||
);
|
||||
}
|
||||
|
||||
if (req.method === "POST") {
|
||||
// Generate the QBO authorization URL
|
||||
const body = await req.json();
|
||||
const { action } = body;
|
||||
|
||||
if (action === "get_auth_url") {
|
||||
const redirectUri = `${SUPABASE_URL}/functions/v1/qbo-auth`;
|
||||
const authUrl = `https://appcenter.intuit.com/connect/oauth2?` +
|
||||
`client_id=${QBO_CLIENT_ID}` +
|
||||
`&redirect_uri=${encodeURIComponent(redirectUri)}` +
|
||||
`&scope=com.intuit.quickbooks.accounting` +
|
||||
`&response_type=code` +
|
||||
`&state=qbo_connect`;
|
||||
|
||||
return new Response(JSON.stringify({ authUrl, redirectUri }), {
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
if (action === "check_status") {
|
||||
const { data: tokenSetting } = await supabaseAdmin
|
||||
.from("company_settings")
|
||||
.select("value")
|
||||
.eq("key", "qbo_refresh_token")
|
||||
.maybeSingle();
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
connected: !!tokenSetting?.value,
|
||||
}), {
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ error: "Method not allowed" }), {
|
||||
status: 405,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("QBO auth error:", err);
|
||||
return new Response(JSON.stringify({ error: err instanceof Error ? err.message : "Unknown error" }), {
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user