feat: implement server-side image compression with sharp and cleanup RLS policies
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user