mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:25:22 +02:00
Fix Supabase credentials error - use API routes
- Create /api/experiments endpoint for server-side Supabase access - Update experiments service to use fetch instead of direct Supabase client - Fixes 'Supabase credentials not configured' error in browser Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
co-authored by
Qwen-Coder
parent
2b62bef707
commit
edbde19f3a
@@ -0,0 +1,112 @@
|
|||||||
|
import type { VercelRequest, VercelResponse } from '@vercel/node';
|
||||||
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
import { Experiment, ExperimentCreateInput } from '../types';
|
||||||
|
|
||||||
|
const supabase = createClient(
|
||||||
|
process.env.SUPABASE_URL || '',
|
||||||
|
process.env.SUPABASE_SERVICE_KEY || ''
|
||||||
|
);
|
||||||
|
|
||||||
|
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||||
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
||||||
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
||||||
|
|
||||||
|
if (req.method === 'OPTIONS') return res.status(200).end();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// GET - List experiments or get single experiment
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
const { id, status, type, marketplace, asin, dateFrom, dateTo } = req.query;
|
||||||
|
|
||||||
|
let query = supabase.from('experiments').select('*');
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
const { data, error } = await query.eq('id', id).single();
|
||||||
|
if (error) throw error;
|
||||||
|
return res.status(200).json(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// List with filters
|
||||||
|
if (status) {
|
||||||
|
const statuses = Array.isArray(status) ? status : [status];
|
||||||
|
query = query.in('status', statuses);
|
||||||
|
}
|
||||||
|
if (type) {
|
||||||
|
const types = Array.isArray(type) ? type : [type];
|
||||||
|
query = query.in('type', types);
|
||||||
|
}
|
||||||
|
if (marketplace) {
|
||||||
|
const markets = Array.isArray(marketplace) ? marketplace : [marketplace];
|
||||||
|
query = query.in('marketplace', markets);
|
||||||
|
}
|
||||||
|
if (asin) {
|
||||||
|
query = query.contains('asins', [asin]);
|
||||||
|
}
|
||||||
|
if (dateFrom) {
|
||||||
|
query = query.gte('start_date', dateFrom as string);
|
||||||
|
}
|
||||||
|
if (dateTo) {
|
||||||
|
query = query.lte('end_date', dateTo as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, error } = await query.order('created_at', { ascending: false });
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
return res.status(200).json(data || []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST - Create experiment
|
||||||
|
if (req.method === 'POST') {
|
||||||
|
const input: ExperimentCreateInput = req.body;
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('experiments')
|
||||||
|
.insert([{
|
||||||
|
...input,
|
||||||
|
status: 'planned',
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
updated_at: new Date().toISOString(),
|
||||||
|
}])
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
return res.status(201).json(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT - Update experiment
|
||||||
|
if (req.method === 'PUT') {
|
||||||
|
const { id } = req.query;
|
||||||
|
const updates = req.body;
|
||||||
|
|
||||||
|
const { data, error } = await supabase
|
||||||
|
.from('experiments')
|
||||||
|
.update({ ...updates, updated_at: new Date().toISOString() })
|
||||||
|
.eq('id', id)
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
return res.status(200).json(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE - Delete experiment
|
||||||
|
if (req.method === 'DELETE') {
|
||||||
|
const { id } = req.query;
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('experiments')
|
||||||
|
.delete()
|
||||||
|
.eq('id', id);
|
||||||
|
|
||||||
|
if (error) throw error;
|
||||||
|
return res.status(200).json({ success: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(405).json({ error: 'Method not allowed' });
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Experiments API error:', error);
|
||||||
|
return res.status(500).json({ error: error.message || 'Internal server error' });
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
-73
@@ -1,4 +1,3 @@
|
|||||||
import { createClient } from '@supabase/supabase-js';
|
|
||||||
import {
|
import {
|
||||||
Experiment,
|
Experiment,
|
||||||
ExperimentCreateInput,
|
ExperimentCreateInput,
|
||||||
@@ -9,75 +8,60 @@ import {
|
|||||||
} from '../types';
|
} from '../types';
|
||||||
import { SalesRecord } from '../types';
|
import { SalesRecord } from '../types';
|
||||||
|
|
||||||
const supabaseUrl = process.env.SUPABASE_URL || '';
|
const API_BASE = '/api/experiments';
|
||||||
const supabaseServiceKey = process.env.SUPABASE_SERVICE_KEY || '';
|
|
||||||
|
|
||||||
const getSupabase = () => {
|
|
||||||
if (!supabaseUrl || !supabaseServiceKey) {
|
|
||||||
throw new Error('Supabase credentials not configured');
|
|
||||||
}
|
|
||||||
return createClient(supabaseUrl, supabaseServiceKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============ CRUD Operations ============
|
// ============ CRUD Operations ============
|
||||||
|
|
||||||
export const createExperiment = async (input: ExperimentCreateInput): Promise<Experiment> => {
|
export const createExperiment = async (input: ExperimentCreateInput): Promise<Experiment> => {
|
||||||
const supabase = getSupabase();
|
const response = await fetch(API_BASE, {
|
||||||
|
method: 'POST',
|
||||||
const { data, error } = await supabase
|
headers: { 'Content-Type': 'application/json' },
|
||||||
.from('experiments')
|
body: JSON.stringify(input),
|
||||||
.insert([{
|
});
|
||||||
...input,
|
|
||||||
status: 'planned',
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
}])
|
|
||||||
.select()
|
|
||||||
.single();
|
|
||||||
|
|
||||||
if (error) throw error;
|
if (!response.ok) {
|
||||||
return data as Experiment;
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to create experiment');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateExperiment = async (
|
export const updateExperiment = async (
|
||||||
id: string,
|
id: string,
|
||||||
updates: Partial<Experiment>
|
updates: Partial<Experiment>
|
||||||
): Promise<Experiment> => {
|
): Promise<Experiment> => {
|
||||||
const supabase = getSupabase();
|
const response = await fetch(`${API_BASE}?id=${id}`, {
|
||||||
|
method: 'PUT',
|
||||||
const { data, error } = await supabase
|
headers: { 'Content-Type': 'application/json' },
|
||||||
.from('experiments')
|
body: JSON.stringify(updates),
|
||||||
.update({ ...updates, updated_at: new Date().toISOString() })
|
});
|
||||||
.eq('id', id)
|
|
||||||
.select()
|
|
||||||
.single();
|
|
||||||
|
|
||||||
if (error) throw error;
|
if (!response.ok) {
|
||||||
return data as Experiment;
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to update experiment');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteExperiment = async (id: string): Promise<void> => {
|
export const deleteExperiment = async (id: string): Promise<void> => {
|
||||||
const supabase = getSupabase();
|
const response = await fetch(`${API_BASE}?id=${id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
const { error } = await supabase
|
});
|
||||||
.from('experiments')
|
|
||||||
.delete()
|
|
||||||
.eq('id', id);
|
|
||||||
|
|
||||||
if (error) throw error;
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to delete experiment');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getExperiment = async (id: string): Promise<Experiment | null> => {
|
export const getExperiment = async (id: string): Promise<Experiment | null> => {
|
||||||
const supabase = getSupabase();
|
const response = await fetch(`${API_BASE}?id=${id}`);
|
||||||
|
|
||||||
const { data, error } = await supabase
|
|
||||||
.from('experiments')
|
|
||||||
.select('*')
|
|
||||||
.eq('id', id)
|
|
||||||
.single();
|
|
||||||
|
|
||||||
if (error) return null;
|
if (!response.ok) return null;
|
||||||
return data as Experiment;
|
|
||||||
|
return response.json();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listExperiments = async (
|
export const listExperiments = async (
|
||||||
@@ -90,35 +74,38 @@ export const listExperiments = async (
|
|||||||
dateTo?: string;
|
dateTo?: string;
|
||||||
}
|
}
|
||||||
): Promise<ExperimentListItem[]> => {
|
): Promise<ExperimentListItem[]> => {
|
||||||
const supabase = getSupabase();
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
let query = supabase.from('experiments').select('*');
|
|
||||||
|
|
||||||
if (filters?.status?.length) {
|
if (filters?.status?.length) {
|
||||||
query = query.in('status', filters.status);
|
filters.status.forEach(s => params.append('status', s));
|
||||||
}
|
}
|
||||||
if (filters?.type?.length) {
|
if (filters?.type?.length) {
|
||||||
query = query.in('type', filters.type);
|
filters.type.forEach(t => params.append('type', t));
|
||||||
}
|
}
|
||||||
if (filters?.marketplace?.length) {
|
if (filters?.marketplace?.length) {
|
||||||
query = query.in('marketplace', filters.marketplace);
|
filters.marketplace.forEach(m => params.append('marketplace', m));
|
||||||
}
|
}
|
||||||
if (filters?.asin) {
|
if (filters?.asin) {
|
||||||
query = query.contains('asins', [filters.asin]);
|
params.append('asin', filters.asin);
|
||||||
}
|
}
|
||||||
if (filters?.dateFrom) {
|
if (filters?.dateFrom) {
|
||||||
query = query.gte('start_date', filters.dateFrom);
|
params.append('dateFrom', filters.dateFrom);
|
||||||
}
|
}
|
||||||
if (filters?.dateTo) {
|
if (filters?.dateTo) {
|
||||||
query = query.lte('end_date', filters.dateTo);
|
params.append('dateTo', filters.dateTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await query.order('created_at', { ascending: false });
|
const response = await fetch(`${API_BASE}?${params.toString()}`);
|
||||||
|
|
||||||
if (error) throw error;
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to list experiments');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
// Transform to ExperimentListItem
|
// Transform to ExperimentListItem
|
||||||
return (data || []).map((exp: any) => {
|
return data.map((exp: any) => {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const startDate = new Date(exp.start_date);
|
const startDate = new Date(exp.start_date);
|
||||||
const endDate = exp.end_date ? new Date(exp.end_date) : null;
|
const endDate = exp.end_date ? new Date(exp.end_date) : null;
|
||||||
@@ -149,24 +136,31 @@ export const listExperiments = async (
|
|||||||
export const getActiveExperimentsForASINs = async (
|
export const getActiveExperimentsForASINs = async (
|
||||||
asins: string[]
|
asins: string[]
|
||||||
): Promise<Map<string, ActiveExperiment[]>> => {
|
): Promise<Map<string, ActiveExperiment[]>> => {
|
||||||
const supabase = getSupabase();
|
const params = new URLSearchParams();
|
||||||
|
asins.forEach(asin => params.append('asin', asin));
|
||||||
|
|
||||||
|
const response = await fetch(`${API_BASE}?${params.toString()}&status=active`);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error || 'Failed to fetch active experiments');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
const { data, error } = await supabase
|
// Filter experiments that are currently active
|
||||||
.from('experiments')
|
const activeExperiments = data.filter((exp: any) =>
|
||||||
.select('*')
|
exp.status === 'active' &&
|
||||||
.eq('status', 'active')
|
exp.start_date <= today &&
|
||||||
.lte('start_date', today)
|
(!exp.end_date || exp.end_date >= today)
|
||||||
.or(`end_date.is.null,end_date.gte.${today}`);
|
);
|
||||||
|
|
||||||
if (error) throw error;
|
|
||||||
|
|
||||||
const map = new Map<string, ActiveExperiment[]>();
|
const map = new Map<string, ActiveExperiment[]>();
|
||||||
|
|
||||||
for (const asin of asins) {
|
for (const asin of asins) {
|
||||||
const experiments = (data || [])
|
const experiments = activeExperiments
|
||||||
.filter((exp: any) => Array.isArray(exp.asins) && exp.asins.includes(asin))
|
.filter((exp: any) => exp.asins?.includes(asin))
|
||||||
.map((exp: any) => {
|
.map((exp: any) => {
|
||||||
const endDate = exp.end_date ? new Date(exp.end_date) : null;
|
const endDate = exp.end_date ? new Date(exp.end_date) : null;
|
||||||
const daysRemaining = endDate
|
const daysRemaining = endDate
|
||||||
|
|||||||
Reference in New Issue
Block a user