Release Candidate: Production Release with Multi-Tenant & Onboarding Enhancements

This commit is contained in:
ajaysi
2026-02-28 20:06:26 +05:30
parent 08a1f4a1d8
commit 4828274cbf
162 changed files with 19489 additions and 4300 deletions

View File

@@ -5,7 +5,7 @@
* and improve performance while managing cache invalidation.
*/
import { apiClient } from './client';
import { apiClient, aiApiClient } from './client';
import analyticsCache from '../services/analyticsCache';
interface PlatformAnalytics {
@@ -73,16 +73,16 @@ class CachedAnalyticsAPI {
/**
* Get analytics data with caching
*/
async getAnalyticsData(platforms?: string[], bypassCache: boolean = false): Promise<AnalyticsResponse> {
async getAnalyticsData(platforms?: string[], bypassCache: boolean = false, opts?: { start_date?: string; end_date?: string }): Promise<AnalyticsResponse> {
const baseParams: any = platforms ? { platforms: platforms.join(',') } : {};
const endpoint = '/api/analytics/data';
// If bypassing cache, add timestamp to force fresh request
const requestParams = bypassCache ? { ...baseParams, _t: Date.now() } : baseParams;
const requestParams = bypassCache ? { ...baseParams, _t: Date.now(), ...(opts || {}) } : { ...baseParams, ...(opts || {}) };
// Try to get from cache first (unless bypassing)
if (!bypassCache) {
const cached = analyticsCache.get<AnalyticsResponse>(endpoint, baseParams);
const cached = analyticsCache.get<AnalyticsResponse>(endpoint, requestParams);
if (cached) {
console.log('📦 Analytics Cache HIT: Analytics data (cached for 60 minutes)');
return cached;
@@ -95,12 +95,19 @@ class CachedAnalyticsAPI {
// Cache the result with extended TTL (unless bypassing)
if (!bypassCache) {
analyticsCache.set(endpoint, baseParams, response.data, this.CACHE_TTL.ANALYTICS_DATA);
analyticsCache.set(endpoint, requestParams, response.data, this.CACHE_TTL.ANALYTICS_DATA);
}
return response.data;
}
async getAIInsights(opts: { start_date: string; end_date: string }): Promise<{ success: boolean; insights?: any; error?: string }> {
const endpoint = '/api/analytics/ai-insights';
const params = { start_date: opts.start_date, end_date: opts.end_date, _t: Date.now() };
const response = await aiApiClient.get(endpoint, { params });
return response.data;
}
/**
* Invalidate platform status cache
*/
@@ -128,7 +135,7 @@ class CachedAnalyticsAPI {
/**
* Force refresh analytics data (bypass cache)
*/
async forceRefreshAnalyticsData(platforms?: string[]): Promise<AnalyticsResponse> {
async forceRefreshAnalyticsData(platforms?: string[], opts?: { start_date?: string; end_date?: string }): Promise<AnalyticsResponse> {
// Try to clear backend cache first (but don't fail if it doesn't work)
try {
await this.clearBackendCache(platforms);
@@ -140,7 +147,7 @@ class CachedAnalyticsAPI {
this.invalidateAnalyticsData();
// Finally get fresh data with cache bypass
return this.getAnalyticsData(platforms, true);
return this.getAnalyticsData(platforms, true, opts);
}
/**

View File

@@ -1,5 +1,21 @@
import axios from 'axios';
const sanitizeUrlForLogging = (url: string | undefined): string => {
if (!url) return '';
try {
const [base, query] = url.split('?');
if (!query) return url;
const params = new URLSearchParams(query);
if (params.has('token')) {
params.set('token', '***');
}
const queryString = params.toString();
return queryString ? `${base}?${queryString}` : base;
} catch {
return url;
}
};
// Global subscription error handler - will be set by the app
// Can be async to support subscription status refresh
let globalSubscriptionErrorHandler: ((error: any) => boolean | Promise<boolean>) | null = null;
@@ -108,7 +124,8 @@ export const pollingApiClient = axios.create({
// Add request interceptor for logging and authentication
apiClient.interceptors.request.use(
async (config) => {
console.log(`Making ${config.method?.toUpperCase()} request to ${config.url}`);
const safeUrl = sanitizeUrlForLogging(config.url);
console.log(`Making ${config.method?.toUpperCase()} request to ${safeUrl}`);
try {
if (!authTokenGetter) {
// If authTokenGetter is not set, reject the request to prevent 401 errors
@@ -123,7 +140,8 @@ apiClient.interceptors.request.use(
if (token) {
config.headers = config.headers || {};
(config.headers as any)['Authorization'] = `Bearer ${token}`;
console.log(`[apiClient] ✅ Added auth token to request: ${config.url}`);
const safeUrlWithToken = sanitizeUrlForLogging(config.url);
console.log(`[apiClient] ✅ Auth token attached for request to ${safeUrlWithToken}`);
} else {
// Token getter returned null - reject request to prevent 401 errors
// ProtectedRoute should ensure user is authenticated before components render
@@ -299,7 +317,8 @@ apiClient.interceptors.response.use(
// Add interceptors for AI client
aiApiClient.interceptors.request.use(
async (config) => {
console.log(`Making AI ${config.method?.toUpperCase()} request to ${config.url}`);
const safeUrl = sanitizeUrlForLogging(config.url);
console.log(`Making AI ${config.method?.toUpperCase()} request to ${safeUrl}`);
try {
if (!authTokenGetter) {
console.warn(`[aiApiClient] ⚠️ authTokenGetter not set for ${config.url} - request may fail authentication`);
@@ -309,7 +328,8 @@ aiApiClient.interceptors.request.use(
if (token) {
config.headers = config.headers || {};
(config.headers as any)['Authorization'] = `Bearer ${token}`;
console.log(`[aiApiClient] ✅ Added auth token to request: ${config.url}`);
const safeUrlWithToken = sanitizeUrlForLogging(config.url);
console.log(`[aiApiClient] ✅ Auth token attached for request to ${safeUrlWithToken}`);
} else {
console.warn(`[aiApiClient] ⚠️ authTokenGetter returned null for ${config.url} - user may not be signed in`);
}
@@ -579,4 +599,4 @@ pollingApiClient.interceptors.response.use(
console.error('Polling API Error:', error.message || error, error.response?.status, error.response?.data);
return Promise.reject(error);
}
);
);

View File

@@ -35,6 +35,27 @@ export interface AIInsight {
tool_path?: string;
}
export interface SIFIndexingHealth {
has_task: boolean;
status: 'healthy' | 'warning' | 'critical' | 'not_scheduled';
message?: string;
task?: {
id: number;
website_url: string;
raw_status: string;
next_execution: string | null;
last_success: string | null;
last_failure: string | null;
consecutive_failures: number;
failure_pattern?: any;
};
last_run?: {
status: string | null;
time: string | null;
error_message: string | null;
};
}
export interface SEODashboardData {
health_score: SEOHealthScore;
key_insight: string;
@@ -141,5 +162,15 @@ export const seoDashboardAPI = {
console.error('Error checking SEO dashboard health:', error);
throw error;
}
},
async getSIFHealth(): Promise<SIFIndexingHealth> {
try {
const response = await apiClient.get('/api/seo-dashboard/sif-health');
return response.data;
} catch (error) {
console.error('Error fetching SIF indexing health:', error);
throw error;
}
}
};