Add protections to company submission functions

Implement rate limiting, ban checks, retry logic, and breadcrumb tracking for all 8 company submission functions: manufacturer, designer, operator, and property_owner (both create and update). This ensures consistency with other protected entity types and enhances the robustness of the submission pipeline.
This commit is contained in:
gpt-engineer-app[bot]
2025-11-07 19:57:47 +00:00
parent eccbe0ab1f
commit 2c9358e884

View File

@@ -2000,9 +2000,36 @@ export async function submitManufacturerCreation(
data: CompanyFormData,
userId: string
): Promise<{ submitted: boolean; submissionId: string }> {
// Rate limiting check
checkRateLimitOrThrow(userId, 'manufacturer_creation');
recordSubmissionAttempt(userId);
// Breadcrumb tracking
breadcrumb.userAction('Start manufacturer submission', 'submitManufacturerCreation', { userId });
// Validate required fields client-side
assertValid(validateCompanyCreateFields({ ...data, company_type: 'manufacturer' }));
// Ban check with retry logic
const { withRetry } = await import('./retryHelpers');
breadcrumb.apiCall('profiles', 'SELECT');
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.');
}
// Upload images
let processedImages = data.images;
if (data.images?.uploaded && data.images.uploaded.length > 0) {
try {
@@ -2016,6 +2043,10 @@ export async function submitManufacturerCreation(
}
}
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
.from('content_submissions')
.insert({
@@ -2050,6 +2081,28 @@ export async function submitManufacturerCreation(
if (itemError) throw itemError;
return { submitted: true, submissionId: submissionData.id };
},
{
maxAttempts: 3,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying manufacturer submission', { attempt, delay });
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'manufacturer' }
}));
},
shouldRetry: (error) => {
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;
}
return isRetryableError(error);
}
}
);
return result;
}
export async function submitManufacturerUpdate(
@@ -2057,6 +2110,32 @@ export async function submitManufacturerUpdate(
data: CompanyFormData,
userId: string
): Promise<{ submitted: boolean; submissionId: string }> {
// Rate limiting check
checkRateLimitOrThrow(userId, 'manufacturer_update');
recordSubmissionAttempt(userId);
// Breadcrumb tracking
breadcrumb.userAction('Start manufacturer update', 'submitManufacturerUpdate', { userId, companyId });
// Ban check with retry logic
const { withRetry } = await import('./retryHelpers');
breadcrumb.apiCall('profiles', 'SELECT');
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.');
}
const { data: existingCompany, error: fetchError } = await supabase
.from('companies')
.select('*')
@@ -2073,6 +2152,10 @@ export async function submitManufacturerUpdate(
let processedImages = data.images;
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
.from('content_submissions')
.insert({
@@ -2105,15 +2188,64 @@ export async function submitManufacturerUpdate(
if (itemError) throw itemError;
return { submitted: true, submissionId: submissionData.id };
},
{
maxAttempts: 3,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying manufacturer update', { attempt, delay });
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'manufacturer_update' }
}));
},
shouldRetry: (error) => {
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;
}
return isRetryableError(error);
}
}
);
return result;
}
export async function submitDesignerCreation(
data: CompanyFormData,
userId: string
): Promise<{ submitted: boolean; submissionId: string }> {
// Rate limiting check
checkRateLimitOrThrow(userId, 'designer_creation');
recordSubmissionAttempt(userId);
// Breadcrumb tracking
breadcrumb.userAction('Start designer submission', 'submitDesignerCreation', { userId });
// Validate required fields client-side
assertValid(validateCompanyCreateFields({ ...data, company_type: 'designer' }));
// Ban check with retry logic
const { withRetry } = await import('./retryHelpers');
breadcrumb.apiCall('profiles', 'SELECT');
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.');
}
// Upload images
let processedImages = data.images;
if (data.images?.uploaded && data.images.uploaded.length > 0) {
try {
@@ -2127,6 +2259,10 @@ export async function submitDesignerCreation(
}
}
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
.from('content_submissions')
.insert({
@@ -2161,6 +2297,28 @@ export async function submitDesignerCreation(
if (itemError) throw itemError;
return { submitted: true, submissionId: submissionData.id };
},
{
maxAttempts: 3,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying designer submission', { attempt, delay });
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'designer' }
}));
},
shouldRetry: (error) => {
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;
}
return isRetryableError(error);
}
}
);
return result;
}
export async function submitDesignerUpdate(
@@ -2168,6 +2326,32 @@ export async function submitDesignerUpdate(
data: CompanyFormData,
userId: string
): Promise<{ submitted: boolean; submissionId: string }> {
// Rate limiting check
checkRateLimitOrThrow(userId, 'designer_update');
recordSubmissionAttempt(userId);
// Breadcrumb tracking
breadcrumb.userAction('Start designer update', 'submitDesignerUpdate', { userId, companyId });
// Ban check with retry logic
const { withRetry } = await import('./retryHelpers');
breadcrumb.apiCall('profiles', 'SELECT');
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.');
}
const { data: existingCompany, error: fetchError } = await supabase
.from('companies')
.select('*')
@@ -2184,6 +2368,10 @@ export async function submitDesignerUpdate(
let processedImages = data.images;
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
.from('content_submissions')
.insert({
@@ -2216,15 +2404,64 @@ export async function submitDesignerUpdate(
if (itemError) throw itemError;
return { submitted: true, submissionId: submissionData.id };
},
{
maxAttempts: 3,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying designer update', { attempt, delay });
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'designer_update' }
}));
},
shouldRetry: (error) => {
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;
}
return isRetryableError(error);
}
}
);
return result;
}
export async function submitOperatorCreation(
data: CompanyFormData,
userId: string
): Promise<{ submitted: boolean; submissionId: string }> {
// Rate limiting check
checkRateLimitOrThrow(userId, 'operator_creation');
recordSubmissionAttempt(userId);
// Breadcrumb tracking
breadcrumb.userAction('Start operator submission', 'submitOperatorCreation', { userId });
// Validate required fields client-side
assertValid(validateCompanyCreateFields({ ...data, company_type: 'operator' }));
// Ban check with retry logic
const { withRetry } = await import('./retryHelpers');
breadcrumb.apiCall('profiles', 'SELECT');
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.');
}
// Upload images
let processedImages = data.images;
if (data.images?.uploaded && data.images.uploaded.length > 0) {
try {
@@ -2238,6 +2475,10 @@ export async function submitOperatorCreation(
}
}
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
.from('content_submissions')
.insert({
@@ -2272,6 +2513,28 @@ export async function submitOperatorCreation(
if (itemError) throw itemError;
return { submitted: true, submissionId: submissionData.id };
},
{
maxAttempts: 3,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying operator submission', { attempt, delay });
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'operator' }
}));
},
shouldRetry: (error) => {
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;
}
return isRetryableError(error);
}
}
);
return result;
}
export async function submitOperatorUpdate(
@@ -2279,6 +2542,32 @@ export async function submitOperatorUpdate(
data: CompanyFormData,
userId: string
): Promise<{ submitted: boolean; submissionId: string }> {
// Rate limiting check
checkRateLimitOrThrow(userId, 'operator_update');
recordSubmissionAttempt(userId);
// Breadcrumb tracking
breadcrumb.userAction('Start operator update', 'submitOperatorUpdate', { userId, companyId });
// Ban check with retry logic
const { withRetry } = await import('./retryHelpers');
breadcrumb.apiCall('profiles', 'SELECT');
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.');
}
const { data: existingCompany, error: fetchError } = await supabase
.from('companies')
.select('*')
@@ -2295,6 +2584,10 @@ export async function submitOperatorUpdate(
let processedImages = data.images;
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
.from('content_submissions')
.insert({
@@ -2327,15 +2620,64 @@ export async function submitOperatorUpdate(
if (itemError) throw itemError;
return { submitted: true, submissionId: submissionData.id };
},
{
maxAttempts: 3,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying operator update', { attempt, delay });
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'operator_update' }
}));
},
shouldRetry: (error) => {
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;
}
return isRetryableError(error);
}
}
);
return result;
}
export async function submitPropertyOwnerCreation(
data: CompanyFormData,
userId: string
): Promise<{ submitted: boolean; submissionId: string }> {
// Rate limiting check
checkRateLimitOrThrow(userId, 'property_owner_creation');
recordSubmissionAttempt(userId);
// Breadcrumb tracking
breadcrumb.userAction('Start property owner submission', 'submitPropertyOwnerCreation', { userId });
// Validate required fields client-side
assertValid(validateCompanyCreateFields({ ...data, company_type: 'property_owner' }));
// Ban check with retry logic
const { withRetry } = await import('./retryHelpers');
breadcrumb.apiCall('profiles', 'SELECT');
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.');
}
// Upload images
let processedImages = data.images;
if (data.images?.uploaded && data.images.uploaded.length > 0) {
try {
@@ -2349,6 +2691,10 @@ export async function submitPropertyOwnerCreation(
}
}
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
.from('content_submissions')
.insert({
@@ -2383,6 +2729,28 @@ export async function submitPropertyOwnerCreation(
if (itemError) throw itemError;
return { submitted: true, submissionId: submissionData.id };
},
{
maxAttempts: 3,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying property owner submission', { attempt, delay });
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'property_owner' }
}));
},
shouldRetry: (error) => {
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;
}
return isRetryableError(error);
}
}
);
return result;
}
export async function submitPropertyOwnerUpdate(
@@ -2390,6 +2758,32 @@ export async function submitPropertyOwnerUpdate(
data: CompanyFormData,
userId: string
): Promise<{ submitted: boolean; submissionId: string }> {
// Rate limiting check
checkRateLimitOrThrow(userId, 'property_owner_update');
recordSubmissionAttempt(userId);
// Breadcrumb tracking
breadcrumb.userAction('Start property owner update', 'submitPropertyOwnerUpdate', { userId, companyId });
// Ban check with retry logic
const { withRetry } = await import('./retryHelpers');
breadcrumb.apiCall('profiles', 'SELECT');
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.');
}
const { data: existingCompany, error: fetchError } = await supabase
.from('companies')
.select('*')
@@ -2406,6 +2800,10 @@ export async function submitPropertyOwnerUpdate(
let processedImages = data.images;
// Submit with retry logic
breadcrumb.apiCall('content_submissions', 'INSERT');
const result = await withRetry(
async () => {
const { data: submissionData, error: submissionError } = await supabase
.from('content_submissions')
.insert({
@@ -2438,6 +2836,28 @@ export async function submitPropertyOwnerUpdate(
if (itemError) throw itemError;
return { submitted: true, submissionId: submissionData.id };
},
{
maxAttempts: 3,
onRetry: (attempt, error, delay) => {
logger.warn('Retrying property owner update', { attempt, delay });
window.dispatchEvent(new CustomEvent('submission-retry', {
detail: { attempt, maxAttempts: 3, delay, type: 'property_owner_update' }
}));
},
shouldRetry: (error) => {
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;
}
return isRetryableError(error);
}
}
);
return result;
}
/**