Add chunked upload support for large video files up to 10GB

- Increased max file size from 500MB to 10GB
- Created chunkedUploadService.js for managing chunked uploads
- Added chunked upload API endpoints (init, chunk, complete, status, abort)
- Added frontend chunked upload methods to photos.service.ts
- Files >100MB automatically use chunked uploads
- 10MB chunk size for reliable transfers
- Auto-cleanup of expired uploads after 24 hours
- Updated README with 10GB limit and nginx configuration example
This commit is contained in:
Claude
2025-11-19 22:03:38 +00:00
committed by paul
parent f3482a9a78
commit 0d95eab86a
4 changed files with 538 additions and 4 deletions
+100
View File
@@ -99,6 +99,106 @@ class PhotosService {
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// Chunked upload methods for large files (videos up to 10GB)
private CHUNK_SIZE = 10 * 1024 * 1024; // 10MB chunks
async initChunkedUpload(
eventId: number,
filename: string,
fileSize: number,
mimeType: string
): Promise<{ uploadId: string; chunkSize: number; expectedChunks: number }> {
const totalChunks = Math.ceil(fileSize / this.CHUNK_SIZE);
const response = await api.post(`/admin/photos/${eventId}/chunked-upload/init`, {
filename,
fileSize,
mimeType,
totalChunks
});
return response.data;
}
async uploadChunk(
eventId: number,
uploadId: string,
chunkIndex: number,
chunkData: Blob
): Promise<{ progress: number; complete: boolean }> {
const response = await api.post(
`/admin/photos/${eventId}/chunked-upload/${uploadId}/chunk/${chunkIndex}`,
chunkData,
{
headers: {
'Content-Type': 'application/octet-stream'
}
}
);
return response.data;
}
async completeChunkedUpload(
eventId: number,
uploadId: string,
categoryId?: number | null
): Promise<{ success: boolean; uploaded: number; photos: AdminPhoto[] }> {
const response = await api.post(
`/admin/photos/${eventId}/chunked-upload/${uploadId}/complete`,
{ category_id: categoryId }
);
return response.data;
}
async abortChunkedUpload(eventId: number, uploadId: string): Promise<void> {
await api.delete(`/admin/photos/${eventId}/chunked-upload/${uploadId}`);
}
async uploadLargeFile(
eventId: number,
file: File,
categoryId?: number | null,
onProgress?: (progress: number) => void
): Promise<AdminPhoto[]> {
// Initialize upload
const { uploadId, expectedChunks } = await this.initChunkedUpload(
eventId,
file.name,
file.size,
file.type
);
try {
// Upload chunks
for (let i = 0; i < expectedChunks; i++) {
const start = i * this.CHUNK_SIZE;
const end = Math.min(start + this.CHUNK_SIZE, file.size);
const chunk = file.slice(start, end);
const result = await this.uploadChunk(eventId, uploadId, i, chunk);
if (onProgress) {
onProgress(result.progress);
}
}
// Complete upload
const result = await this.completeChunkedUpload(eventId, uploadId, categoryId);
return result.photos;
} catch (error) {
// Abort on error
try {
await this.abortChunkedUpload(eventId, uploadId);
} catch (abortError) {
console.error('Failed to abort upload:', abortError);
}
throw error;
}
}
// Check if file should use chunked upload (> 100MB)
shouldUseChunkedUpload(fileSize: number): boolean {
return fileSize > 100 * 1024 * 1024; // 100MB threshold
}
}
export const photosService = new PhotosService();