mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-25 04:11:12 -05:00
feat: Implement circuit breaker and retry logic
This commit is contained in:
@@ -3,6 +3,8 @@ import type { Json } from '@/integrations/supabase/types';
|
||||
import { uploadPendingImages } from './imageUploadHelper';
|
||||
import { CompanyFormData, TempCompanyData } from '@/types/company';
|
||||
import { handleError } from './errorHandler';
|
||||
import { withRetry } from './retryHelpers';
|
||||
import { logger } from './logger';
|
||||
|
||||
export type { CompanyFormData, TempCompanyData };
|
||||
|
||||
@@ -11,12 +13,18 @@ export async function submitCompanyCreation(
|
||||
companyType: 'manufacturer' | 'designer' | 'operator' | 'property_owner',
|
||||
userId: string
|
||||
) {
|
||||
// Check if user is banned
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select('banned')
|
||||
.eq('user_id', userId)
|
||||
.single();
|
||||
// Check if user is banned (with quick retry for read operation)
|
||||
const profile = await withRetry(
|
||||
async () => {
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select('banned')
|
||||
.eq('user_id', userId)
|
||||
.single();
|
||||
return profile;
|
||||
},
|
||||
{ maxAttempts: 2 }
|
||||
);
|
||||
|
||||
if (profile?.banned) {
|
||||
throw new Error('Account suspended. Contact support for assistance.');
|
||||
@@ -40,46 +48,83 @@ export async function submitCompanyCreation(
|
||||
}
|
||||
}
|
||||
|
||||
// Create the main submission record
|
||||
const { data: submissionData, error: submissionError } = await supabase
|
||||
.from('content_submissions')
|
||||
.insert({
|
||||
user_id: userId,
|
||||
submission_type: companyType,
|
||||
content: {
|
||||
action: 'create'
|
||||
},
|
||||
status: 'pending' as const
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
// Create submission with retry logic
|
||||
const result = await withRetry(
|
||||
async () => {
|
||||
// Create the main submission record
|
||||
const { data: submissionData, error: submissionError } = await supabase
|
||||
.from('content_submissions')
|
||||
.insert({
|
||||
user_id: userId,
|
||||
submission_type: companyType,
|
||||
content: {
|
||||
action: 'create'
|
||||
},
|
||||
status: 'pending' as const
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
|
||||
if (submissionError) throw submissionError;
|
||||
if (submissionError) throw submissionError;
|
||||
|
||||
// Create the submission item with actual company data
|
||||
const { error: itemError } = await supabase
|
||||
.from('submission_items')
|
||||
.insert({
|
||||
submission_id: submissionData.id,
|
||||
item_type: companyType,
|
||||
item_data: {
|
||||
name: data.name,
|
||||
slug: data.slug,
|
||||
description: data.description,
|
||||
person_type: data.person_type,
|
||||
website_url: data.website_url,
|
||||
founded_year: data.founded_year,
|
||||
headquarters_location: data.headquarters_location,
|
||||
company_type: companyType,
|
||||
images: processedImages as unknown as Json
|
||||
// Create the submission item with actual company data
|
||||
const { error: itemError } = await supabase
|
||||
.from('submission_items')
|
||||
.insert({
|
||||
submission_id: submissionData.id,
|
||||
item_type: companyType,
|
||||
item_data: {
|
||||
name: data.name,
|
||||
slug: data.slug,
|
||||
description: data.description,
|
||||
person_type: data.person_type,
|
||||
website_url: data.website_url,
|
||||
founded_year: data.founded_year,
|
||||
headquarters_location: data.headquarters_location,
|
||||
company_type: companyType,
|
||||
images: processedImages as unknown as Json
|
||||
},
|
||||
status: 'pending' as const,
|
||||
order_index: 0
|
||||
});
|
||||
|
||||
if (itemError) throw itemError;
|
||||
|
||||
return { submitted: true, submissionId: submissionData.id };
|
||||
},
|
||||
{
|
||||
maxAttempts: 3,
|
||||
onRetry: (attempt, error, delay) => {
|
||||
logger.warn('Retrying company submission', { attempt, delay, companyType });
|
||||
|
||||
// Emit event for UI indicator
|
||||
window.dispatchEvent(new CustomEvent('submission-retry', {
|
||||
detail: { attempt, maxAttempts: 3, delay, type: companyType }
|
||||
}));
|
||||
},
|
||||
status: 'pending' as const,
|
||||
order_index: 0
|
||||
shouldRetry: (error) => {
|
||||
// Don't retry validation/business logic errors
|
||||
if (error instanceof Error) {
|
||||
const message = error.message.toLowerCase();
|
||||
if (message.includes('required')) return false;
|
||||
if (message.includes('banned')) return false;
|
||||
if (message.includes('slug')) return false;
|
||||
if (message.includes('permission')) return false;
|
||||
}
|
||||
|
||||
const { isRetryableError } = require('./retryHelpers');
|
||||
return isRetryableError(error);
|
||||
}
|
||||
}
|
||||
).catch((error) => {
|
||||
handleError(error, {
|
||||
action: `${companyType} submission`,
|
||||
metadata: { retriesExhausted: true },
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
|
||||
if (itemError) throw itemError;
|
||||
|
||||
return { submitted: true, submissionId: submissionData.id };
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function submitCompanyUpdate(
|
||||
@@ -87,12 +132,18 @@ export async function submitCompanyUpdate(
|
||||
data: CompanyFormData,
|
||||
userId: string
|
||||
) {
|
||||
// Check if user is banned
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select('banned')
|
||||
.eq('user_id', userId)
|
||||
.single();
|
||||
// Check if user is banned (with quick retry for read operation)
|
||||
const profile = await withRetry(
|
||||
async () => {
|
||||
const { data: profile } = await supabase
|
||||
.from('profiles')
|
||||
.select('banned')
|
||||
.eq('user_id', userId)
|
||||
.single();
|
||||
return profile;
|
||||
},
|
||||
{ maxAttempts: 2 }
|
||||
);
|
||||
|
||||
if (profile?.banned) {
|
||||
throw new Error('Account suspended. Contact support for assistance.');
|
||||
@@ -126,46 +177,83 @@ export async function submitCompanyUpdate(
|
||||
}
|
||||
}
|
||||
|
||||
// Create the main submission record
|
||||
const { data: submissionData, error: submissionError } = await supabase
|
||||
.from('content_submissions')
|
||||
.insert({
|
||||
user_id: userId,
|
||||
submission_type: existingCompany.company_type,
|
||||
content: {
|
||||
action: 'edit',
|
||||
company_id: companyId
|
||||
},
|
||||
status: 'pending' as const
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
// Create submission with retry logic
|
||||
const result = await withRetry(
|
||||
async () => {
|
||||
// Create the main submission record
|
||||
const { data: submissionData, error: submissionError } = await supabase
|
||||
.from('content_submissions')
|
||||
.insert({
|
||||
user_id: userId,
|
||||
submission_type: existingCompany.company_type,
|
||||
content: {
|
||||
action: 'edit',
|
||||
company_id: companyId
|
||||
},
|
||||
status: 'pending' as const
|
||||
})
|
||||
.select('id')
|
||||
.single();
|
||||
|
||||
if (submissionError) throw submissionError;
|
||||
if (submissionError) throw submissionError;
|
||||
|
||||
// Create the submission item with actual company data AND original data
|
||||
const { error: itemError } = await supabase
|
||||
.from('submission_items')
|
||||
.insert({
|
||||
submission_id: submissionData.id,
|
||||
item_type: existingCompany.company_type,
|
||||
item_data: {
|
||||
company_id: companyId,
|
||||
name: data.name,
|
||||
slug: data.slug,
|
||||
description: data.description,
|
||||
person_type: data.person_type,
|
||||
website_url: data.website_url,
|
||||
founded_year: data.founded_year,
|
||||
headquarters_location: data.headquarters_location,
|
||||
images: processedImages as unknown as Json
|
||||
// Create the submission item with actual company data AND original data
|
||||
const { error: itemError } = await supabase
|
||||
.from('submission_items')
|
||||
.insert({
|
||||
submission_id: submissionData.id,
|
||||
item_type: existingCompany.company_type,
|
||||
item_data: {
|
||||
company_id: companyId,
|
||||
name: data.name,
|
||||
slug: data.slug,
|
||||
description: data.description,
|
||||
person_type: data.person_type,
|
||||
website_url: data.website_url,
|
||||
founded_year: data.founded_year,
|
||||
headquarters_location: data.headquarters_location,
|
||||
images: processedImages as unknown as Json
|
||||
},
|
||||
original_data: JSON.parse(JSON.stringify(existingCompany)),
|
||||
status: 'pending' as const,
|
||||
order_index: 0
|
||||
});
|
||||
|
||||
if (itemError) throw itemError;
|
||||
|
||||
return { submitted: true, submissionId: submissionData.id };
|
||||
},
|
||||
{
|
||||
maxAttempts: 3,
|
||||
onRetry: (attempt, error, delay) => {
|
||||
logger.warn('Retrying company update', { attempt, delay, companyId });
|
||||
|
||||
// Emit event for UI indicator
|
||||
window.dispatchEvent(new CustomEvent('submission-retry', {
|
||||
detail: { attempt, maxAttempts: 3, delay, type: `${existingCompany.company_type} update` }
|
||||
}));
|
||||
},
|
||||
original_data: JSON.parse(JSON.stringify(existingCompany)),
|
||||
status: 'pending' as const,
|
||||
order_index: 0
|
||||
shouldRetry: (error) => {
|
||||
// Don't retry validation/business logic errors
|
||||
if (error instanceof Error) {
|
||||
const message = error.message.toLowerCase();
|
||||
if (message.includes('required')) return false;
|
||||
if (message.includes('banned')) return false;
|
||||
if (message.includes('slug')) return false;
|
||||
if (message.includes('permission')) return false;
|
||||
}
|
||||
|
||||
const { isRetryableError } = require('./retryHelpers');
|
||||
return isRetryableError(error);
|
||||
}
|
||||
}
|
||||
).catch((error) => {
|
||||
handleError(error, {
|
||||
action: `${existingCompany.company_type} update`,
|
||||
metadata: { retriesExhausted: true, companyId },
|
||||
});
|
||||
throw error;
|
||||
});
|
||||
|
||||
if (itemError) throw itemError;
|
||||
|
||||
return { submitted: true, submissionId: submissionData.id };
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user