Implement automatic retry in entitySubmissionHelpers.ts

Add exponential backoff retry logic with user feedback
- Integrate with existing withRetry patterns
- Introduce unique retry IDs for submissions
- Emit consistent UI events for retry progress, success, and failure
- Enhance rate-limit handling including Retry-After scenarios
- Standardize baseDelay and shouldRetry across park, ride, company, ride_model, and other submissions
This commit is contained in:
gpt-engineer-app[bot]
2025-11-10 19:02:11 +00:00
parent 496ff48e34
commit 8ed5edbe24

View File

@@ -773,6 +773,8 @@ export async function submitParkCreation(
}
// Create submission with retry logic
const retryId = crypto.randomUUID();
const result = await withRetry(
async () => {
// Create the main submission record
@@ -882,12 +884,13 @@ export async function submitParkCreation(
},
{
maxAttempts: 3,
baseDelay: 1000,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying park submission', { attempt, delay });
logger.warn('Retrying park submission', { attempt, delay, error: error instanceof Error ? error.message : String(error) });
// Emit event for UI indicator
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'park' }
detail: { id: retryId, attempt, maxAttempts: 3, delay, type: 'park' }
}));
},
shouldRetry: (error) => {
@@ -896,18 +899,35 @@ export async function submitParkCreation(
const message = error.message.toLowerCase();
if (message.includes('required')) return false;
if (message.includes('banned')) return false;
if (message.includes('suspended')) return false;
if (message.includes('slug')) return false;
if (message.includes('already exists')) return false;
if (message.includes('duplicate')) return false;
if (message.includes('permission')) return false;
if (message.includes('forbidden')) return false;
if (message.includes('unauthorized')) return false;
}
return isRetryableError(error);
}
}
).catch((error) => {
handleError(error, {
).then((data) => {
// Emit success event
window.dispatchEvent(new CustomEvent('submission-retry-success', {
detail: { id: retryId }
}));
return data;
}).catch((error) => {
const errorId = handleError(error, {
action: 'Park submission',
metadata: { retriesExhausted: true },
});
// Emit failure event
window.dispatchEvent(new CustomEvent('submission-retry-failed', {
detail: { id: retryId, errorId }
}));
throw error;
});
@@ -1103,6 +1123,7 @@ export async function submitParkUpdate(
},
{
maxAttempts: 3,
baseDelay: 1000,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying park update submission', {
attempt,
@@ -1506,8 +1527,13 @@ export async function submitRideCreation(
},
{
maxAttempts: 3,
baseDelay: 1000,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying ride submission', { attempt, delay });
logger.warn('Retrying ride submission', {
attempt,
delay,
error: error instanceof Error ? error.message : String(error)
});
// Emit event for UI indicator
window.dispatchEvent(new CustomEvent('submission-retry', {
@@ -1520,8 +1546,13 @@ export async function submitRideCreation(
const message = error.message.toLowerCase();
if (message.includes('required')) return false;
if (message.includes('banned')) return false;
if (message.includes('suspended')) return false;
if (message.includes('slug')) return false;
if (message.includes('already exists')) return false;
if (message.includes('duplicate')) return false;
if (message.includes('permission')) return false;
if (message.includes('forbidden')) return false;
if (message.includes('unauthorized')) return false;
}
return isRetryableError(error);
@@ -1714,6 +1745,7 @@ export async function submitRideUpdate(
},
{
maxAttempts: 3,
baseDelay: 1000,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying ride update submission', {
attempt,
@@ -1733,8 +1765,13 @@ export async function submitRideUpdate(
const message = error.message.toLowerCase();
if (message.includes('required')) return false;
if (message.includes('banned')) return false;
if (message.includes('suspended')) return false;
if (message.includes('slug')) return false;
if (message.includes('already exists')) return false;
if (message.includes('duplicate')) return false;
if (message.includes('permission')) return false;
if (message.includes('forbidden')) return false;
if (message.includes('unauthorized')) return false;
if (message.includes('not found')) return false;
if (message.includes('not allowed')) return false;
}
@@ -1838,6 +1875,8 @@ export async function submitRideModelCreation(
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const retryId = crypto.randomUUID();
const result = await withRetry(
async () => {
// Create the main submission record
@@ -1925,10 +1964,15 @@ export async function submitRideModelCreation(
},
{
maxAttempts: 3,
baseDelay: 1000,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying ride model submission', { attempt, delay });
logger.warn('Retrying ride model submission', {
attempt,
delay,
error: error instanceof Error ? error.message : String(error)
});
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'ride_model' }
detail: { id: retryId, attempt, maxAttempts: 3, delay, type: 'ride_model' }
}));
},
shouldRetry: (error) => {
@@ -1936,12 +1980,36 @@ export async function submitRideModelCreation(
const message = error.message.toLowerCase();
if (message.includes('required')) return false;
if (message.includes('banned')) return false;
if (message.includes('suspended')) return false;
if (message.includes('slug')) return false;
if (message.includes('already exists')) return false;
if (message.includes('duplicate')) return false;
if (message.includes('permission')) return false;
if (message.includes('forbidden')) return false;
if (message.includes('unauthorized')) return false;
}
return isRetryableError(error);
}
}
);
).then((data) => {
// Emit success event
window.dispatchEvent(new CustomEvent('submission-retry-success', {
detail: { id: retryId }
}));
return data;
}).catch((error) => {
const errorId = handleError(error, {
action: 'Ride model submission',
metadata: { retriesExhausted: true },
});
// Emit failure event
window.dispatchEvent(new CustomEvent('submission-retry-failed', {
detail: { id: retryId, errorId }
}));
throw error;
});
return result;
}
@@ -2006,6 +2074,8 @@ export async function submitRideModelUpdate(
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const retryId = crypto.randomUUID();
const result = await withRetry(
async () => {
// Create the main submission record
@@ -2091,10 +2161,15 @@ export async function submitRideModelUpdate(
},
{
maxAttempts: 3,
baseDelay: 1000,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying ride model update', { attempt, delay });
logger.warn('Retrying ride model update', {
attempt,
delay,
error: error instanceof Error ? error.message : String(error)
});
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'ride_model_update' }
detail: { id: retryId, attempt, maxAttempts: 3, delay, type: 'ride_model_update' }
}));
},
shouldRetry: (error) => {
@@ -2102,12 +2177,34 @@ export async function submitRideModelUpdate(
const message = error.message.toLowerCase();
if (message.includes('required')) return false;
if (message.includes('banned')) return false;
if (message.includes('suspended')) return false;
if (message.includes('slug')) return false;
if (message.includes('already exists')) return false;
if (message.includes('duplicate')) return false;
if (message.includes('permission')) return false;
if (message.includes('forbidden')) return false;
if (message.includes('unauthorized')) return false;
}
return isRetryableError(error);
}
}
);
).then((data) => {
window.dispatchEvent(new CustomEvent('submission-retry-success', {
detail: { id: retryId }
}));
return data;
}).catch((error) => {
const errorId = handleError(error, {
action: 'Ride model update submission',
metadata: { retriesExhausted: true },
});
window.dispatchEvent(new CustomEvent('submission-retry-failed', {
detail: { id: retryId, errorId }
}));
throw error;
});
return result;
}
@@ -2170,6 +2267,8 @@ export async function submitManufacturerCreation(
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const retryId = crypto.randomUUID();
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
@@ -2209,10 +2308,15 @@ export async function submitManufacturerCreation(
},
{
maxAttempts: 3,
baseDelay: 1000,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying manufacturer submission', { attempt, delay });
logger.warn('Retrying manufacturer submission', {
attempt,
delay,
error: error instanceof Error ? error.message : String(error)
});
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'manufacturer' }
detail: { id: retryId, attempt, maxAttempts: 3, delay, type: 'manufacturer' }
}));
},
shouldRetry: (error) => {
@@ -2220,12 +2324,34 @@ export async function submitManufacturerCreation(
const message = error.message.toLowerCase();
if (message.includes('required')) return false;
if (message.includes('banned')) return false;
if (message.includes('suspended')) return false;
if (message.includes('slug')) return false;
if (message.includes('already exists')) return false;
if (message.includes('duplicate')) return false;
if (message.includes('permission')) return false;
if (message.includes('forbidden')) return false;
if (message.includes('unauthorized')) return false;
}
return isRetryableError(error);
}
}
);
).then((data) => {
window.dispatchEvent(new CustomEvent('submission-retry-success', {
detail: { id: retryId }
}));
return data;
}).catch((error) => {
const errorId = handleError(error, {
action: 'Manufacturer submission',
metadata: { retriesExhausted: true },
});
window.dispatchEvent(new CustomEvent('submission-retry-failed', {
detail: { id: retryId, errorId }
}));
throw error;
});
return result;
}
@@ -2618,6 +2744,8 @@ export async function submitOperatorCreation(
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const retryId = crypto.randomUUID();
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
@@ -2657,10 +2785,15 @@ export async function submitOperatorCreation(
},
{
maxAttempts: 3,
baseDelay: 1000,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying operator submission', { attempt, delay });
logger.warn('Retrying operator submission', {
attempt,
delay,
error: error instanceof Error ? error.message : String(error)
});
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'operator' }
detail: { id: retryId, attempt, maxAttempts: 3, delay, type: 'operator' }
}));
},
shouldRetry: (error) => {
@@ -2668,12 +2801,34 @@ export async function submitOperatorCreation(
const message = error.message.toLowerCase();
if (message.includes('required')) return false;
if (message.includes('banned')) return false;
if (message.includes('suspended')) return false;
if (message.includes('slug')) return false;
if (message.includes('already exists')) return false;
if (message.includes('duplicate')) return false;
if (message.includes('permission')) return false;
if (message.includes('forbidden')) return false;
if (message.includes('unauthorized')) return false;
}
return isRetryableError(error);
}
}
);
).then((data) => {
window.dispatchEvent(new CustomEvent('submission-retry-success', {
detail: { id: retryId }
}));
return data;
}).catch((error) => {
const errorId = handleError(error, {
action: 'Operator submission',
metadata: { retriesExhausted: true },
});
window.dispatchEvent(new CustomEvent('submission-retry-failed', {
detail: { id: retryId, errorId }
}));
throw error;
});
return result;
}
@@ -2731,6 +2886,8 @@ export async function submitOperatorUpdate(
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const retryId = crypto.randomUUID();
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
@@ -2842,6 +2999,8 @@ export async function submitPropertyOwnerCreation(
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const retryId = crypto.randomUUID();
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
@@ -2881,10 +3040,15 @@ export async function submitPropertyOwnerCreation(
},
{
maxAttempts: 3,
baseDelay: 1000,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying property owner submission', { attempt, delay });
logger.warn('Retrying property owner submission', {
attempt,
delay,
error: error instanceof Error ? error.message : String(error)
});
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'property_owner' }
detail: { id: retryId, attempt, maxAttempts: 3, delay, type: 'property_owner' }
}));
},
shouldRetry: (error) => {
@@ -2892,12 +3056,34 @@ export async function submitPropertyOwnerCreation(
const message = error.message.toLowerCase();
if (message.includes('required')) return false;
if (message.includes('banned')) return false;
if (message.includes('suspended')) return false;
if (message.includes('slug')) return false;
if (message.includes('already exists')) return false;
if (message.includes('duplicate')) return false;
if (message.includes('permission')) return false;
if (message.includes('forbidden')) return false;
if (message.includes('unauthorized')) return false;
}
return isRetryableError(error);
}
}
);
).then((data) => {
window.dispatchEvent(new CustomEvent('submission-retry-success', {
detail: { id: retryId }
}));
return data;
}).catch((error) => {
const errorId = handleError(error, {
action: 'Property owner submission',
metadata: { retriesExhausted: true },
});
window.dispatchEvent(new CustomEvent('submission-retry-failed', {
detail: { id: retryId, errorId }
}));
throw error;
});
return result;
}