feat: implement client-side image resizing for avatar uploads
- Add resizeImage function to SettingsPage - Resize images larger than 2MB to max 800x800 pixels - Convert to JPEG with 80% quality to reduce file size - Add server-side validation for 10MB file size limit - Maintain aspect ratio during resizing - Log resizing details for debugging Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
@@ -83,7 +83,7 @@ async def upload_avatar(
|
||||
print(f"Avatar upload called for member {current_member.id}")
|
||||
print(f"File: {file.filename}, Content-Type: {file.content_type}")
|
||||
|
||||
# Validate file type and size
|
||||
# Validate file type
|
||||
if not file.content_type.startswith("image/"):
|
||||
print("Invalid file type")
|
||||
raise HTTPException(
|
||||
@@ -91,6 +91,15 @@ async def upload_avatar(
|
||||
detail="Only image files are allowed"
|
||||
)
|
||||
|
||||
# Validate file size (10MB limit)
|
||||
max_size = 10 * 1024 * 1024 # 10MB
|
||||
if file.size > max_size:
|
||||
print(f"File too large: {file.size} bytes (max {max_size})")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
|
||||
detail=f"File too large. Maximum size is {max_size / 1024 / 1024}MB"
|
||||
)
|
||||
|
||||
# Create uploads directory if it doesn't exist
|
||||
upload_dir = "uploads/avatars"
|
||||
os.makedirs(upload_dir, exist_ok=True)
|
||||
|
||||
@@ -44,6 +44,74 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
|
||||
const [saved, setSaved] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Image resizing function
|
||||
const resizeImage = (file: File, maxWidth: number, maxHeight: number): Promise<File> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = (event) => {
|
||||
if (typeof event.target?.result !== 'string') {
|
||||
reject(new Error('Failed to read file'));
|
||||
return;
|
||||
}
|
||||
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
let width = img.width;
|
||||
let height = img.height;
|
||||
|
||||
// Calculate new dimensions
|
||||
if (width > height) {
|
||||
if (width > maxWidth) {
|
||||
height *= maxWidth / width;
|
||||
width = maxWidth;
|
||||
}
|
||||
} else {
|
||||
if (height > maxHeight) {
|
||||
width *= maxHeight / height;
|
||||
height = maxHeight;
|
||||
}
|
||||
}
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
reject(new Error('Failed to get canvas context'));
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
if (!blob) {
|
||||
reject(new Error('Failed to create blob'));
|
||||
return;
|
||||
}
|
||||
|
||||
const resizedFile = new File([blob], file.name, {
|
||||
type: 'image/jpeg',
|
||||
lastModified: Date.now()
|
||||
});
|
||||
|
||||
console.log(`Resized image from ${img.width}x${img.height} to ${width}x${height}`);
|
||||
console.log(`File size reduced from ${file.size} to ${resizedFile.size} bytes`);
|
||||
|
||||
resolve(resizedFile);
|
||||
}, 'image/jpeg', 0.8); // JPEG quality 80%
|
||||
};
|
||||
|
||||
img.onerror = reject;
|
||||
img.src = event.target?.result;
|
||||
};
|
||||
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
};
|
||||
|
||||
const saveMutation = useMutation({
|
||||
mutationFn: () =>
|
||||
updateSettings({
|
||||
@@ -127,9 +195,19 @@ function SettingsForm({ me, onBack }: { me: MemberRead; onBack: () => void }) {
|
||||
if (file) {
|
||||
console.log("Selected file:", file.name, file.type, file.size);
|
||||
setUploading(true);
|
||||
|
||||
try {
|
||||
// Check file size and resize if needed
|
||||
const maxSize = 2 * 1024 * 1024; // 2MB
|
||||
let processedFile = file;
|
||||
|
||||
if (file.size > maxSize) {
|
||||
console.log("File too large, resizing...");
|
||||
processedFile = await resizeImage(file, 800, 800); // Max 800x800
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('file', processedFile, processedFile.name || file.name);
|
||||
|
||||
console.log("Uploading file to /auth/me/avatar");
|
||||
const response = await api.post<MemberRead>('/auth/me/avatar', formData);
|
||||
|
||||
Reference in New Issue
Block a user