Implement RLS and security functions

Apply Row Level Security to orphaned_images and system_alerts tables. Create RLS policies for admin/moderator access. Replace system_health view with get_system_health() function.
This commit is contained in:
gpt-engineer-app[bot]
2025-11-07 01:02:58 +00:00
parent 6bc5343256
commit e747e1f881
6 changed files with 265 additions and 22 deletions

View File

@@ -16,6 +16,21 @@ interface UploadedImageWithFlag extends UploadedImage {
wasNewlyUploaded?: boolean;
}
// Upload timeout in milliseconds (30 seconds)
const UPLOAD_TIMEOUT_MS = 30000;
/**
* Creates a promise that rejects after a timeout
*/
function withTimeout<T>(promise: Promise<T>, timeoutMs: number, operation: string): Promise<T> {
return Promise.race([
promise,
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error(`${operation} timed out after ${timeoutMs}ms`)), timeoutMs)
)
]);
}
/**
* Uploads pending local images to Cloudflare via Supabase Edge Function
* @param images Array of UploadedImage objects (mix of local and already uploaded)
@@ -27,10 +42,14 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
if (image.isLocal && image.file) {
const fileName = image.file.name;
// Step 1: Get upload URL from our Supabase Edge Function (with tracking)
const { data: uploadUrlData, error: urlError, requestId } = await invokeWithTracking(
'upload-image',
{ action: 'get-upload-url' }
// Step 1: Get upload URL from our Supabase Edge Function (with tracking and timeout)
const { data: uploadUrlData, error: urlError, requestId } = await withTimeout(
invokeWithTracking(
'upload-image',
{ action: 'get-upload-url' }
),
UPLOAD_TIMEOUT_MS,
'Get upload URL'
);
if (urlError || !uploadUrlData?.uploadURL) {
@@ -43,21 +62,25 @@ export async function uploadPendingImages(images: UploadedImage[]): Promise<Uplo
}
// Step 2: Upload file directly to Cloudflare
// Step 2: Upload file directly to Cloudflare (with timeout)
const formData = new FormData();
formData.append('file', image.file);
const uploadResponse = await fetch(uploadUrlData.uploadURL, {
method: 'POST',
body: formData,
});
const uploadResponse = await withTimeout(
fetch(uploadUrlData.uploadURL, {
method: 'POST',
body: formData,
}),
UPLOAD_TIMEOUT_MS,
'Cloudflare upload'
);
if (!uploadResponse.ok) {
const errorText = await uploadResponse.text();
const error = new Error(`Upload failed for "${fileName}" (status ${uploadResponse.status}): ${errorText}`);
handleError(error, {
action: 'Cloudflare Upload',
metadata: { fileName, status: uploadResponse.status }
metadata: { fileName, status: uploadResponse.status, timeout_ms: UPLOAD_TIMEOUT_MS }
});
throw error;
}

View File

@@ -98,7 +98,7 @@ export const authTestSuite: TestSuite = {
// Test is_superuser() database function
const { data: isSuper, error: superError } = await supabase
.rpc('is_superuser', { _user_id: user.id });
.rpc('is_superuser', { p_user_id: user.id });
if (superError) throw new Error(`is_superuser() failed: ${superError.message}`);
@@ -217,7 +217,7 @@ export const authTestSuite: TestSuite = {
// Test is_user_banned() database function
const { data: isBanned, error: bannedError } = await supabase
.rpc('is_user_banned', { _user_id: user.id });
.rpc('is_user_banned', { p_user_id: user.id });
if (bannedError) throw new Error(`is_user_banned() failed: ${bannedError.message}`);

View File

@@ -220,7 +220,7 @@ export const performanceTestSuite: TestSuite = {
const banStart = Date.now();
const { data: isBanned, error: banError } = await supabase
.rpc('is_user_banned', {
_user_id: userData.user.id
p_user_id: userData.user.id
});
const banDuration = Date.now() - banStart;