feat: Implement circuit breaker and retry logic

This commit is contained in:
gpt-engineer-app[bot]
2025-11-05 13:27:22 +00:00
parent 5e0640252c
commit ec5181b9e6
7 changed files with 664 additions and 245 deletions

View File

@@ -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;
}