fix: implement chunked file uploading to bypass proxy size limits
Build and Deploy / build-and-push (push) Successful in 27s
Build and Deploy / build-and-push (push) Successful in 27s
This commit is contained in:
@@ -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
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user