916ad39176
Deploy to route.crispygoat.com / deploy (push) Failing after 3m1s
- Add MinIO/S3-compatible storage client (src/lib/storage.ts) with uploadObject, deleteObject, presigned URL helpers, and BUCKETS constant - Wire product images, brand logos, and water log photos to MinIO via the new storage client - Migrate forgot-password to Neon Auth (remove Supabase /auth/v1/recover call) - Migrate send-scheduled cron to direct Postgres + Resend (remove Supabase Edge Function proxy) - Add logoUrl to email types (OrderReceipt, Welcome, PasswordReset) and pass brand_settings.logo_url from all call sites - Update email templates to use dynamic logoUrl instead of hardcoded Supabase bucket URLs - Remove hardcoded Supabase URLs from TuxedoVideoHero, TuxedoAboutPage, TimeTrackingFieldClient; use brand_settings props + local public/ fallback - Download brand logos (3) and tuxedo-hero.mp4 (36MB) from Supabase bucket to public/ for local development - Add MinIO env vars to .env.example (endpoint, access key, secret, buckets) - Fix TimeTrackingFieldClient to destructure logoUrl and brandAccent props - Fix admin/users.ts logoUrl type (null → undefined for optional string) - Remove stale sb- cookie from wholesale-auth - Migrate tuxedo/about page to remove supabase import and use pool query for wholesale_settings lookup
77 lines
2.2 KiB
TypeScript
77 lines
2.2 KiB
TypeScript
"use server";
|
|
|
|
import { supabase } from "@/lib/supabase";
|
|
|
|
export type AIAuthConfig = {
|
|
provider: string;
|
|
api_key: string;
|
|
organization_id: string;
|
|
base_url: string;
|
|
model: string;
|
|
max_tokens: number;
|
|
};
|
|
|
|
export async function getAIPreferences(brandId: string): Promise<{
|
|
api_key?: string;
|
|
organization_id?: string;
|
|
base_url?: string;
|
|
model?: string;
|
|
max_tokens?: number;
|
|
} | null> {
|
|
const { data, error } = await supabase
|
|
.from("brand_ai_settings")
|
|
.select("api_key, organization_id, base_url, model, max_tokens")
|
|
.eq("brand_id", brandId)
|
|
.single();
|
|
|
|
if (error || !data) return null;
|
|
return data;
|
|
}
|
|
|
|
export async function saveAIPreferences(
|
|
brandId: string,
|
|
config: AIAuthConfig
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
const result = await supabase
|
|
.from("brand_ai_settings")
|
|
.upsert({
|
|
brand_id: brandId,
|
|
provider: config.provider,
|
|
api_key: config.api_key || null,
|
|
organization_id: config.organization_id || null,
|
|
base_url: config.base_url || null,
|
|
model: config.model || "gpt-4o-mini",
|
|
max_tokens: config.max_tokens || 4000,
|
|
updated_at: new Date().toISOString(),
|
|
}) as { data: unknown; error: { message: string } | null };
|
|
|
|
if (result.error) return { success: false, error: result.error.message };
|
|
return { success: true };
|
|
}
|
|
|
|
export async function testAIConnection(config: AIAuthConfig): Promise<{ ok: boolean; message: string }> {
|
|
if (!config.api_key?.trim()) {
|
|
return { ok: false, message: "API key is required" };
|
|
}
|
|
|
|
try {
|
|
const baseUrl = config.base_url?.trim() || "https://api.openai.com/v1";
|
|
const url = `${baseUrl.replace(/\/$/, "")}/models`;
|
|
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
Authorization: `Bearer ${config.api_key}`,
|
|
},
|
|
});
|
|
|
|
if (response.ok) {
|
|
return { ok: true, message: "Connection successful! Your API key is valid." };
|
|
} else if (response.status === 401) {
|
|
return { ok: false, message: "Invalid API key. Please check and try again." };
|
|
} else {
|
|
return { ok: false, message: `Error: ${response.status} ${response.statusText}` };
|
|
}
|
|
} catch {
|
|
return { ok: false, message: "Could not connect. Check your network and API key." };
|
|
}
|
|
} |