Files
route-commerce/src/actions/ai/preferences.ts
T
openclaw 916ad39176
Deploy to route.crispygoat.com / deploy (push) Failing after 3m1s
feat(storage): MinIO object storage, Neon Auth, Supabase removal
- 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
2026-06-09 12:23:37 -06:00

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." };
}
}