fix: implement chunked file uploading to bypass proxy size limits
Build and Deploy / build-and-push (push) Successful in 27s

This commit is contained in:
AI Bot
2026-09-02 07:39:14 +05:30
parent 39d131a9a5
commit 3a3ccceb96
3 changed files with 117 additions and 15 deletions
+55
View File
@@ -216,6 +216,61 @@ app.post('/api/upload', authenticate, upload.single('file'), (req, res) => {
res.json({ url }); res.json({ url });
}); });
// 10b. Chunked Upload - Receive chunk
app.post('/api/upload/chunk', authenticate, upload.single('file'), (req, res) => {
const { uploadId, chunkIndex, totalChunks } = req.body;
if (!req.file || !uploadId || !chunkIndex || !totalChunks) {
return res.status(400).json({ error: 'Missing chunk data' });
}
const tempDir = path.join(uploadsDir, 'temp', uploadId);
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const chunkPath = path.join(tempDir, chunkIndex);
fs.renameSync(req.file.path, chunkPath);
res.json({ success: true });
});
// 10c. Chunked Upload - Complete
app.post('/api/upload/complete', authenticate, (req, res) => {
const { uploadId, fileName, totalChunks } = req.body;
if (!uploadId || !fileName || !totalChunks) {
return res.status(400).json({ error: 'Missing completion data' });
}
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const finalFileName = uniqueSuffix + '-' + fileName;
const finalPath = path.join(uploadsDir, finalFileName);
const tempDir = path.join(uploadsDir, 'temp', uploadId);
try {
const writeStream = fs.createWriteStream(finalPath);
for (let i = 0; i < totalChunks; i++) {
const chunkPath = path.join(tempDir, i.toString());
if (!fs.existsSync(chunkPath)) {
throw new Error(`Chunk ${i} is missing`);
}
const data = fs.readFileSync(chunkPath);
writeStream.write(data);
}
writeStream.end();
// Clean up temp dir
fs.rmSync(tempDir, { recursive: true, force: true });
const url = `/uploads/${finalFileName}`;
res.json({ url });
} catch (error) {
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
res.status(500).json({ error: error.message });
}
});
const PORT = process.env.PORT || 3005; const PORT = process.env.PORT || 3005;
app.listen(PORT, () => { app.listen(PORT, () => {
console.log(`Backend server running on port ${PORT}`); console.log(`Backend server running on port ${PORT}`);
+55 -12
View File
@@ -112,19 +112,62 @@ export const api = {
// Storage // Storage
uploadAsset: async (fileBlob) => { uploadAsset: async (fileBlob) => {
const formData = new FormData(); const chunkSize = 10 * 1024 * 1024; // 10MB chunks
formData.append('file', fileBlob);
const token = localStorage.getItem('auth_token'); const token = localStorage.getItem('auth_token');
const headers = token ? { Authorization: `Bearer ${token}` } : {}; const authHeaders = token ? { Authorization: `Bearer ${token}` } : {};
const res = await fetch(`${API_URL}/upload`, { if (fileBlob.size <= chunkSize) {
method: 'POST', // Standard upload for small files
headers, const formData = new FormData();
body: formData formData.append('file', fileBlob);
});
const data = await res.json(); const res = await fetch(`${API_URL}/upload`, {
if (!res.ok) throw new Error(data.error); method: 'POST',
return data.url; headers: authHeaders,
body: formData
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
return data.url;
} else {
// Chunked upload for large files
const uploadId = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
const totalChunks = Math.ceil(fileBlob.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
const chunk = fileBlob.slice(i * chunkSize, (i + 1) * chunkSize);
const formData = new FormData();
formData.append('file', chunk);
formData.append('uploadId', uploadId);
formData.append('chunkIndex', i.toString());
formData.append('totalChunks', totalChunks.toString());
const res = await fetch(`${API_URL}/upload/chunk`, {
method: 'POST',
headers: authHeaders,
body: formData
});
if (!res.ok) throw new Error(`Failed to upload chunk ${i}`);
}
const completeHeaders = {
'Content-Type': 'application/json',
...authHeaders
};
const res = await fetch(`${API_URL}/upload/complete`, {
method: 'POST',
headers: completeHeaders,
body: JSON.stringify({
uploadId,
fileName: fileBlob.name || 'uploaded_file',
totalChunks
})
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
return data.url;
}
} }
}; };
+6 -2
View File
@@ -5,8 +5,12 @@ export const uploadAssetToStorage = async (file: Blob | File, bucket: string, pa
const url = await api.uploadAsset(file); const url = await api.uploadAsset(file);
return url; return url;
} catch (err) { } catch (err) {
console.warn("Storage upload exception, falling back to data URL.", err); console.warn("Storage upload exception.", err);
return await fileToDataUrl(file); if (file.size < 5 * 1024 * 1024) {
console.warn("Falling back to data URL for small file.");
return await fileToDataUrl(file);
}
throw err;
} }
}; };