mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11:13 -05:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user