Add photo upload functionality

This commit is contained in:
gpt-engineer-app[bot]
2025-09-20 13:18:03 +00:00
parent 7802164584
commit 2c05925724
6 changed files with 1307 additions and 22 deletions

View File

@@ -0,0 +1,141 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
serve(async (req) => {
// Handle CORS preflight requests
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders })
}
try {
const CLOUDFLARE_ACCOUNT_ID = Deno.env.get('CLOUDFLARE_ACCOUNT_ID')
const CLOUDFLARE_IMAGES_API_TOKEN = Deno.env.get('CLOUDFLARE_IMAGES_API_TOKEN')
if (!CLOUDFLARE_ACCOUNT_ID || !CLOUDFLARE_IMAGES_API_TOKEN) {
throw new Error('Missing Cloudflare credentials')
}
if (req.method !== 'POST') {
return new Response(
JSON.stringify({ error: 'Method not allowed' }),
{
status: 405,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
const formData = await req.formData()
const file = formData.get('file') as File
if (!file) {
return new Response(
JSON.stringify({ error: 'No file provided' }),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Validate file size (10MB limit)
const maxSize = 10 * 1024 * 1024 // 10MB
if (file.size > maxSize) {
return new Response(
JSON.stringify({ error: 'File size exceeds 10MB limit' }),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Validate file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
if (!allowedTypes.includes(file.type)) {
return new Response(
JSON.stringify({ error: 'Invalid file type. Only JPEG, PNG, and WebP are allowed.' }),
{
status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Create FormData for Cloudflare Images API
const cloudflareFormData = new FormData()
cloudflareFormData.append('file', file)
// Optional metadata
const metadata = formData.get('metadata')
if (metadata) {
cloudflareFormData.append('metadata', metadata.toString())
}
// Upload to Cloudflare Images
const uploadResponse = await fetch(
`https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/images/v1`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${CLOUDFLARE_IMAGES_API_TOKEN}`,
},
body: cloudflareFormData,
}
)
const uploadResult = await uploadResponse.json()
if (!uploadResponse.ok) {
console.error('Cloudflare upload error:', uploadResult)
return new Response(
JSON.stringify({
error: 'Failed to upload image',
details: uploadResult.errors || uploadResult.error
}),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
// Return the upload result with image URLs
return new Response(
JSON.stringify({
success: true,
id: uploadResult.result.id,
filename: uploadResult.result.filename,
uploaded: uploadResult.result.uploaded,
variants: uploadResult.result.variants,
// Provide convenient URLs for different sizes
urls: {
original: uploadResult.result.variants[0], // First variant is usually the original
thumbnail: `${uploadResult.result.variants[0]}/w=400,h=400,fit=crop`, // 400x400 thumbnail
medium: `${uploadResult.result.variants[0]}/w=800,h=600,fit=cover`, // 800x600 medium
large: `${uploadResult.result.variants[0]}/w=1200,h=900,fit=cover`, // 1200x900 large
}
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
} catch (error) {
console.error('Upload error:', error)
return new Response(
JSON.stringify({
error: 'Internal server error',
message: error.message
}),
{
status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
}
)
}
})