feat: implement server-side image compression with sharp and cleanup RLS policies

This commit is contained in:
2025-12-18 22:08:28 +01:00
parent d26ebc0b2e
commit 9eb9b41061
5 changed files with 153 additions and 26 deletions

View File

@@ -62,6 +62,7 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
const [lastSavedId, setLastSavedId] = useState<string | null>(null);
const [wbDiscovery, setWbDiscovery] = useState<{ id: string; url: string; title: string } | null>(null);
const [isDiscovering, setIsDiscovering] = useState(false);
const [originalFile, setOriginalFile] = useState<File | null>(null);
const handleCapture = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
@@ -88,13 +89,14 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
quality: 0.8
});
// heic2any can return an array if the file contains multiple images
const blob = Array.isArray(convertedBlob) ? convertedBlob[0] : convertedBlob;
fileToProcess = new File([blob], file.name.replace(/\.(heic|heif)$/i, '.jpg'), {
type: 'image/jpeg'
});
}
setOriginalFile(fileToProcess);
const compressedBase64 = await compressImage(fileToProcess);
setPreviewUrl(compressedBase64);
@@ -151,7 +153,21 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
throw new Error(t('camera.authRequired'));
}
const response = await saveBottle(analysisResult, previewUrl, user.id);
let imageUrl = undefined;
if (originalFile) {
const formData = new FormData();
formData.append('file', originalFile);
const uploadRes = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const uploadData = await uploadRes.json();
if (uploadData.url) {
imageUrl = uploadData.url;
}
}
const response = await saveBottle(analysisResult, previewUrl, user.id, imageUrl);
if (response.success && response.data) {
const url = `/bottles/${response.data.id}${validatedSessionId ? `?session_id=${validatedSessionId}` : ''}`;
@@ -174,13 +190,26 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
setError(null);
try {
// Get current user (simple check for now, can be improved with Auth)
const { data: { user } } = await supabase.auth.getUser();
if (!user) {
throw new Error(t('camera.authRequired'));
}
const response = await saveBottle(analysisResult, previewUrl, user.id);
let imageUrl = undefined;
if (originalFile) {
const formData = new FormData();
formData.append('file', originalFile);
const uploadRes = await fetch('/api/upload', {
method: 'POST',
body: formData
});
const uploadData = await uploadRes.json();
if (uploadData.url) {
imageUrl = uploadData.url;
}
}
const response = await saveBottle(analysisResult, previewUrl, user.id, imageUrl);
if (response.success && response.data) {
setLastSavedId(response.data.id);
@@ -232,7 +261,7 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
img.src = event.target?.result as string;
img.onload = () => {
const canvas = document.createElement('canvas');
const MAX_WIDTH = 1024;
const MAX_WIDTH = 1200;
let width = img.width;
let height = img.height;
@@ -251,7 +280,7 @@ export default function CameraCapture({ onImageCaptured, onAnalysisComplete, onS
}
ctx.drawImage(img, 0, 0, width, height);
const base64 = canvas.toDataURL('image/jpeg', 0.8);
const base64 = canvas.toDataURL('image/jpeg', 0.9);
resolve(base64);
};
img.onerror = reject;