Improve error handling and environment configuration across the application

Enhance input validation, update environment variable usage for Supabase and Turnstile, and refine image upload and auth logic for better robustness and developer experience.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: cb061c75-702e-4b89-a8d1-77a96cdcdfbb
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7cdf4e95-3f41-4180-b8e3-8ef56d032c0e/cb061c75-702e-4b89-a8d1-77a96cdcdfbb/ANdRXVZ
This commit is contained in:
pac7
2025-10-07 14:42:22 +00:00
parent 01c2df62a8
commit 1addcbc0dd
8 changed files with 412 additions and 116 deletions

View File

@@ -24,6 +24,48 @@ serve(async (req) => {
const { subscriberId, email, firstName, lastName, phone, avatar, data } = await req.json();
// Validate required fields
if (!subscriberId || typeof subscriberId !== 'string' || subscriberId.trim() === '') {
return new Response(
JSON.stringify({
success: false,
error: 'subscriberId is required and must be a non-empty string',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
if (!email || typeof email !== 'string' || email.trim() === '') {
return new Response(
JSON.stringify({
success: false,
error: 'email is required and must be a non-empty string',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
// Validate email format using regex
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return new Response(
JSON.stringify({
success: false,
error: 'Invalid email format. Please provide a valid email address',
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 400,
}
);
}
console.log('Creating Novu subscriber:', { subscriberId, email, firstName });
const subscriber = await novu.subscribers.identify(subscriberId, {

View File

@@ -56,6 +56,54 @@ serve(async (req) => {
const { itemIds, userId, submissionId }: ApprovalRequest = await req.json();
// UUID validation regex
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
// Validate itemIds
if (!itemIds || !Array.isArray(itemIds)) {
return new Response(
JSON.stringify({ error: 'itemIds is required and must be an array' }),
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
if (itemIds.length === 0) {
return new Response(
JSON.stringify({ error: 'itemIds must be a non-empty array' }),
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
// Validate userId
if (!userId || typeof userId !== 'string' || userId.trim() === '') {
return new Response(
JSON.stringify({ error: 'userId is required and must be a non-empty string' }),
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
if (!uuidRegex.test(userId)) {
return new Response(
JSON.stringify({ error: 'userId must be a valid UUID format' }),
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
// Validate submissionId
if (!submissionId || typeof submissionId !== 'string' || submissionId.trim() === '') {
return new Response(
JSON.stringify({ error: 'submissionId is required and must be a non-empty string' }),
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
if (!uuidRegex.test(submissionId)) {
return new Response(
JSON.stringify({ error: 'submissionId must be a valid UUID format' }),
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
);
}
console.log('Processing selective approval:', { itemIds, userId, submissionId });
// Fetch all items for the submission
@@ -84,7 +132,13 @@ serve(async (req) => {
// Topologically sort items by dependencies
const sortedItems = topologicalSort(items);
const dependencyMap = new Map<string, string>();
const approvalResults = [];
const approvalResults: Array<{
itemId: string;
entityId?: string | null;
itemType: string;
success: boolean;
error?: string;
}> = [];
// Process items in order
for (const item of sortedItems) {

View File

@@ -55,11 +55,24 @@ serve(async (req) => {
}
// Delete image from Cloudflare
const { imageId } = await req.json()
if (!imageId) {
let requestBody;
try {
requestBody = await req.json();
} catch (error) {
return new Response(
JSON.stringify({ error: 'Image ID is required for deletion' }),
JSON.stringify({ error: 'Invalid JSON in request body' }),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
const { imageId } = requestBody;
if (!imageId || typeof imageId !== 'string' || imageId.trim() === '') {
return new Response(
JSON.stringify({ error: 'imageId is required and must be a non-empty string' }),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
@@ -103,7 +116,25 @@ serve(async (req) => {
if (req.method === 'POST') {
// Request a direct upload URL from Cloudflare
const { metadata = {}, variant = 'public', requireSignedURLs = false } = await req.json().catch(() => ({}))
let requestBody;
try {
requestBody = await req.json();
} catch (error) {
requestBody = {};
}
// Validate request body structure
if (requestBody && typeof requestBody !== 'object') {
return new Response(
JSON.stringify({ error: 'Request body must be a valid JSON object' }),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
const { metadata = {}, variant = 'public', requireSignedURLs = false } = requestBody;
// Create FormData for the request (Cloudflare API requires multipart/form-data)
const formData = new FormData()
@@ -159,9 +190,9 @@ serve(async (req) => {
const url = new URL(req.url)
const imageId = url.searchParams.get('id')
if (!imageId) {
if (!imageId || imageId.trim() === '') {
return new Response(
JSON.stringify({ error: 'Image ID is required' }),
JSON.stringify({ error: 'id query parameter is required and must be non-empty' }),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }