Set up drop in replacement routes for data coming from acot connection that previously came from klaviyo
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, useCallback, memo } from "react";
|
||||
import axios from "axios";
|
||||
import { acotService } from "@/services/acotService";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -175,10 +176,8 @@ const MiniSalesChart = ({ className = "" }) => {
|
||||
|
||||
try {
|
||||
setProjectionLoading(true);
|
||||
const response = await axios.get("/api/klaviyo/events/projection", {
|
||||
params: { timeRange: "last30days" }
|
||||
});
|
||||
setProjection(response.data);
|
||||
const response = await acotService.getProjection({ timeRange: "last30days" });
|
||||
setProjection(response);
|
||||
} catch (error) {
|
||||
console.error("Error loading projection:", error);
|
||||
} finally {
|
||||
@@ -191,21 +190,19 @@ const MiniSalesChart = ({ className = "" }) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const response = await axios.get("/api/klaviyo/events/stats/details", {
|
||||
params: {
|
||||
timeRange: "last30days",
|
||||
metric: "revenue",
|
||||
daily: true,
|
||||
},
|
||||
const response = await acotService.getStatsDetails({
|
||||
timeRange: "last30days",
|
||||
metric: "revenue",
|
||||
daily: true,
|
||||
});
|
||||
|
||||
if (!response.data) {
|
||||
throw new Error("Invalid response format");
|
||||
}
|
||||
|
||||
const stats = Array.isArray(response.data)
|
||||
? response.data
|
||||
: response.data.stats || [];
|
||||
const stats = Array.isArray(response)
|
||||
? response
|
||||
: response.stats || [];
|
||||
|
||||
const processedData = processData(stats);
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, useCallback, memo } from "react";
|
||||
import axios from "axios";
|
||||
import { acotService } from "@/services/acotService";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -307,13 +308,11 @@ const MiniStatCards = ({
|
||||
|
||||
const params =
|
||||
timeRange === "custom" ? { startDate, endDate } : { timeRange };
|
||||
const response = await axios.get("/api/klaviyo/events/stats", {
|
||||
params,
|
||||
});
|
||||
const response = await acotService.getStats(params);
|
||||
|
||||
if (!isMounted) return;
|
||||
|
||||
setStats(response.data.stats);
|
||||
setStats(response.stats);
|
||||
setLastUpdate(DateTime.now().setZone("America/New_York"));
|
||||
setError(null);
|
||||
} catch (error) {
|
||||
@@ -345,12 +344,10 @@ const MiniStatCards = ({
|
||||
setProjectionLoading(true);
|
||||
const params =
|
||||
timeRange === "custom" ? { startDate, endDate } : { timeRange };
|
||||
const response = await axios.get("/api/klaviyo/events/projection", {
|
||||
params,
|
||||
});
|
||||
const response = await acotService.getProjection(params);
|
||||
|
||||
if (!isMounted) return;
|
||||
setProjection(response.data);
|
||||
setProjection(response);
|
||||
} catch (error) {
|
||||
console.error("Error loading projection:", error);
|
||||
} finally {
|
||||
@@ -373,16 +370,12 @@ const MiniStatCards = ({
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const [statsResponse, projectionResponse] = await Promise.all([
|
||||
axios.get("/api/klaviyo/events/stats", {
|
||||
params: { timeRange: "today" },
|
||||
}),
|
||||
axios.get("/api/klaviyo/events/projection", {
|
||||
params: { timeRange: "today" },
|
||||
}),
|
||||
acotService.getStats({ timeRange: "today" }),
|
||||
acotService.getProjection({ timeRange: "today" }),
|
||||
]);
|
||||
|
||||
setStats(statsResponse.data.stats);
|
||||
setProjection(projectionResponse.data);
|
||||
setStats(statsResponse.stats);
|
||||
setProjection(projectionResponse);
|
||||
setLastUpdate(DateTime.now().setZone("America/New_York"));
|
||||
} catch (error) {
|
||||
console.error("Error auto-refreshing stats:", error);
|
||||
@@ -399,15 +392,13 @@ const MiniStatCards = ({
|
||||
|
||||
setDetailDataLoading((prev) => ({ ...prev, [metric]: true }));
|
||||
try {
|
||||
const response = await axios.get("/api/klaviyo/events/stats/details", {
|
||||
params: {
|
||||
timeRange: "last30days",
|
||||
metric,
|
||||
daily: true,
|
||||
},
|
||||
});
|
||||
const response = await acotService.getStatsDetails({
|
||||
timeRange: "last30days",
|
||||
metric,
|
||||
daily: true,
|
||||
});
|
||||
|
||||
setDetailData((prev) => ({ ...prev, [metric]: response.data.stats }));
|
||||
setDetailData((prev) => ({ ...prev, [metric]: response.stats }));
|
||||
} catch (error) {
|
||||
console.error(`Error fetching detail data for ${metric}:`, error);
|
||||
} finally {
|
||||
@@ -424,13 +415,23 @@ const MiniStatCards = ({
|
||||
}
|
||||
}, [selectedMetric, fetchDetailData]);
|
||||
|
||||
// Add preload effect
|
||||
// Add preload effect with throttling
|
||||
useEffect(() => {
|
||||
// Preload all detail data when component mounts
|
||||
const metrics = ["revenue", "orders", "average_order", "shipping"];
|
||||
metrics.forEach((metric) => {
|
||||
fetchDetailData(metric);
|
||||
});
|
||||
// Preload detail data with throttling to avoid overwhelming the server
|
||||
const preloadData = async () => {
|
||||
const metrics = ["revenue", "orders", "average_order", "shipping"];
|
||||
for (const metric of metrics) {
|
||||
try {
|
||||
await fetchDetailData(metric);
|
||||
// Small delay between requests
|
||||
await new Promise(resolve => setTimeout(resolve, 25));
|
||||
} catch (error) {
|
||||
console.error(`Error preloading ${metric}:`, error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
preloadData();
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
if (loading && !stats) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { acotService } from "@/services/acotService";
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Loader2, ArrowUpDown, AlertCircle, Package, Settings2, Search, X } from "lucide-react";
|
||||
@@ -57,10 +58,8 @@ const ProductGrid = ({
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
const response = await axios.get("/api/klaviyo/events/products", {
|
||||
params: { timeRange: selectedTimeRange },
|
||||
});
|
||||
setProducts(response.data.stats.products.list || []);
|
||||
const response = await acotService.getProducts({ timeRange: selectedTimeRange });
|
||||
setProducts(response.stats.products.list || []);
|
||||
} catch (error) {
|
||||
console.error("Error fetching products:", error);
|
||||
setError(error.message);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, useMemo, useCallback, memo } from "react";
|
||||
import axios from "axios";
|
||||
import { acotService } from "@/services/acotService";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -550,10 +551,8 @@ const SalesChart = ({ timeRange = "last30days", title = "Sales Overview" }) => {
|
||||
|
||||
try {
|
||||
setProjectionLoading(true);
|
||||
const response = await axios.get("/api/klaviyo/events/projection", {
|
||||
params,
|
||||
});
|
||||
setProjection(response.data);
|
||||
const response = await acotService.getProjection(params);
|
||||
setProjection(response);
|
||||
} catch (error) {
|
||||
console.error("Error loading projection:", error);
|
||||
} finally {
|
||||
@@ -568,22 +567,20 @@ const SalesChart = ({ timeRange = "last30days", title = "Sales Overview" }) => {
|
||||
setError(null);
|
||||
|
||||
// Fetch data
|
||||
const response = await axios.get("/api/klaviyo/events/stats/details", {
|
||||
params: {
|
||||
...params,
|
||||
metric: "revenue",
|
||||
daily: true,
|
||||
},
|
||||
const response = await acotService.getStatsDetails({
|
||||
...params,
|
||||
metric: "revenue",
|
||||
daily: true,
|
||||
});
|
||||
|
||||
if (!response.data) {
|
||||
if (!response.stats) {
|
||||
throw new Error("Invalid response format");
|
||||
}
|
||||
|
||||
// Process the data
|
||||
const currentStats = Array.isArray(response.data)
|
||||
? response.data
|
||||
: response.data.stats || [];
|
||||
const currentStats = Array.isArray(response.stats)
|
||||
? response.stats
|
||||
: [];
|
||||
|
||||
// Process the data directly without remapping
|
||||
const processedData = processData(currentStats);
|
||||
@@ -614,20 +611,15 @@ const SalesChart = ({ timeRange = "last30days", title = "Sales Overview" }) => {
|
||||
[fetchData]
|
||||
);
|
||||
|
||||
// Initial load effect
|
||||
useEffect(() => {
|
||||
fetchData({ timeRange: selectedTimeRange });
|
||||
}, [selectedTimeRange, fetchData]);
|
||||
|
||||
// Auto-refresh effect for 'today' view
|
||||
// Initial load and auto-refresh effect
|
||||
useEffect(() => {
|
||||
let intervalId = null;
|
||||
|
||||
if (selectedTimeRange === "today") {
|
||||
// Initial fetch
|
||||
fetchData({ timeRange: "today" });
|
||||
// Initial fetch
|
||||
fetchData({ timeRange: selectedTimeRange });
|
||||
|
||||
// Set up interval
|
||||
// Set up auto-refresh only for 'today' view
|
||||
if (selectedTimeRange === "today") {
|
||||
intervalId = setInterval(() => {
|
||||
fetchData({ timeRange: "today" });
|
||||
}, 60000);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState, useEffect, useCallback, Suspense, memo } from "react";
|
||||
import axios from "axios";
|
||||
import { acotService } from "@/services/acotService";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
@@ -1415,10 +1416,8 @@ const StatCards = ({
|
||||
|
||||
// For metrics that need the full stats
|
||||
if (["shipping", "brands_categories"].includes(metric)) {
|
||||
const response = await axios.get("/api/klaviyo/events/stats", {
|
||||
params,
|
||||
});
|
||||
const data = [response.data.stats];
|
||||
const response = await acotService.getStats(params);
|
||||
const data = [response.stats];
|
||||
setCacheData(detailTimeRange, metric, data);
|
||||
setDetailData((prev) => ({ ...prev, [metric]: data }));
|
||||
setError(null);
|
||||
@@ -1427,16 +1426,11 @@ const StatCards = ({
|
||||
|
||||
// For order types (pre_orders, local_pickup, on_hold)
|
||||
if (["pre_orders", "local_pickup", "on_hold"].includes(metric)) {
|
||||
const response = await axios.get(
|
||||
"/api/klaviyo/events/stats/details",
|
||||
{
|
||||
params: {
|
||||
...params,
|
||||
orderType: orderType,
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = response.data.stats;
|
||||
const response = await acotService.getStatsDetails({
|
||||
...params,
|
||||
orderType: orderType,
|
||||
});
|
||||
const data = response.stats;
|
||||
setCacheData(detailTimeRange, metric, data);
|
||||
setDetailData((prev) => ({ ...prev, [metric]: data }));
|
||||
setError(null);
|
||||
@@ -1445,17 +1439,12 @@ const StatCards = ({
|
||||
|
||||
// For refunds and cancellations
|
||||
if (["refunds", "cancellations"].includes(metric)) {
|
||||
const response = await axios.get(
|
||||
"/api/klaviyo/events/stats/details",
|
||||
{
|
||||
params: {
|
||||
...params,
|
||||
eventType:
|
||||
metric === "refunds" ? "PAYMENT_REFUNDED" : "CANCELED_ORDER",
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = response.data.stats;
|
||||
const response = await acotService.getStatsDetails({
|
||||
...params,
|
||||
eventType:
|
||||
metric === "refunds" ? "PAYMENT_REFUNDED" : "CANCELED_ORDER",
|
||||
});
|
||||
const data = response.stats;
|
||||
|
||||
// Transform the data to match the expected format
|
||||
const transformedData = data.map((day) => ({
|
||||
@@ -1487,16 +1476,11 @@ const StatCards = ({
|
||||
|
||||
// For order range
|
||||
if (metric === "order_range") {
|
||||
const response = await axios.get(
|
||||
"/api/klaviyo/events/stats/details",
|
||||
{
|
||||
params: {
|
||||
...params,
|
||||
eventType: "PLACED_ORDER",
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = response.data.stats;
|
||||
const response = await acotService.getStatsDetails({
|
||||
...params,
|
||||
eventType: "PLACED_ORDER",
|
||||
});
|
||||
const data = response.stats;
|
||||
console.log("Fetched order range data:", data);
|
||||
setCacheData(detailTimeRange, metric, data);
|
||||
setDetailData((prev) => ({ ...prev, [metric]: data }));
|
||||
@@ -1505,10 +1489,8 @@ const StatCards = ({
|
||||
}
|
||||
|
||||
// For all other metrics
|
||||
const response = await axios.get("/api/klaviyo/events/stats/details", {
|
||||
params,
|
||||
});
|
||||
const data = response.data.stats;
|
||||
const response = await acotService.getStatsDetails(params);
|
||||
const data = response.stats;
|
||||
setCacheData(detailTimeRange, metric, data);
|
||||
setDetailData((prev) => ({ ...prev, [metric]: data }));
|
||||
setError(null);
|
||||
@@ -1531,8 +1513,8 @@ const StatCards = ({
|
||||
]
|
||||
);
|
||||
|
||||
// Corrected preloadDetailData function
|
||||
const preloadDetailData = useCallback(() => {
|
||||
// Throttled preloadDetailData function to avoid overwhelming the server
|
||||
const preloadDetailData = useCallback(async () => {
|
||||
const metrics = [
|
||||
"revenue",
|
||||
"orders",
|
||||
@@ -1545,11 +1527,22 @@ const StatCards = ({
|
||||
"on_hold",
|
||||
];
|
||||
|
||||
return Promise.all(
|
||||
metrics.map((metric) => fetchDetailData(metric, metric))
|
||||
).catch((error) => {
|
||||
console.error("Error during detail data preload:", error);
|
||||
});
|
||||
// Process metrics in batches of 3 to avoid overwhelming the connection pool
|
||||
const batchSize = 3;
|
||||
for (let i = 0; i < metrics.length; i += batchSize) {
|
||||
const batch = metrics.slice(i, i + batchSize);
|
||||
try {
|
||||
await Promise.all(
|
||||
batch.map((metric) => fetchDetailData(metric, metric))
|
||||
);
|
||||
// Small delay between batches to prevent overwhelming the server
|
||||
if (i + batchSize < metrics.length) {
|
||||
await new Promise(resolve => setTimeout(resolve, 50));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error during detail data preload batch ${i / batchSize + 1}:`, error);
|
||||
}
|
||||
}
|
||||
}, [fetchDetailData]);
|
||||
|
||||
// Move trend calculation functions inside the component
|
||||
@@ -1630,14 +1623,12 @@ const StatCards = ({
|
||||
const params =
|
||||
timeRange === "custom" ? { startDate, endDate } : { timeRange };
|
||||
|
||||
const response = await axios.get("/api/klaviyo/events/stats", {
|
||||
params,
|
||||
});
|
||||
const response = await acotService.getStats(params);
|
||||
|
||||
if (!isMounted) return;
|
||||
|
||||
setDateRange(response.data.timeRange);
|
||||
setStats(response.data.stats);
|
||||
setDateRange(response.timeRange);
|
||||
setStats(response.stats);
|
||||
setLastUpdate(DateTime.now().setZone("America/New_York"));
|
||||
setError(null);
|
||||
|
||||
@@ -1674,12 +1665,10 @@ const StatCards = ({
|
||||
const params =
|
||||
timeRange === "custom" ? { startDate, endDate } : { timeRange };
|
||||
|
||||
const response = await axios.get("/api/klaviyo/events/projection", {
|
||||
params,
|
||||
});
|
||||
const response = await acotService.getProjection(params);
|
||||
|
||||
if (!isMounted) return;
|
||||
setProjection(response.data);
|
||||
setProjection(response);
|
||||
} catch (error) {
|
||||
console.error("Error loading projection:", error);
|
||||
} finally {
|
||||
@@ -1702,16 +1691,12 @@ const StatCards = ({
|
||||
const interval = setInterval(async () => {
|
||||
try {
|
||||
const [statsResponse, projectionResponse] = await Promise.all([
|
||||
axios.get("/api/klaviyo/events/stats", {
|
||||
params: { timeRange: "today" },
|
||||
}),
|
||||
axios.get("/api/klaviyo/events/projection", {
|
||||
params: { timeRange: "today" },
|
||||
}),
|
||||
acotService.getStats({ timeRange: "today" }),
|
||||
acotService.getProjection({ timeRange: "today" }),
|
||||
]);
|
||||
|
||||
setStats(statsResponse.data.stats);
|
||||
setProjection(projectionResponse.data);
|
||||
setStats(statsResponse.stats);
|
||||
setProjection(projectionResponse);
|
||||
setLastUpdate(DateTime.now().setZone("America/New_York"));
|
||||
} catch (error) {
|
||||
console.error("Error auto-refreshing stats:", error);
|
||||
|
||||
176
dashboard/src/services/acotService.js
Normal file
176
dashboard/src/services/acotService.js
Normal file
@@ -0,0 +1,176 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// Use the proxy in development, direct URL in production
|
||||
const ACOT_BASE_URL = process.env.NODE_ENV === 'development'
|
||||
? '' // Use proxy in development (which now points to production)
|
||||
: (process.env.REACT_APP_ACOT_API_URL || 'https://dashboard.kent.pw');
|
||||
|
||||
const acotApi = axios.create({
|
||||
baseURL: ACOT_BASE_URL,
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
// Request deduplication cache
|
||||
const requestCache = new Map();
|
||||
|
||||
// Periodic cache cleanup (every 5 minutes)
|
||||
setInterval(() => {
|
||||
const now = Date.now();
|
||||
const maxAge = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
for (const [key, value] of requestCache.entries()) {
|
||||
if (value.timestamp && now - value.timestamp > maxAge) {
|
||||
requestCache.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (requestCache.size > 0) {
|
||||
console.log(`[ACOT API] Cache cleanup: ${requestCache.size} entries remaining`);
|
||||
}
|
||||
}, 5 * 60 * 1000);
|
||||
|
||||
// Retry function for timeout errors
|
||||
const retryRequest = async (requestFn, maxRetries = 2, delay = 1000) => {
|
||||
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
||||
try {
|
||||
return await requestFn();
|
||||
} catch (error) {
|
||||
const isTimeout = error.code === 'ECONNABORTED' || error.message.includes('timeout');
|
||||
const isLastAttempt = attempt === maxRetries + 1;
|
||||
|
||||
if (isTimeout && !isLastAttempt) {
|
||||
console.log(`[ACOT API] Timeout on attempt ${attempt}, retrying in ${delay}ms...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
delay *= 1.5; // Exponential backoff
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Request deduplication function
|
||||
const deduplicatedRequest = async (cacheKey, requestFn, cacheDuration = 5000) => {
|
||||
// Check if we have a pending request for this key
|
||||
if (requestCache.has(cacheKey)) {
|
||||
const cached = requestCache.get(cacheKey);
|
||||
|
||||
// If it's a pending promise, return it
|
||||
if (cached.promise) {
|
||||
console.log(`[ACOT API] Deduplicating request: ${cacheKey}`);
|
||||
return cached.promise;
|
||||
}
|
||||
|
||||
// If it's cached data and still fresh, return it
|
||||
if (cached.data && Date.now() - cached.timestamp < cacheDuration) {
|
||||
console.log(`[ACOT API] Using cached data: ${cacheKey}`);
|
||||
return cached.data;
|
||||
}
|
||||
}
|
||||
|
||||
// Create new request
|
||||
const promise = requestFn().then(data => {
|
||||
// Cache the result
|
||||
requestCache.set(cacheKey, {
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
promise: null
|
||||
});
|
||||
return data;
|
||||
}).catch(error => {
|
||||
// Remove from cache on error
|
||||
requestCache.delete(cacheKey);
|
||||
throw error;
|
||||
});
|
||||
|
||||
// Cache the promise while it's pending
|
||||
requestCache.set(cacheKey, {
|
||||
promise,
|
||||
timestamp: Date.now(),
|
||||
data: null
|
||||
});
|
||||
|
||||
return promise;
|
||||
};
|
||||
|
||||
// Add request interceptor for logging
|
||||
acotApi.interceptors.request.use(
|
||||
(config) => {
|
||||
console.log(`[ACOT API] ${config.method?.toUpperCase()} ${config.url}`, config.params);
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
console.error('[ACOT API] Request error:', error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Add response interceptor for logging
|
||||
acotApi.interceptors.response.use(
|
||||
(response) => {
|
||||
console.log(`[ACOT API] Response ${response.status}:`, response.data);
|
||||
return response;
|
||||
},
|
||||
(error) => {
|
||||
console.error('[ACOT API] Response error:', error.response?.data || error.message);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Cleanup function to clear cache
|
||||
const clearCache = () => {
|
||||
requestCache.clear();
|
||||
console.log('[ACOT API] Request cache cleared');
|
||||
};
|
||||
|
||||
export const acotService = {
|
||||
// Get main stats - replaces klaviyo events/stats
|
||||
getStats: async (params) => {
|
||||
const cacheKey = `stats_${JSON.stringify(params)}`;
|
||||
return deduplicatedRequest(cacheKey, () =>
|
||||
retryRequest(async () => {
|
||||
const response = await acotApi.get('/api/acot/events/stats', { params });
|
||||
return response.data;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
// Get detailed stats - replaces klaviyo events/stats/details
|
||||
getStatsDetails: async (params) => {
|
||||
const cacheKey = `details_${JSON.stringify(params)}`;
|
||||
return deduplicatedRequest(cacheKey, () =>
|
||||
retryRequest(async () => {
|
||||
const response = await acotApi.get('/api/acot/events/stats/details', { params });
|
||||
return response.data;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
// Get products data - replaces klaviyo events/products
|
||||
getProducts: async (params) => {
|
||||
const cacheKey = `products_${JSON.stringify(params)}`;
|
||||
return deduplicatedRequest(cacheKey, () =>
|
||||
retryRequest(async () => {
|
||||
const response = await acotApi.get('/api/acot/events/products', { params });
|
||||
return response.data;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
// Get projections - replaces klaviyo events/projection
|
||||
getProjection: async (params) => {
|
||||
const cacheKey = `projection_${JSON.stringify(params)}`;
|
||||
return deduplicatedRequest(cacheKey, () =>
|
||||
retryRequest(async () => {
|
||||
const response = await acotApi.get('/api/acot/events/projection', { params });
|
||||
return response.data;
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
// Utility functions
|
||||
clearCache,
|
||||
};
|
||||
|
||||
export default acotService;
|
||||
@@ -31,6 +31,42 @@ export default defineConfig(({ mode }) => {
|
||||
host: "0.0.0.0",
|
||||
port: 3000,
|
||||
proxy: {
|
||||
"/api/acot": {
|
||||
target: "https://dashboard.kent.pw",
|
||||
changeOrigin: true,
|
||||
secure: true,
|
||||
rewrite: (path) => path.replace(/^\/api\/acot/, "/api/acot"),
|
||||
configure: (proxy, _options) => {
|
||||
proxy.on("error", (err, req, res) => {
|
||||
console.error("ACOT proxy error:", err);
|
||||
res.writeHead(500, {
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
error: "Proxy Error",
|
||||
message: err.message,
|
||||
details: err.stack
|
||||
})
|
||||
);
|
||||
});
|
||||
proxy.on("proxyReq", (proxyReq, req, _res) => {
|
||||
console.log("Outgoing ACOT request:", {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
path: proxyReq.path,
|
||||
headers: proxyReq.getHeaders(),
|
||||
});
|
||||
});
|
||||
proxy.on("proxyRes", (proxyRes, req, _res) => {
|
||||
console.log("ACOT proxy response:", {
|
||||
statusCode: proxyRes.statusCode,
|
||||
url: req.url,
|
||||
headers: proxyRes.headers,
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
"/api/klaviyo": {
|
||||
target: "https://dashboard.kent.pw",
|
||||
changeOrigin: true,
|
||||
@@ -203,42 +239,6 @@ export default defineConfig(({ mode }) => {
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
"/api/acot": {
|
||||
target: "https://dashboard.kent.pw",
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
rewrite: (path) => path.replace(/^\/api\/acot/, "/api/acot"),
|
||||
configure: (proxy, _options) => {
|
||||
proxy.on("error", (err, req, res) => {
|
||||
console.error("ACOT proxy error:", err);
|
||||
res.writeHead(500, {
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
error: "Proxy Error",
|
||||
message: err.message,
|
||||
details: err.stack
|
||||
})
|
||||
);
|
||||
});
|
||||
proxy.on("proxyReq", (proxyReq, req, _res) => {
|
||||
console.log("Outgoing ACOT request:", {
|
||||
method: req.method,
|
||||
url: req.url,
|
||||
path: proxyReq.path,
|
||||
headers: proxyReq.getHeaders(),
|
||||
});
|
||||
});
|
||||
proxy.on("proxyRes", (proxyRes, req, _res) => {
|
||||
console.log("ACOT proxy response:", {
|
||||
statusCode: proxyRes.statusCode,
|
||||
url: req.url,
|
||||
headers: proxyRes.headers,
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user