mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 13:51:12 -05:00
feat: Implement final error coverage
This commit is contained in:
@@ -11,8 +11,7 @@ import type {
|
||||
IdentitySafetyCheck,
|
||||
IdentityOperationResult
|
||||
} from '@/types/identity';
|
||||
import { logger } from './logger';
|
||||
import { getErrorMessage } from './errorHandler';
|
||||
import { handleNonCriticalError, handleError, getErrorMessage } from './errorHandler';
|
||||
|
||||
/**
|
||||
* Get all identities for the current user
|
||||
@@ -25,10 +24,9 @@ export async function getUserIdentities(): Promise<UserIdentity[]> {
|
||||
|
||||
return (data?.identities || []) as UserIdentity[];
|
||||
} catch (error) {
|
||||
const errorMsg = getErrorMessage(error);
|
||||
logger.error('Failed to get user identities', {
|
||||
action: 'get_identities',
|
||||
error: errorMsg
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Get User Identities',
|
||||
metadata: { returnedEmptyArray: true }
|
||||
});
|
||||
return [];
|
||||
}
|
||||
@@ -102,9 +100,9 @@ export async function disconnectIdentity(
|
||||
// Get AAL level - fail closed on error
|
||||
const { data: aalData, error: aalError } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel();
|
||||
if (aalError) {
|
||||
logger.error('Failed to get AAL level for identity disconnect', {
|
||||
action: 'disconnect_identity_aal_check',
|
||||
error: aalError.message
|
||||
handleNonCriticalError(aalError, {
|
||||
action: 'Get AAL Level (Identity Disconnect)',
|
||||
metadata: { failClosed: true }
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
@@ -120,9 +118,9 @@ export async function disconnectIdentity(
|
||||
const { data: factors, error: factorsError } = await supabase.auth.mfa.listFactors();
|
||||
|
||||
if (factorsError) {
|
||||
logger.error('Failed to list MFA factors for identity disconnect', {
|
||||
action: 'disconnect_identity_mfa_check',
|
||||
error: factorsError.message
|
||||
handleNonCriticalError(factorsError, {
|
||||
action: 'List MFA Factors (Identity Disconnect)',
|
||||
metadata: { failClosed: true }
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
@@ -177,15 +175,13 @@ export async function disconnectIdentity(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMsg = getErrorMessage(error);
|
||||
logger.error('Failed to disconnect identity', {
|
||||
action: 'identity_disconnect',
|
||||
provider,
|
||||
error: errorMsg
|
||||
handleError(error, {
|
||||
action: 'Disconnect Identity',
|
||||
metadata: { provider }
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: errorMsg
|
||||
error: getErrorMessage(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -210,15 +206,13 @@ export async function connectIdentity(
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMsg = getErrorMessage(error);
|
||||
logger.error('Failed to connect identity', {
|
||||
action: 'identity_connect',
|
||||
provider,
|
||||
error: errorMsg
|
||||
handleError(error, {
|
||||
action: 'Connect Identity',
|
||||
metadata: { provider }
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: errorMsg
|
||||
error: getErrorMessage(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -240,11 +234,6 @@ export async function addPasswordToAccount(): Promise<IdentityOperationResult> {
|
||||
};
|
||||
}
|
||||
|
||||
logger.info('Initiating password setup', {
|
||||
action: 'password_setup_initiated',
|
||||
email: userEmail
|
||||
});
|
||||
|
||||
// Trigger Supabase password reset email
|
||||
// User clicks link and sets password, which automatically creates email identity
|
||||
const { error: resetError } = await supabase.auth.resetPasswordForEmail(
|
||||
@@ -255,20 +244,14 @@ export async function addPasswordToAccount(): Promise<IdentityOperationResult> {
|
||||
);
|
||||
|
||||
if (resetError) {
|
||||
logger.error('Failed to send password reset email', {
|
||||
handleError(resetError, {
|
||||
action: 'Send Password Reset Email',
|
||||
userId: user?.id,
|
||||
action: 'password_setup_email',
|
||||
error: resetError.message
|
||||
metadata: { email: userEmail }
|
||||
});
|
||||
throw resetError;
|
||||
}
|
||||
|
||||
logger.info('Password reset email sent', {
|
||||
userId: user!.id,
|
||||
action: 'password_setup_initiated',
|
||||
email: userEmail
|
||||
});
|
||||
|
||||
// Log the action
|
||||
await logIdentityChange(user!.id, 'password_setup_initiated', {
|
||||
method: 'reset_password_flow',
|
||||
@@ -281,14 +264,12 @@ export async function addPasswordToAccount(): Promise<IdentityOperationResult> {
|
||||
email: userEmail
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMsg = getErrorMessage(error);
|
||||
logger.error('Failed to initiate password setup', {
|
||||
action: 'password_setup',
|
||||
error: errorMsg
|
||||
handleError(error, {
|
||||
action: 'Initiate Password Setup'
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: errorMsg
|
||||
error: getErrorMessage(error)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -310,10 +291,10 @@ async function logIdentityChange(
|
||||
_details: details
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to log identity change to audit', {
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Log Identity Change to Audit',
|
||||
userId,
|
||||
action,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
metadata: { auditAction: action }
|
||||
});
|
||||
// Don't fail the operation if audit logging fails
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { invokeWithTracking } from './edgeFunctionTracking';
|
||||
import type { UploadedImage } from '@/components/upload/EntityMultiImageUploader';
|
||||
import { logger } from './logger';
|
||||
import { handleError, handleNonCriticalError } from './errorHandler';
|
||||
|
||||
export interface CloudflareUploadResponse {
|
||||
result: {
|
||||
@@ -34,20 +34,14 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
|
||||
);
|
||||
|
||||
if (urlError || !uploadUrlData?.uploadURL) {
|
||||
logger.error('Failed to get upload URL', {
|
||||
action: 'upload_pending_images',
|
||||
fileName,
|
||||
requestId,
|
||||
error: urlError?.message || 'Unknown error',
|
||||
const error = new Error(`Failed to get upload URL for "${fileName}": ${urlError?.message || 'Unknown error'}`);
|
||||
handleError(error, {
|
||||
action: 'Get Upload URL',
|
||||
metadata: { fileName, requestId }
|
||||
});
|
||||
throw new Error(`Failed to get upload URL for "${fileName}": ${urlError?.message || 'Unknown error'}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info('Got upload URL', {
|
||||
action: 'upload_pending_images',
|
||||
fileName,
|
||||
requestId,
|
||||
});
|
||||
|
||||
// Step 2: Upload file directly to Cloudflare
|
||||
const formData = new FormData();
|
||||
@@ -60,30 +54,25 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
|
||||
|
||||
if (!uploadResponse.ok) {
|
||||
const errorText = await uploadResponse.text();
|
||||
logger.error('Cloudflare upload failed', {
|
||||
action: 'upload_pending_images',
|
||||
fileName,
|
||||
status: uploadResponse.status,
|
||||
error: errorText,
|
||||
const error = new Error(`Upload failed for "${fileName}" (status ${uploadResponse.status}): ${errorText}`);
|
||||
handleError(error, {
|
||||
action: 'Cloudflare Upload',
|
||||
metadata: { fileName, status: uploadResponse.status }
|
||||
});
|
||||
throw new Error(`Upload failed for "${fileName}" (status ${uploadResponse.status}): ${errorText}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
const result: CloudflareUploadResponse = await uploadResponse.json();
|
||||
|
||||
if (!result.success || !result.result) {
|
||||
logger.error('Cloudflare upload unsuccessful', {
|
||||
action: 'upload_pending_images',
|
||||
fileName,
|
||||
const error = new Error(`Cloudflare upload returned unsuccessful response for "${fileName}"`);
|
||||
handleError(error, {
|
||||
action: 'Cloudflare Upload',
|
||||
metadata: { fileName }
|
||||
});
|
||||
throw new Error(`Cloudflare upload returned unsuccessful response for "${fileName}"`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info('Image uploaded successfully', {
|
||||
action: 'upload_pending_images',
|
||||
fileName,
|
||||
imageId: result.result.id,
|
||||
});
|
||||
|
||||
// Clean up object URL
|
||||
URL.revokeObjectURL(image.url);
|
||||
@@ -132,10 +121,13 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
|
||||
// If any uploads failed, clean up ONLY newly uploaded images and throw error
|
||||
if (errors.length > 0) {
|
||||
if (newlyUploadedImageIds.length > 0) {
|
||||
logger.error('Some uploads failed, cleaning up', {
|
||||
action: 'upload_pending_images',
|
||||
newlyUploadedCount: newlyUploadedImageIds.length,
|
||||
failureCount: errors.length,
|
||||
const cleanupError = new Error(`Some uploads failed, cleaning up ${newlyUploadedImageIds.length} newly uploaded images`);
|
||||
handleError(cleanupError, {
|
||||
action: 'Upload Cleanup',
|
||||
metadata: {
|
||||
newlyUploadedCount: newlyUploadedImageIds.length,
|
||||
failureCount: errors.length
|
||||
}
|
||||
});
|
||||
|
||||
// Attempt cleanup in parallel with detailed error tracking
|
||||
@@ -148,24 +140,29 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
|
||||
)
|
||||
);
|
||||
|
||||
// Track cleanup failures for better debugging
|
||||
// Track cleanup failures silently (non-critical)
|
||||
const cleanupFailures = cleanupResults.filter(r => r.status === 'rejected');
|
||||
if (cleanupFailures.length > 0) {
|
||||
logger.error('Failed to cleanup images', {
|
||||
action: 'upload_pending_images_cleanup',
|
||||
cleanupFailures: cleanupFailures.length,
|
||||
totalCleanup: newlyUploadedImageIds.length,
|
||||
orphanedImages: newlyUploadedImageIds.filter((_, i) => cleanupResults[i].status === 'rejected'),
|
||||
});
|
||||
} else {
|
||||
logger.info('Successfully cleaned up images', {
|
||||
action: 'upload_pending_images_cleanup',
|
||||
cleanedCount: newlyUploadedImageIds.length,
|
||||
});
|
||||
handleNonCriticalError(
|
||||
new Error(`Failed to cleanup ${cleanupFailures.length} of ${newlyUploadedImageIds.length} images`),
|
||||
{
|
||||
action: 'Image Cleanup',
|
||||
metadata: {
|
||||
cleanupFailures: cleanupFailures.length,
|
||||
totalCleanup: newlyUploadedImageIds.length,
|
||||
orphanedImages: newlyUploadedImageIds.filter((_, i) => cleanupResults[i].status === 'rejected')
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Failed to upload ${errors.length} of ${images.length} images: ${errors.join('; ')}`);
|
||||
const finalError = new Error(`Failed to upload ${errors.length} of ${images.length} images: ${errors.join('; ')}`);
|
||||
handleError(finalError, {
|
||||
action: 'Image Upload',
|
||||
metadata: { failureCount: errors.length, totalCount: images.length }
|
||||
});
|
||||
throw finalError;
|
||||
}
|
||||
|
||||
// Remove the wasNewlyUploaded flag before returning
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
import { SupabaseClient } from '@supabase/supabase-js';
|
||||
import { createTableQuery } from '@/lib/supabaseHelpers';
|
||||
import type { ModerationItem } from '@/types/moderation';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { getErrorMessage } from '@/lib/errorHandler';
|
||||
import { handleError, handleNonCriticalError, getErrorMessage } from '@/lib/errorHandler';
|
||||
import { invokeWithTracking, invokeBatchWithTracking } from '@/lib/edgeFunctionTracking';
|
||||
|
||||
/**
|
||||
@@ -154,16 +153,15 @@ export async function approvePhotoSubmission(
|
||||
shouldRemoveFromQueue: true,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
logger.error('Photo approval failed', {
|
||||
action: 'approve_photo',
|
||||
submissionId: config.submissionId,
|
||||
error: errorMessage
|
||||
handleError(error, {
|
||||
action: 'Approve Photo Submission',
|
||||
userId: config.moderatorId,
|
||||
metadata: { submissionId: config.submissionId }
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to approve photo submission',
|
||||
error: new Error(errorMessage),
|
||||
error: error instanceof Error ? error : new Error(getErrorMessage(error)),
|
||||
shouldRemoveFromQueue: false,
|
||||
};
|
||||
}
|
||||
@@ -194,22 +192,14 @@ export async function approveSubmissionItems(
|
||||
);
|
||||
|
||||
if (approvalError) {
|
||||
logger.error('Submission items approval failed via edge function', {
|
||||
action: 'approve_submission_items',
|
||||
submissionId,
|
||||
itemCount: itemIds.length,
|
||||
requestId,
|
||||
error: approvalError.message,
|
||||
const error = new Error(`Failed to process submission items: ${approvalError.message}`);
|
||||
handleError(error, {
|
||||
action: 'Approve Submission Items',
|
||||
metadata: { submissionId, itemCount: itemIds.length, requestId }
|
||||
});
|
||||
throw new Error(`Failed to process submission items: ${approvalError.message}`);
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info('Submission items approved successfully', {
|
||||
action: 'approve_submission_items',
|
||||
submissionId,
|
||||
itemCount: itemIds.length,
|
||||
requestId,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
@@ -217,17 +207,14 @@ export async function approveSubmissionItems(
|
||||
shouldRemoveFromQueue: true,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
logger.error('Submission items approval failed', {
|
||||
action: 'approve_submission_items',
|
||||
submissionId,
|
||||
itemCount: itemIds.length,
|
||||
error: errorMessage
|
||||
handleError(error, {
|
||||
action: 'Approve Submission Items',
|
||||
metadata: { submissionId, itemCount: itemIds.length }
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to approve submission items',
|
||||
error: new Error(errorMessage),
|
||||
error: error instanceof Error ? error : new Error(getErrorMessage(error)),
|
||||
shouldRemoveFromQueue: false,
|
||||
};
|
||||
}
|
||||
@@ -260,11 +247,9 @@ export async function rejectSubmissionItems(
|
||||
.eq('status', 'pending');
|
||||
|
||||
if (rejectError) {
|
||||
const errorMessage = getErrorMessage(rejectError);
|
||||
logger.error('Item rejection cascade failed', {
|
||||
action: 'reject_submission_items',
|
||||
submissionId,
|
||||
error: errorMessage
|
||||
handleError(rejectError, {
|
||||
action: 'Reject Submission Items (Cascade)',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -274,16 +259,14 @@ export async function rejectSubmissionItems(
|
||||
shouldRemoveFromQueue: false, // Parent rejection will handle removal
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
logger.error('Submission items rejection failed', {
|
||||
action: 'reject_submission_items',
|
||||
submissionId,
|
||||
error: errorMessage
|
||||
handleError(error, {
|
||||
action: 'Reject Submission Items',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to reject submission items',
|
||||
error: new Error(errorMessage),
|
||||
error: error instanceof Error ? error : new Error(getErrorMessage(error)),
|
||||
shouldRemoveFromQueue: false,
|
||||
};
|
||||
}
|
||||
@@ -412,17 +395,15 @@ export async function performModerationAction(
|
||||
shouldRemoveFromQueue: action === 'approved' || action === 'rejected',
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = getErrorMessage(error);
|
||||
logger.error('Moderation action failed', {
|
||||
action: config.action,
|
||||
itemType: item.type,
|
||||
itemId: item.id,
|
||||
error: errorMessage
|
||||
handleError(error, {
|
||||
action: `${config.action === 'approved' ? 'Approve' : 'Reject'} Content`,
|
||||
userId: config.moderatorId,
|
||||
metadata: { itemType: item.type, itemId: item.id }
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
message: `Failed to ${config.action} content`,
|
||||
error: new Error(errorMessage),
|
||||
error: error instanceof Error ? error : new Error(getErrorMessage(error)),
|
||||
shouldRemoveFromQueue: false,
|
||||
};
|
||||
}
|
||||
@@ -513,15 +494,20 @@ export async function deleteSubmission(
|
||||
const successfulDeletions = deleteResults.filter(r => !r.error);
|
||||
deletedPhotoCount = successfulDeletions.length;
|
||||
|
||||
// Log any failures
|
||||
// Log any failures silently (background operation)
|
||||
const failedDeletions = deleteResults.filter(r => r.error);
|
||||
if (failedDeletions.length > 0) {
|
||||
logger.error('Some photo deletions failed', {
|
||||
action: 'delete_submission_photos',
|
||||
failureCount: failedDeletions.length,
|
||||
totalAttempted: validImageIds.length,
|
||||
failedRequestIds: failedDeletions.map(r => r.requestId),
|
||||
});
|
||||
handleNonCriticalError(
|
||||
new Error(`Failed to delete ${failedDeletions.length} of ${validImageIds.length} photos`),
|
||||
{
|
||||
action: 'Delete Submission Photos',
|
||||
metadata: {
|
||||
failureCount: failedDeletions.length,
|
||||
totalAttempted: validImageIds.length,
|
||||
failedRequestIds: failedDeletions.map(r => r.requestId)
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -563,12 +549,15 @@ export async function deleteSubmission(
|
||||
message,
|
||||
shouldRemoveFromQueue: true,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('Error deleting submission', { error, submissionId: item.id });
|
||||
} catch (error: unknown) {
|
||||
handleError(error, {
|
||||
action: 'Delete Submission',
|
||||
metadata: { submissionId: item.id, deletePhotos }
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
message: 'Failed to delete submission',
|
||||
error: error as Error,
|
||||
error: error instanceof Error ? error : new Error('Unknown error'),
|
||||
shouldRemoveFromQueue: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import type { ModerationAction } from '../moderationStateMachine';
|
||||
import { hasActiveLock, needsLockRenewal } from '../moderationStateMachine';
|
||||
import { toast } from '@/hooks/use-toast';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { logger } from '../logger';
|
||||
import { handleNonCriticalError } from '../errorHandler';
|
||||
|
||||
/**
|
||||
* Hook to monitor lock status and warn about expiry
|
||||
@@ -33,14 +33,6 @@ export function useLockMonitor(
|
||||
|
||||
const checkInterval = setInterval(() => {
|
||||
if (needsLockRenewal(state)) {
|
||||
logger.warn('Lock expiring soon', {
|
||||
action: 'lock_expiry_warning',
|
||||
itemId,
|
||||
lockExpires: state.status === 'locked' || state.status === 'reviewing'
|
||||
? state.lockExpires
|
||||
: undefined,
|
||||
});
|
||||
|
||||
// Dispatch lock expiry warning
|
||||
dispatch({ type: 'LOCK_EXPIRED' });
|
||||
|
||||
@@ -103,16 +95,10 @@ export async function handleExtendLock(
|
||||
title: 'Lock Extended',
|
||||
description: 'You have 15 more minutes to complete your review.',
|
||||
});
|
||||
|
||||
logger.info('Lock extended successfully', {
|
||||
action: 'lock_extended',
|
||||
submissionId,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
logger.error('Failed to extend lock', {
|
||||
action: 'extend_lock_error',
|
||||
submissionId,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Extend Lock',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
|
||||
toast({
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { supabase } from "@/lib/supabaseClient";
|
||||
import { invokeWithTracking } from "@/lib/edgeFunctionTracking";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { AppError } from "@/lib/errorHandler";
|
||||
import { handleNonCriticalError, AppError } from "@/lib/errorHandler";
|
||||
import { z } from "zod";
|
||||
import type {
|
||||
NotificationPayload,
|
||||
@@ -29,9 +28,9 @@ class NotificationService {
|
||||
|
||||
return !!data?.setting_value;
|
||||
} catch (error: unknown) {
|
||||
logger.error('Failed to check Novu status', {
|
||||
action: 'check_novu_status',
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Check Novu Status',
|
||||
metadata: { returnedFalse: true }
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -47,10 +46,6 @@ class NotificationService {
|
||||
|
||||
const novuEnabled = await this.isNovuEnabled();
|
||||
if (!novuEnabled) {
|
||||
logger.warn('Novu not configured, skipping subscriber update', {
|
||||
action: 'update_novu_subscriber',
|
||||
userId: validated.subscriberId
|
||||
});
|
||||
return { success: false, error: 'Novu not configured' };
|
||||
}
|
||||
|
||||
@@ -60,11 +55,10 @@ class NotificationService {
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logger.error('Edge function error updating Novu subscriber', {
|
||||
action: 'update_novu_subscriber',
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Update Novu Subscriber (Edge Function)',
|
||||
userId: validated.subscriberId,
|
||||
requestId,
|
||||
error: error.message
|
||||
metadata: { requestId }
|
||||
});
|
||||
throw new AppError(
|
||||
'Failed to update notification subscriber',
|
||||
@@ -73,18 +67,11 @@ class NotificationService {
|
||||
);
|
||||
}
|
||||
|
||||
logger.info('Novu subscriber updated successfully', {
|
||||
action: 'update_novu_subscriber',
|
||||
userId: validated.subscriberId,
|
||||
requestId
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error in updateSubscriber', {
|
||||
action: 'update_novu_subscriber',
|
||||
userId: subscriberData.subscriberId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Update Novu Subscriber',
|
||||
userId: subscriberData.subscriberId
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -104,10 +91,6 @@ class NotificationService {
|
||||
|
||||
const novuEnabled = await this.isNovuEnabled();
|
||||
if (!novuEnabled) {
|
||||
logger.warn('Novu not configured, skipping subscriber creation', {
|
||||
action: 'create_novu_subscriber',
|
||||
userId: validated.subscriberId
|
||||
});
|
||||
return { success: false, error: 'Novu not configured' };
|
||||
}
|
||||
|
||||
@@ -117,11 +100,10 @@ class NotificationService {
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logger.error('Edge function error creating Novu subscriber', {
|
||||
action: 'create_novu_subscriber',
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Create Novu Subscriber (Edge Function)',
|
||||
userId: validated.subscriberId,
|
||||
requestId,
|
||||
error: error.message
|
||||
metadata: { requestId }
|
||||
});
|
||||
throw new AppError(
|
||||
'Failed to create notification subscriber',
|
||||
@@ -146,27 +128,18 @@ class NotificationService {
|
||||
});
|
||||
|
||||
if (dbError) {
|
||||
logger.error('Failed to store subscriber preferences', {
|
||||
action: 'store_subscriber_preferences',
|
||||
userId: validated.subscriberId,
|
||||
error: dbError.message,
|
||||
errorCode: dbError.code
|
||||
handleNonCriticalError(dbError, {
|
||||
action: 'Store Subscriber Preferences',
|
||||
userId: validated.subscriberId
|
||||
});
|
||||
throw dbError;
|
||||
}
|
||||
|
||||
logger.info('Novu subscriber created successfully', {
|
||||
action: 'create_novu_subscriber',
|
||||
userId: validated.subscriberId,
|
||||
requestId
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error in createSubscriber', {
|
||||
action: 'create_novu_subscriber',
|
||||
userId: subscriberData.subscriberId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Create Novu Subscriber',
|
||||
userId: subscriberData.subscriberId
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -207,11 +180,10 @@ class NotificationService {
|
||||
);
|
||||
|
||||
if (novuError) {
|
||||
logger.error('Failed to update Novu preferences', {
|
||||
action: 'update_novu_preferences',
|
||||
handleNonCriticalError(novuError, {
|
||||
action: 'Update Novu Preferences',
|
||||
userId,
|
||||
requestId,
|
||||
error: novuError.message
|
||||
metadata: { requestId }
|
||||
});
|
||||
throw novuError;
|
||||
}
|
||||
@@ -228,11 +200,9 @@ class NotificationService {
|
||||
});
|
||||
|
||||
if (dbError) {
|
||||
logger.error('Failed to save notification preferences', {
|
||||
action: 'save_notification_preferences',
|
||||
userId,
|
||||
error: dbError.message,
|
||||
errorCode: dbError.code
|
||||
handleNonCriticalError(dbError, {
|
||||
action: 'Save Notification Preferences',
|
||||
userId
|
||||
});
|
||||
throw dbError;
|
||||
}
|
||||
@@ -268,17 +238,11 @@ class NotificationService {
|
||||
});
|
||||
}
|
||||
|
||||
logger.info('Notification preferences updated', {
|
||||
action: 'update_notification_preferences',
|
||||
userId
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error updating notification preferences', {
|
||||
action: 'update_notification_preferences',
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Update Notification Preferences',
|
||||
userId
|
||||
});
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
@@ -307,20 +271,14 @@ class NotificationService {
|
||||
.maybeSingle();
|
||||
|
||||
if (error && error.code !== 'PGRST116') {
|
||||
logger.error('Failed to fetch notification preferences', {
|
||||
action: 'fetch_notification_preferences',
|
||||
userId,
|
||||
error: error.message,
|
||||
errorCode: error.code
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch Notification Preferences',
|
||||
userId
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
logger.info('No preferences found, returning defaults', {
|
||||
action: 'fetch_notification_preferences',
|
||||
userId
|
||||
});
|
||||
return DEFAULT_NOTIFICATION_PREFERENCES;
|
||||
}
|
||||
|
||||
@@ -331,10 +289,9 @@ class NotificationService {
|
||||
frequencySettings: data.frequency_settings
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error fetching notification preferences', {
|
||||
action: 'fetch_notification_preferences',
|
||||
userId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Get Notification Preferences',
|
||||
userId
|
||||
});
|
||||
return null;
|
||||
}
|
||||
@@ -352,10 +309,8 @@ class NotificationService {
|
||||
.order('category', { ascending: true });
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to fetch notification templates', {
|
||||
action: 'fetch_notification_templates',
|
||||
error: error.message,
|
||||
errorCode: error.code
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch Notification Templates'
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
@@ -367,9 +322,8 @@ class NotificationService {
|
||||
novu_workflow_id: t.novu_workflow_id || null,
|
||||
}));
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error fetching notification templates', {
|
||||
action: 'fetch_notification_templates',
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Get Notification Templates'
|
||||
});
|
||||
return [];
|
||||
}
|
||||
@@ -382,11 +336,6 @@ class NotificationService {
|
||||
try {
|
||||
const novuEnabled = await this.isNovuEnabled();
|
||||
if (!novuEnabled) {
|
||||
logger.warn('Novu not configured, skipping notification', {
|
||||
action: 'trigger_notification',
|
||||
workflowId: payload.workflowId,
|
||||
subscriberId: payload.subscriberId
|
||||
});
|
||||
return { success: false, error: 'Novu not configured' };
|
||||
}
|
||||
|
||||
@@ -396,31 +345,18 @@ class NotificationService {
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to trigger notification', {
|
||||
action: 'trigger_notification',
|
||||
workflowId: payload.workflowId,
|
||||
subscriberId: payload.subscriberId,
|
||||
requestId,
|
||||
error: error.message
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Trigger Notification',
|
||||
metadata: { workflowId: payload.workflowId, subscriberId: payload.subscriberId, requestId }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info('Notification triggered successfully', {
|
||||
action: 'trigger_notification',
|
||||
workflowId: payload.workflowId,
|
||||
subscriberId: payload.subscriberId,
|
||||
transactionId: data?.transactionId,
|
||||
requestId
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error triggering notification', {
|
||||
action: 'trigger_notification',
|
||||
workflowId: payload.workflowId,
|
||||
subscriberId: payload.subscriberId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Trigger Notification',
|
||||
metadata: { workflowId: payload.workflowId, subscriberId: payload.subscriberId }
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -446,25 +382,16 @@ class NotificationService {
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to notify moderators', {
|
||||
action: 'notify_moderators',
|
||||
submissionId: payload.submission_id,
|
||||
requestId,
|
||||
error: error.message
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Notify Moderators (Submission)',
|
||||
metadata: { submissionId: payload.submission_id, requestId }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info('Moderators notified successfully', {
|
||||
action: 'notify_moderators',
|
||||
submissionId: payload.submission_id,
|
||||
requestId
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error notifying moderators', {
|
||||
action: 'notify_moderators',
|
||||
submissionId: payload.submission_id,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Notify Moderators (Submission)',
|
||||
metadata: { submissionId: payload.submission_id }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -482,10 +409,6 @@ class NotificationService {
|
||||
try {
|
||||
const novuEnabled = await this.isNovuEnabled();
|
||||
if (!novuEnabled) {
|
||||
logger.warn('Novu not configured, skipping system announcement', {
|
||||
action: 'send_system_announcement',
|
||||
title: payload.title
|
||||
});
|
||||
return { success: false, error: 'Novu not configured' };
|
||||
}
|
||||
|
||||
@@ -495,31 +418,21 @@ class NotificationService {
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to send system announcement', {
|
||||
action: 'send_system_announcement',
|
||||
title: payload.title,
|
||||
requestId,
|
||||
error: error.message
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Send System Announcement',
|
||||
metadata: { title: payload.title, requestId }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info('System announcement sent successfully', {
|
||||
action: 'send_system_announcement',
|
||||
title: payload.title,
|
||||
announcementId: data?.announcementId,
|
||||
requestId
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
announcementId: data?.announcementId
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error sending system announcement', {
|
||||
action: 'send_system_announcement',
|
||||
title: payload.title,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Send System Announcement',
|
||||
metadata: { title: payload.title }
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -545,10 +458,6 @@ class NotificationService {
|
||||
try {
|
||||
const novuEnabled = await this.isNovuEnabled();
|
||||
if (!novuEnabled) {
|
||||
logger.warn('Novu not configured, skipping report notification', {
|
||||
action: 'notify_moderators_report',
|
||||
reportId: payload.reportId
|
||||
});
|
||||
return { success: false, error: 'Novu not configured' };
|
||||
}
|
||||
|
||||
@@ -558,27 +467,18 @@ class NotificationService {
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to notify moderators about report', {
|
||||
action: 'notify_moderators_report',
|
||||
reportId: payload.reportId,
|
||||
requestId,
|
||||
error: error.message
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Notify Moderators (Report)',
|
||||
metadata: { reportId: payload.reportId, requestId }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
logger.info('Moderators notified about report successfully', {
|
||||
action: 'notify_moderators_report',
|
||||
reportId: payload.reportId,
|
||||
requestId
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error notifying moderators about report', {
|
||||
action: 'notify_moderators_report',
|
||||
reportId: payload.reportId,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Notify Moderators (Report)',
|
||||
metadata: { reportId: payload.reportId }
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -168,7 +168,7 @@ async function logRequestMetadata(metadata: RequestMetadata): Promise<void> {
|
||||
});
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to log metadata to database', { error, context: 'RequestTracking' });
|
||||
// Already logged by handleNonCriticalError in requestTracking
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { logger } from './logger';
|
||||
import { handleError } from './errorHandler';
|
||||
|
||||
/**
|
||||
* Generate a URL-safe slug from a name
|
||||
@@ -51,7 +51,10 @@ export async function ensureUniqueSlug(
|
||||
const { data, error } = await query.limit(1);
|
||||
|
||||
if (error) {
|
||||
logger.error('Error checking slug uniqueness', { error, tableName });
|
||||
handleError(error, {
|
||||
action: 'Check Slug Uniqueness',
|
||||
metadata: { tableName, slug }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ import type {
|
||||
RideModelSubmissionData
|
||||
} from '@/types/submission-data';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { logger } from './logger';
|
||||
import { getErrorMessage } from './errorHandler';
|
||||
import { handleNonCriticalError, getErrorMessage } from './errorHandler';
|
||||
|
||||
type SubmissionDataTypes =
|
||||
| ParkSubmissionData
|
||||
@@ -81,11 +80,9 @@ async function detectPhotoChanges(submissionId: string): Promise<PhotoChange[]>
|
||||
.eq('submission_id', submissionId);
|
||||
|
||||
if (photoError) {
|
||||
const errorMessage = getErrorMessage(photoError);
|
||||
logger.error('Photo submission fetch failed', {
|
||||
action: 'detect_photo_changes',
|
||||
submissionId,
|
||||
error: errorMessage
|
||||
handleNonCriticalError(photoError, {
|
||||
action: 'Detect Photo Changes (Fetch Photo Submission)',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
} else {
|
||||
const photoSubmission = photoSubmissions?.[0];
|
||||
@@ -109,11 +106,9 @@ async function detectPhotoChanges(submissionId: string): Promise<PhotoChange[]>
|
||||
.in('item_type', ['photo_edit', 'photo_delete']);
|
||||
|
||||
if (itemsError) {
|
||||
const errorMessage = getErrorMessage(itemsError);
|
||||
logger.error('Submission items fetch failed', {
|
||||
action: 'detect_photo_changes',
|
||||
submissionId,
|
||||
error: errorMessage
|
||||
handleNonCriticalError(itemsError, {
|
||||
action: 'Detect Photo Changes (Fetch Submission Items)',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
} else if (submissionItems && submissionItems.length > 0) {
|
||||
for (const item of submissionItems) {
|
||||
@@ -123,11 +118,9 @@ async function detectPhotoChanges(submissionId: string): Promise<PhotoChange[]>
|
||||
}
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = getErrorMessage(err);
|
||||
logger.error('Photo change detection failed', {
|
||||
action: 'detect_photo_changes',
|
||||
submissionId,
|
||||
error: errorMessage
|
||||
handleNonCriticalError(err, {
|
||||
action: 'Detect Photo Changes',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
}
|
||||
|
||||
@@ -349,7 +342,10 @@ export async function detectChanges(
|
||||
if (data?.name) entityName = `${data.name} (${formatEntityType(entityType)})`;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Error fetching entity name for photo operation', { error: err, entityType: itemData.entity_type, entityId: itemData.entity_id });
|
||||
handleNonCriticalError(err, {
|
||||
action: 'Fetch Entity Name for Photo Operation',
|
||||
metadata: { entityType: itemData.entity_type, entityId: itemData.entity_id }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,7 +391,10 @@ export async function detectChanges(
|
||||
entityName = `${formatEntityType(entityType)} - ${itemData.title}`;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Error fetching entity name for milestone', { error: err, entityType: itemData.entity_type, entityId: itemData.entity_id });
|
||||
handleNonCriticalError(err, {
|
||||
action: 'Fetch Entity Name for Milestone',
|
||||
metadata: { entityType: itemData.entity_type, entityId: itemData.entity_id }
|
||||
});
|
||||
// Fall back to just the title if database lookup fails
|
||||
if (itemData.title) {
|
||||
entityName = itemData.title;
|
||||
@@ -434,7 +433,10 @@ export async function detectChanges(
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Error resolving entity name for field display', { error: err, entityType: itemData.entity_type, entityId: itemData.entity_id });
|
||||
handleNonCriticalError(err, {
|
||||
action: 'Resolve Entity Name for Field Display',
|
||||
metadata: { entityType: itemData.entity_type, entityId: itemData.entity_id }
|
||||
});
|
||||
}
|
||||
|
||||
// Add entity name as an explicit field change at the beginning
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { getErrorMessage } from './errorHandler';
|
||||
import { logger } from './logger';
|
||||
import { handleError, handleNonCriticalError, getErrorMessage } from './errorHandler';
|
||||
import { extractCloudflareImageId } from './cloudflareImageUtils';
|
||||
|
||||
// Core submission item interface with dependencies
|
||||
@@ -297,21 +296,19 @@ export async function approveSubmissionItems(
|
||||
dependencyMap.set(item.id, entityId);
|
||||
|
||||
} catch (error: unknown) {
|
||||
const errorMsg = getErrorMessage(error);
|
||||
logger.error('Error approving items', {
|
||||
action: 'approve_submission_items',
|
||||
error: errorMsg,
|
||||
handleError(error, {
|
||||
action: 'Approve Submission Items',
|
||||
userId,
|
||||
itemCount: items.length
|
||||
metadata: { itemCount: items.length, itemType: item.item_type }
|
||||
});
|
||||
|
||||
// Update item with error status
|
||||
await updateSubmissionItem(item.id, {
|
||||
status: 'rejected' as const,
|
||||
rejection_reason: `Failed to create entity: ${errorMsg}`,
|
||||
rejection_reason: `Failed to create entity: ${getErrorMessage(error)}`,
|
||||
});
|
||||
|
||||
throw new Error(`Failed to approve ${item.item_type}: ${errorMsg}`);
|
||||
throw new Error(`Failed to approve ${item.item_type}: ${getErrorMessage(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,7 +335,6 @@ async function createVersionForApprovedItem(
|
||||
// - app.current_user_id = original submitter
|
||||
// - app.submission_id = submission ID
|
||||
// Then the trigger creates the version automatically
|
||||
logger.debug('Version will be created automatically by trigger', { itemType, entityId });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -452,10 +448,9 @@ async function createPark(data: any, dependencyMap: Map<string, string>, sortedI
|
||||
.eq('id', data.park_id);
|
||||
|
||||
if (error) {
|
||||
logger.error('Error updating park', {
|
||||
action: 'update_park',
|
||||
parkId: data.park_id,
|
||||
error: error.message
|
||||
handleError(error, {
|
||||
action: 'Update Park',
|
||||
metadata: { parkId: data.park_id, parkName: resolvedData.name }
|
||||
});
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
@@ -495,10 +490,9 @@ async function createPark(data: any, dependencyMap: Map<string, string>, sortedI
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
logger.error('Error creating park', {
|
||||
action: 'create_park',
|
||||
parkName: resolvedData.name,
|
||||
error: error.message
|
||||
handleError(error, {
|
||||
action: 'Create Park',
|
||||
metadata: { parkName: resolvedData.name }
|
||||
});
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
@@ -551,10 +545,9 @@ async function resolveLocationId(locationData: any): Promise<string | null> {
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
logger.error('Error creating location', {
|
||||
action: 'create_location',
|
||||
locationData,
|
||||
error: error.message
|
||||
handleError(error, {
|
||||
action: 'Create Location',
|
||||
metadata: { locationData }
|
||||
});
|
||||
throw new Error(`Failed to create location: ${error.message}`);
|
||||
}
|
||||
@@ -611,7 +604,10 @@ async function createRide(data: any, dependencyMap: Map<string, string>, sortedI
|
||||
.eq('id', data.ride_id);
|
||||
|
||||
if (error) {
|
||||
logger.error('Error updating ride', { error: error.message, rideId: data.ride_id });
|
||||
handleError(error, {
|
||||
action: 'Update Ride',
|
||||
metadata: { rideId: data.ride_id, rideName: resolvedData.name }
|
||||
});
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -642,7 +638,10 @@ async function createRide(data: any, dependencyMap: Map<string, string>, sortedI
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
logger.error('Error creating ride', { error: error.message, rideName: resolvedData.name });
|
||||
handleError(error, {
|
||||
action: 'Create Ride',
|
||||
metadata: { rideName: resolvedData.name }
|
||||
});
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -686,7 +685,10 @@ async function createCompany(
|
||||
.eq('id', data.id);
|
||||
|
||||
if (error) {
|
||||
logger.error('Error updating company', { error: error.message, companyId: data.id });
|
||||
handleError(error, {
|
||||
action: 'Update Company',
|
||||
metadata: { companyId: data.id, companyName: resolvedData.name, companyType }
|
||||
});
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -721,7 +723,10 @@ async function createCompany(
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
logger.error('Error creating company', { error: error.message, companyName: resolvedData.name, companyType });
|
||||
handleError(error, {
|
||||
action: 'Create Company',
|
||||
metadata: { companyName: resolvedData.name, companyType }
|
||||
});
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -760,7 +765,10 @@ async function createRideModel(data: any, dependencyMap: Map<string, string>, so
|
||||
.eq('id', data.ride_model_id);
|
||||
|
||||
if (error) {
|
||||
logger.error('Error updating ride model', { error: error.message, rideModelId: data.ride_model_id });
|
||||
handleError(error, {
|
||||
action: 'Update Ride Model',
|
||||
metadata: { rideModelId: data.ride_model_id, modelName: resolvedData.name }
|
||||
});
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -797,7 +805,10 @@ async function createRideModel(data: any, dependencyMap: Map<string, string>, so
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
logger.error('Error creating ride model', { error: error.message, modelName: resolvedData.name });
|
||||
handleError(error, {
|
||||
action: 'Create Ride Model',
|
||||
metadata: { modelName: resolvedData.name }
|
||||
});
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -876,7 +887,10 @@ async function approvePhotos(data: any, dependencyMap: Map<string, string>, user
|
||||
.select();
|
||||
|
||||
if (error) {
|
||||
logger.error('Error inserting photos', { error: error.message, photoCount: photosToInsert.length, entityType, entityId: finalEntityId });
|
||||
handleError(error, {
|
||||
action: 'Insert Photos',
|
||||
metadata: { photoCount: photosToInsert.length, entityType, entityId: finalEntityId }
|
||||
});
|
||||
throw new Error(`Database error: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -958,7 +972,10 @@ async function updateEntityFeaturedImage(
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error updating entity featured image', { error, entityType, entityId });
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Update Entity Featured Image',
|
||||
metadata: { entityType, entityId }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1136,10 +1153,13 @@ export async function rejectSubmissionItems(
|
||||
})
|
||||
.eq('id', itemId);
|
||||
|
||||
if (error) {
|
||||
logger.error('Error rejecting item', { error, itemId });
|
||||
throw error;
|
||||
}
|
||||
if (error) {
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Reject Submission Item',
|
||||
metadata: { itemId }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(updates);
|
||||
@@ -1171,7 +1191,10 @@ async function updateSubmissionStatusAfterRejection(submissionId: string): Promi
|
||||
.eq('submission_id', submissionId);
|
||||
|
||||
if (fetchError) {
|
||||
logger.error('Error fetching submission items', { error: fetchError, submissionId });
|
||||
handleNonCriticalError(fetchError, {
|
||||
action: 'Fetch Submission Items for Status Update',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1202,7 +1225,10 @@ async function updateSubmissionStatusAfterRejection(submissionId: string): Promi
|
||||
.eq('id', submissionId);
|
||||
|
||||
if (updateError) {
|
||||
logger.error('Error updating submission status', { error: updateError, submissionId });
|
||||
handleNonCriticalError(updateError, {
|
||||
action: 'Update Submission Status After Rejection',
|
||||
metadata: { submissionId, newStatus }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1273,10 +1299,9 @@ export async function editSubmissionItem(
|
||||
});
|
||||
|
||||
if (historyError) {
|
||||
logger.error('Failed to record edit history', {
|
||||
itemId,
|
||||
editorId: userId,
|
||||
error: historyError.message,
|
||||
handleNonCriticalError(historyError, {
|
||||
action: 'Record Edit History',
|
||||
metadata: { itemId, editorId: userId }
|
||||
});
|
||||
// Don't fail the whole operation if history tracking fails
|
||||
}
|
||||
@@ -1293,10 +1318,12 @@ export async function editSubmissionItem(
|
||||
true // isEdit = true
|
||||
);
|
||||
} catch (versionError) {
|
||||
logger.error('Failed to create version for manual edit', {
|
||||
action: 'create_version_for_edit',
|
||||
itemType: currentItem.item_type,
|
||||
entityId: currentItem.approved_entity_id
|
||||
handleNonCriticalError(versionError, {
|
||||
action: 'Create Version for Manual Edit',
|
||||
metadata: {
|
||||
itemType: currentItem.item_type,
|
||||
entityId: currentItem.approved_entity_id
|
||||
}
|
||||
});
|
||||
// Don't fail the entire operation, just log the error
|
||||
// The edit itself is still saved, just without version history
|
||||
@@ -1390,7 +1417,10 @@ export async function escalateSubmission(
|
||||
}
|
||||
});
|
||||
} catch (auditError) {
|
||||
logger.error('Failed to log escalation audit', { error: auditError });
|
||||
handleNonCriticalError(auditError, {
|
||||
action: 'Log Escalation Audit',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1421,9 +1451,9 @@ export async function fetchEditHistory(itemId: string) {
|
||||
|
||||
return data || [];
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error fetching edit history', {
|
||||
itemId,
|
||||
error: getErrorMessage(error),
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch Edit History',
|
||||
metadata: { itemId }
|
||||
});
|
||||
return [];
|
||||
}
|
||||
@@ -1476,9 +1506,9 @@ export async function checkSubmissionConflict(
|
||||
},
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error checking submission conflict', {
|
||||
submissionId,
|
||||
error: getErrorMessage(error),
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Check Submission Conflict',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
@@ -1526,9 +1556,9 @@ export async function fetchSubmissionVersions(
|
||||
|
||||
return data || [];
|
||||
} catch (error: unknown) {
|
||||
logger.error('Error fetching submission versions', {
|
||||
submissionId,
|
||||
error: getErrorMessage(error),
|
||||
handleNonCriticalError(error, {
|
||||
action: 'Fetch Submission Versions',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
return [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user