mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 13:11:16 -05:00
feat: Execute complete production readiness plan
This commit is contained in:
@@ -13,6 +13,8 @@ import type {
|
|||||||
MFAChallengeResult
|
MFAChallengeResult
|
||||||
} from '@/types/auth';
|
} from '@/types/auth';
|
||||||
import { setStepUpRequired, setAuthMethod, clearAllAuthFlags } from './sessionFlags';
|
import { setStepUpRequired, setAuthMethod, clearAllAuthFlags } from './sessionFlags';
|
||||||
|
import { logger } from './logger';
|
||||||
|
import { getErrorMessage } from './errorHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract AAL level from session using Supabase API
|
* Extract AAL level from session using Supabase API
|
||||||
@@ -20,14 +22,14 @@ import { setStepUpRequired, setAuthMethod, clearAllAuthFlags } from './sessionFl
|
|||||||
*/
|
*/
|
||||||
export async function getSessionAal(session: Session | null): Promise<AALLevel> {
|
export async function getSessionAal(session: Session | null): Promise<AALLevel> {
|
||||||
if (!session) {
|
if (!session) {
|
||||||
console.log('🔍 [AuthService] No session, returning aal1');
|
logger.log('[AuthService] No session, returning aal1');
|
||||||
return 'aal1';
|
return 'aal1';
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, error } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel();
|
const { data, error } = await supabase.auth.mfa.getAuthenticatorAssuranceLevel();
|
||||||
|
|
||||||
console.log('🔍 [AuthService] getSessionAal result:', {
|
logger.log('[AuthService] getSessionAal result', {
|
||||||
hasData: !!data,
|
hasData: !!data,
|
||||||
currentLevel: data?.currentLevel,
|
currentLevel: data?.currentLevel,
|
||||||
nextLevel: data?.nextLevel,
|
nextLevel: data?.nextLevel,
|
||||||
@@ -35,15 +37,22 @@ export async function getSessionAal(session: Session | null): Promise<AALLevel>
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('[AuthService] Error getting AAL:', error);
|
logger.error('[AuthService] Error getting AAL', {
|
||||||
|
action: 'get_session_aal',
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
return 'aal1';
|
return 'aal1';
|
||||||
}
|
}
|
||||||
|
|
||||||
const level = (data.currentLevel as AALLevel) || 'aal1';
|
const level = (data.currentLevel as AALLevel) || 'aal1';
|
||||||
console.log('🔐 [AuthService] Returning AAL:', level);
|
logger.log('[AuthService] Returning AAL', { level });
|
||||||
return level;
|
return level;
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('[AuthService] Exception getting AAL:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
logger.error('[AuthService] Exception getting AAL', {
|
||||||
|
action: 'get_session_aal',
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
return 'aal1';
|
return 'aal1';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,7 +65,10 @@ export async function getEnrolledFactors(): Promise<MFAFactor[]> {
|
|||||||
const { data, error } = await supabase.auth.mfa.listFactors();
|
const { data, error } = await supabase.auth.mfa.listFactors();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('[AuthService] Error listing factors:', error);
|
logger.error('[AuthService] Error listing factors', {
|
||||||
|
action: 'get_enrolled_factors',
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,8 +82,12 @@ export async function getEnrolledFactors(): Promise<MFAFactor[]> {
|
|||||||
created_at: f.created_at,
|
created_at: f.created_at,
|
||||||
updated_at: f.updated_at,
|
updated_at: f.updated_at,
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('[AuthService] Exception listing factors:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
logger.error('[AuthService] Exception listing factors', {
|
||||||
|
action: 'get_enrolled_factors',
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,13 +135,22 @@ export async function verifyMfaRequired(userId: string): Promise<boolean> {
|
|||||||
.in('role', ['admin', 'moderator']);
|
.in('role', ['admin', 'moderator']);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('[AuthService] Error checking roles:', error);
|
logger.error('[AuthService] Error checking roles', {
|
||||||
|
action: 'verify_mfa_required',
|
||||||
|
userId,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (data?.length || 0) > 0;
|
return (data?.length || 0) > 0;
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('[AuthService] Exception checking roles:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
logger.error('[AuthService] Exception checking roles', {
|
||||||
|
action: 'verify_mfa_required',
|
||||||
|
userId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +171,10 @@ export async function handlePostAuthFlow(
|
|||||||
const aalCheck = await checkAalStepUp(session);
|
const aalCheck = await checkAalStepUp(session);
|
||||||
|
|
||||||
if (aalCheck.needsStepUp) {
|
if (aalCheck.needsStepUp) {
|
||||||
console.log(`[AuthService] ${authMethod} sign-in requires MFA step-up`);
|
logger.info('[AuthService] MFA step-up required', {
|
||||||
|
authMethod,
|
||||||
|
currentAal: aalCheck.currentLevel
|
||||||
|
});
|
||||||
|
|
||||||
// Set flag and redirect to step-up page
|
// Set flag and redirect to step-up page
|
||||||
setStepUpRequired(true, window.location.pathname);
|
setStepUpRequired(true, window.location.pathname);
|
||||||
@@ -178,11 +206,16 @@ export async function handlePostAuthFlow(
|
|||||||
shouldRedirect: false,
|
shouldRedirect: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('[AuthService] Error in post-auth flow:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
logger.error('[AuthService] Error in post-auth flow', {
|
||||||
|
action: 'handle_post_auth_flow',
|
||||||
|
authMethod,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
error: errorMessage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -201,7 +234,11 @@ export async function verifyMfaUpgrade(session: Session | null): Promise<MFAChal
|
|||||||
const currentAal = await getSessionAal(session);
|
const currentAal = await getSessionAal(session);
|
||||||
|
|
||||||
if (currentAal !== 'aal2') {
|
if (currentAal !== 'aal2') {
|
||||||
console.error('[AuthService] MFA verification failed - still at AAL1');
|
logger.error('[AuthService] MFA verification failed', {
|
||||||
|
action: 'verify_mfa_upgrade',
|
||||||
|
expectedAal: 'aal2',
|
||||||
|
actualAal: currentAal
|
||||||
|
});
|
||||||
await logAuthEvent(session.user.id, 'mfa_verification_failed', {
|
await logAuthEvent(session.user.id, 'mfa_verification_failed', {
|
||||||
expected_aal: 'aal2',
|
expected_aal: 'aal2',
|
||||||
actual_aal: currentAal,
|
actual_aal: currentAal,
|
||||||
@@ -245,10 +282,21 @@ async function logAuthEvent(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('[AuthService] Error logging auth event:', error);
|
logger.error('[AuthService] Error logging auth event', {
|
||||||
|
action: 'log_auth_event',
|
||||||
|
eventAction: action,
|
||||||
|
userId,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('[AuthService] Exception logging auth event:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
logger.error('[AuthService] Exception logging auth event', {
|
||||||
|
action: 'log_auth_event',
|
||||||
|
eventAction: action,
|
||||||
|
userId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { uploadPendingImages } from './imageUploadHelper';
|
|||||||
import type { ProcessedImage } from './supabaseHelpers';
|
import type { ProcessedImage } from './supabaseHelpers';
|
||||||
import { extractChangedFields } from './submissionChangeDetection';
|
import { extractChangedFields } from './submissionChangeDetection';
|
||||||
import type { CompanyDatabaseRecord, TimelineEventDatabaseRecord } from '@/types/company-data';
|
import type { CompanyDatabaseRecord, TimelineEventDatabaseRecord } from '@/types/company-data';
|
||||||
|
import { logger } from './logger';
|
||||||
|
import { getErrorMessage } from './errorHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ═══════════════════════════════════════════════════════════════════
|
* ═══════════════════════════════════════════════════════════════════
|
||||||
@@ -186,9 +188,13 @@ export async function submitParkCreation(
|
|||||||
...data.images,
|
...data.images,
|
||||||
uploaded: uploadedImages
|
uploaded: uploadedImages
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images for park creation:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Park image upload failed', {
|
||||||
|
action: 'park_creation',
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,9 +278,14 @@ export async function submitParkUpdate(
|
|||||||
...data.images,
|
...data.images,
|
||||||
uploaded: uploadedImages
|
uploaded: uploadedImages
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images for park update:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Park image upload failed', {
|
||||||
|
action: 'park_update',
|
||||||
|
parkId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,9 +360,13 @@ export async function submitRideCreation(
|
|||||||
...data.images,
|
...data.images,
|
||||||
uploaded: uploadedImages
|
uploaded: uploadedImages
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images for ride creation:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Ride image upload failed', {
|
||||||
|
action: 'ride_creation',
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,9 +450,14 @@ export async function submitRideUpdate(
|
|||||||
...data.images,
|
...data.images,
|
||||||
uploaded: uploadedImages
|
uploaded: uploadedImages
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images for ride update:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Ride image upload failed', {
|
||||||
|
action: 'ride_update',
|
||||||
|
rideId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -509,9 +529,13 @@ export async function submitRideModelCreation(
|
|||||||
...data.images,
|
...data.images,
|
||||||
uploaded: uploadedImages
|
uploaded: uploadedImages
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images for ride model creation:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Ride model image upload failed', {
|
||||||
|
action: 'ride_model_creation',
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,9 +605,14 @@ export async function submitRideModelUpdate(
|
|||||||
...data.images,
|
...data.images,
|
||||||
uploaded: uploadedImages
|
uploaded: uploadedImages
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images for ride model update:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Ride model image upload failed', {
|
||||||
|
action: 'ride_model_update',
|
||||||
|
rideModelId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -640,9 +669,13 @@ export async function submitManufacturerCreation(
|
|||||||
try {
|
try {
|
||||||
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
processedImages = { ...data.images, uploaded: uploadedImages };
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Company image upload failed', {
|
||||||
|
action: 'manufacturer_creation',
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -698,9 +731,14 @@ export async function submitManufacturerUpdate(
|
|||||||
try {
|
try {
|
||||||
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
processedImages = { ...data.images, uploaded: uploadedImages };
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Company image upload failed', {
|
||||||
|
action: 'manufacturer_update',
|
||||||
|
companyId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,9 +786,13 @@ export async function submitDesignerCreation(
|
|||||||
try {
|
try {
|
||||||
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
processedImages = { ...data.images, uploaded: uploadedImages };
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Company image upload failed', {
|
||||||
|
action: 'designer_creation',
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -806,9 +848,14 @@ export async function submitDesignerUpdate(
|
|||||||
try {
|
try {
|
||||||
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
processedImages = { ...data.images, uploaded: uploadedImages };
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Company image upload failed', {
|
||||||
|
action: 'designer_update',
|
||||||
|
companyId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -856,9 +903,13 @@ export async function submitOperatorCreation(
|
|||||||
try {
|
try {
|
||||||
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
processedImages = { ...data.images, uploaded: uploadedImages };
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Company image upload failed', {
|
||||||
|
action: 'operator_creation',
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -914,9 +965,14 @@ export async function submitOperatorUpdate(
|
|||||||
try {
|
try {
|
||||||
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
processedImages = { ...data.images, uploaded: uploadedImages };
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Company image upload failed', {
|
||||||
|
action: 'operator_update',
|
||||||
|
companyId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -964,9 +1020,13 @@ export async function submitPropertyOwnerCreation(
|
|||||||
try {
|
try {
|
||||||
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
processedImages = { ...data.images, uploaded: uploadedImages };
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Company image upload failed', {
|
||||||
|
action: 'property_owner_creation',
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1022,9 +1082,14 @@ export async function submitPropertyOwnerUpdate(
|
|||||||
try {
|
try {
|
||||||
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
const uploadedImages = await uploadPendingImages(data.images.uploaded);
|
||||||
processedImages = { ...data.images, uploaded: uploadedImages };
|
processedImages = { ...data.images, uploaded: uploadedImages };
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Failed to upload images:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
throw new Error('Failed to upload images. Please check your connection and try again.');
|
logger.error('Company image upload failed', {
|
||||||
|
action: 'property_owner_update',
|
||||||
|
companyId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
throw new Error(`Failed to upload images: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1133,7 +1198,12 @@ export async function submitTimelineEvent(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (error || !submissionId) {
|
if (error || !submissionId) {
|
||||||
console.error('Failed to create timeline event submission:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
logger.error('Timeline event submission failed', {
|
||||||
|
action: 'create_timeline_event',
|
||||||
|
userId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
throw new Error('Failed to submit timeline event for review');
|
throw new Error('Failed to submit timeline event for review');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1205,7 +1275,12 @@ export async function submitTimelineEventUpdate(
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (rpcError || !result) {
|
if (rpcError || !result) {
|
||||||
console.error('Failed to create timeline event update:', rpcError);
|
const errorMessage = getErrorMessage(rpcError);
|
||||||
|
logger.error('Timeline event update failed', {
|
||||||
|
action: 'update_timeline_event',
|
||||||
|
eventId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
throw new Error('Failed to submit timeline event update');
|
throw new Error('Failed to submit timeline event update');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1233,7 +1308,12 @@ export async function deleteTimelineEvent(
|
|||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (fetchError) {
|
if (fetchError) {
|
||||||
console.error('Error fetching timeline event:', fetchError);
|
const errorMessage = getErrorMessage(fetchError);
|
||||||
|
logger.error('Timeline event fetch failed', {
|
||||||
|
action: 'delete_timeline_event',
|
||||||
|
eventId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
throw new Error('Timeline event not found');
|
throw new Error('Timeline event not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1257,9 +1337,18 @@ export async function deleteTimelineEvent(
|
|||||||
.eq('id', eventId);
|
.eq('id', eventId);
|
||||||
|
|
||||||
if (deleteError) {
|
if (deleteError) {
|
||||||
console.error('Error deleting timeline event:', deleteError);
|
const errorMessage = getErrorMessage(deleteError);
|
||||||
|
logger.error('Timeline event deletion failed', {
|
||||||
|
action: 'delete_timeline_event',
|
||||||
|
eventId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
throw new Error('Failed to delete timeline event');
|
throw new Error('Failed to delete timeline event');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ Timeline event deleted:', eventId);
|
logger.info('Timeline event deleted', {
|
||||||
|
action: 'delete_timeline_event',
|
||||||
|
eventId,
|
||||||
|
userId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
import { SupabaseClient } from '@supabase/supabase-js';
|
import { SupabaseClient } from '@supabase/supabase-js';
|
||||||
import { createTableQuery } from '@/lib/supabaseHelpers';
|
import { createTableQuery } from '@/lib/supabaseHelpers';
|
||||||
import type { ModerationItem } from '@/types/moderation';
|
import type { ModerationItem } from '@/types/moderation';
|
||||||
|
import { logger } from '@/lib/logger';
|
||||||
|
import { getErrorMessage } from '@/lib/errorHandler';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type-safe update data for review moderation
|
* Type-safe update data for review moderation
|
||||||
@@ -150,12 +152,17 @@ export async function approvePhotoSubmission(
|
|||||||
message: `Successfully approved and published ${photoSubmission.items.length} photo(s)`,
|
message: `Successfully approved and published ${photoSubmission.items.length} photo(s)`,
|
||||||
shouldRemoveFromQueue: true,
|
shouldRemoveFromQueue: true,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Photo approval error:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
logger.error('Photo approval failed', {
|
||||||
|
action: 'approve_photo',
|
||||||
|
submissionId: config.submissionId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Failed to approve photo submission',
|
message: 'Failed to approve photo submission',
|
||||||
error: error as Error,
|
error: new Error(errorMessage),
|
||||||
shouldRemoveFromQueue: false,
|
shouldRemoveFromQueue: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -196,12 +203,18 @@ export async function approveSubmissionItems(
|
|||||||
message: `Successfully processed ${itemIds.length} item(s)`,
|
message: `Successfully processed ${itemIds.length} item(s)`,
|
||||||
shouldRemoveFromQueue: true,
|
shouldRemoveFromQueue: true,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Submission items approval error:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
logger.error('Submission items approval failed', {
|
||||||
|
action: 'approve_submission_items',
|
||||||
|
submissionId,
|
||||||
|
itemCount: itemIds.length,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Failed to approve submission items',
|
message: 'Failed to approve submission items',
|
||||||
error: error as Error,
|
error: new Error(errorMessage),
|
||||||
shouldRemoveFromQueue: false,
|
shouldRemoveFromQueue: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -234,8 +247,12 @@ export async function rejectSubmissionItems(
|
|||||||
.eq('status', 'pending');
|
.eq('status', 'pending');
|
||||||
|
|
||||||
if (rejectError) {
|
if (rejectError) {
|
||||||
console.error('Failed to cascade rejection:', rejectError);
|
const errorMessage = getErrorMessage(rejectError);
|
||||||
// Don't fail the whole operation, just log it
|
logger.error('Item rejection cascade failed', {
|
||||||
|
action: 'reject_submission_items',
|
||||||
|
submissionId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -243,12 +260,17 @@ export async function rejectSubmissionItems(
|
|||||||
message: 'Submission items rejected',
|
message: 'Submission items rejected',
|
||||||
shouldRemoveFromQueue: false, // Parent rejection will handle removal
|
shouldRemoveFromQueue: false, // Parent rejection will handle removal
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Submission items rejection error:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
logger.error('Submission items rejection failed', {
|
||||||
|
action: 'reject_submission_items',
|
||||||
|
submissionId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Failed to reject submission items',
|
message: 'Failed to reject submission items',
|
||||||
error: error as Error,
|
error: new Error(errorMessage),
|
||||||
shouldRemoveFromQueue: false,
|
shouldRemoveFromQueue: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -366,12 +388,18 @@ export async function performModerationAction(
|
|||||||
message: `Content ${action}`,
|
message: `Content ${action}`,
|
||||||
shouldRemoveFromQueue: action === 'approved' || action === 'rejected',
|
shouldRemoveFromQueue: action === 'approved' || action === 'rejected',
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error: unknown) {
|
||||||
console.error('Moderation action error:', error);
|
const errorMessage = getErrorMessage(error);
|
||||||
|
logger.error('Moderation action failed', {
|
||||||
|
action: config.action,
|
||||||
|
itemType: item.type,
|
||||||
|
itemId: item.id,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
message: `Failed to ${action} content`,
|
message: `Failed to ${config.action} content`,
|
||||||
error: error as Error,
|
error: new Error(errorMessage),
|
||||||
shouldRemoveFromQueue: false,
|
shouldRemoveFromQueue: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -456,8 +484,13 @@ export async function deleteSubmission(
|
|||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
body: { imageId },
|
body: { imageId },
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (photoDeleteError: unknown) {
|
||||||
console.error(`Failed to delete photo ${imageId}:`, error);
|
const errorMessage = getErrorMessage(photoDeleteError);
|
||||||
|
logger.error('Photo deletion failed', {
|
||||||
|
action: 'delete_submission_photo',
|
||||||
|
imageId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import type {
|
|||||||
RideModelSubmissionData
|
RideModelSubmissionData
|
||||||
} from '@/types/submission-data';
|
} from '@/types/submission-data';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { logger } from './logger';
|
||||||
|
import { getErrorMessage } from './errorHandler';
|
||||||
|
|
||||||
type SubmissionDataTypes =
|
type SubmissionDataTypes =
|
||||||
| ParkSubmissionData
|
| ParkSubmissionData
|
||||||
@@ -79,7 +81,12 @@ async function detectPhotoChanges(submissionId: string): Promise<PhotoChange[]>
|
|||||||
.eq('submission_id', submissionId);
|
.eq('submission_id', submissionId);
|
||||||
|
|
||||||
if (photoError) {
|
if (photoError) {
|
||||||
console.error('Error fetching photo submissions:', photoError);
|
const errorMessage = getErrorMessage(photoError);
|
||||||
|
logger.error('Photo submission fetch failed', {
|
||||||
|
action: 'detect_photo_changes',
|
||||||
|
submissionId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const photoSubmission = photoSubmissions?.[0];
|
const photoSubmission = photoSubmissions?.[0];
|
||||||
if (photoSubmission?.items && photoSubmission.items.length > 0) {
|
if (photoSubmission?.items && photoSubmission.items.length > 0) {
|
||||||
@@ -102,7 +109,12 @@ async function detectPhotoChanges(submissionId: string): Promise<PhotoChange[]>
|
|||||||
.in('item_type', ['photo_edit', 'photo_delete']);
|
.in('item_type', ['photo_edit', 'photo_delete']);
|
||||||
|
|
||||||
if (itemsError) {
|
if (itemsError) {
|
||||||
console.error('Error fetching submission items for photos:', itemsError);
|
const errorMessage = getErrorMessage(itemsError);
|
||||||
|
logger.error('Submission items fetch failed', {
|
||||||
|
action: 'detect_photo_changes',
|
||||||
|
submissionId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
} else if (submissionItems && submissionItems.length > 0) {
|
} else if (submissionItems && submissionItems.length > 0) {
|
||||||
for (const item of submissionItems) {
|
for (const item of submissionItems) {
|
||||||
const itemData = item.item_data as Record<string, any>;
|
const itemData = item.item_data as Record<string, any>;
|
||||||
@@ -134,8 +146,13 @@ async function detectPhotoChanges(submissionId: string): Promise<PhotoChange[]>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
console.error('Error detecting photo changes:', err);
|
const errorMessage = getErrorMessage(err);
|
||||||
|
logger.error('Photo change detection failed', {
|
||||||
|
action: 'detect_photo_changes',
|
||||||
|
submissionId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
|
|||||||
Reference in New Issue
Block a user