Commit c2a2be92 authored by Mahmoud Aglan's avatar Mahmoud Aglan

fix: avatar upload 500 — add error handlers, use mime_content_type

Replace finfo_open with simpler mime_content_type(). Add global
exception/error handlers that return JSON instead of HTML error pages.
Include curl errors and storage HTTP codes in error responses for
debugging. Remove fileinfo from Dockerfile (already bundled in PHP 8.3).
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 13f4c712
FROM php:8.3-apache
RUN apt-get update && apt-get install -y libpq-dev libcurl4-openssl-dev \
&& docker-php-ext-install pdo pdo_pgsql pgsql curl fileinfo \
&& docker-php-ext-install pdo pdo_pgsql pgsql curl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN echo "upload_max_filesize = 10M\npost_max_size = 12M\nmax_file_uploads = 5" > /usr/local/etc/php/conf.d/uploads.ini
......
<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
set_exception_handler(function($e) {
http_response_code(500);
header('Content-Type: application/json');
echo json_encode(['error' => $e->getMessage(), 'line' => $e->getLine()]);
exit;
});
set_error_handler(function($errno, $errstr, $errfile, $errline) {
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, OPTIONS');
......@@ -16,26 +30,23 @@ $userId = getUserId($token);
if (!$userId) jsonError('Invalid user', 401);
if (!isset($_FILES['avatar'])) {
jsonError('No file in request. Check upload_max_filesize (' . ini_get('upload_max_filesize') . ')');
jsonError('No file in request. upload_max_filesize=' . ini_get('upload_max_filesize') . ' post_max_size=' . ini_get('post_max_size'));
}
if ($_FILES['avatar']['error'] !== UPLOAD_ERR_OK) {
$errors = [1 => 'File exceeds server limit', 2 => 'File exceeds form limit', 3 => 'Partial upload', 4 => 'No file sent', 6 => 'No tmp dir', 7 => 'Write failed'];
jsonError($errors[$_FILES['avatar']['error']] ?? 'Upload error ' . $_FILES['avatar']['error']);
jsonError($errors[$_FILES['avatar']['error']] ?? 'Upload error code ' . $_FILES['avatar']['error']);
}
$file = $_FILES['avatar'];
$maxSize = 5 * 1024 * 1024;
if ($file['size'] > $maxSize) {
if ($file['size'] > 5 * 1024 * 1024) {
jsonError('File too large (max 5MB)');
}
$mime = mime_content_type($file['tmp_name']);
$allowed = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if (!in_array($mime, $allowed)) {
jsonError('Invalid file type. Allowed: JPEG, PNG, WebP, GIF');
jsonError('Invalid file type: ' . $mime . '. Allowed: JPEG, PNG, WebP, GIF');
}
$ext = match($mime) {
......@@ -51,6 +62,11 @@ $storagePath = 'avatars/' . $filename;
$bucket = 'profile-images';
$storageUrl = SUPABASE_STORAGE . '/object/' . $bucket . '/' . $storagePath;
$fileContents = file_get_contents($file['tmp_name']);
if ($fileContents === false) {
jsonError('Failed to read uploaded file');
}
$ch = curl_init($storageUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
......@@ -60,16 +76,21 @@ curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: ' . $mime,
'x-upsert: true'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, file_get_contents($file['tmp_name']));
curl_setopt($ch, CURLOPT_POSTFIELDS, $fileContents);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlError) {
jsonError('Storage connection failed: ' . $curlError, 500);
}
if ($httpCode >= 400) {
$decoded = json_decode($response, true);
jsonError($decoded['message'] ?? 'Upload failed', 500);
jsonError('Storage error (' . $httpCode . '): ' . ($decoded['message'] ?? $decoded['error'] ?? $response), 500);
}
$publicUrl = SUPABASE_STORAGE . '/object/public/' . $bucket . '/' . $storagePath;
......@@ -78,7 +99,7 @@ $db = supabaseService();
$result = $db->update('profiles', ['avatar_url' => $publicUrl], ['id' => 'eq.' . $userId]);
if (isset($result['error'])) {
jsonError('Failed to update profile');
jsonError('Profile update failed: ' . $result['error']);
}
jsonResponse(['avatar_url' => $publicUrl]);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment