diff --git a/apps/server/src/modules/filesystem/controller.ts b/apps/server/src/modules/filesystem/controller.ts index 3a789da..00b7791 100644 --- a/apps/server/src/modules/filesystem/controller.ts +++ b/apps/server/src/modules/filesystem/controller.ts @@ -98,43 +98,82 @@ export class FilesystemController { } catch (error) { try { await fs.promises.unlink(tempPath); - } catch (error) { - console.error("Error deleting temp file:", error); + } catch (cleanupError) { + console.error("Error deleting temp file:", cleanupError); } throw error; } } private async uploadSmallFile(request: FastifyRequest, provider: FilesystemStorageProvider, objectName: string) { - const stream = request.body as any; - const chunks: Buffer[] = []; + const body = request.body as any; - return new Promise((resolve, reject) => { - stream.on("data", (chunk: Buffer) => { - chunks.push(chunk); - }); + if (Buffer.isBuffer(body)) { + if (body.length === 0) { + throw new Error("No file data received"); + } + await provider.uploadFile(objectName, body); + return; + } - stream.on("end", async () => { - try { - const buffer = Buffer.concat(chunks); + if (typeof body === "string") { + const buffer = Buffer.from(body, "utf8"); + if (buffer.length === 0) { + throw new Error("No file data received"); + } + await provider.uploadFile(objectName, buffer); + return; + } - if (buffer.length === 0) { - throw new Error("No file data received"); + if (typeof body === "object" && body !== null && !body.on) { + const buffer = Buffer.from(JSON.stringify(body), "utf8"); + if (buffer.length === 0) { + throw new Error("No file data received"); + } + await provider.uploadFile(objectName, buffer); + return; + } + + if (body && typeof body.on === "function") { + const chunks: Buffer[] = []; + + return new Promise((resolve, reject) => { + body.on("data", (chunk: Buffer) => { + chunks.push(chunk); + }); + + body.on("end", async () => { + try { + const buffer = Buffer.concat(chunks); + + if (buffer.length === 0) { + throw new Error("No file data received"); + } + + await provider.uploadFile(objectName, buffer); + resolve(); + } catch (error) { + console.error("Error uploading small file:", error); + reject(error); } + }); - await provider.uploadFile(objectName, buffer); - resolve(); - } catch (error) { - console.error("Error uploading small file:", error); + body.on("error", (error: Error) => { + console.error("Error reading upload stream:", error); reject(error); - } + }); }); + } - stream.on("error", (error: Error) => { - console.error("Error reading upload stream:", error); - reject(error); - }); - }); + try { + const buffer = Buffer.from(body); + if (buffer.length === 0) { + throw new Error("No file data received"); + } + await provider.uploadFile(objectName, buffer); + } catch (error) { + throw new Error(`Unsupported request body type: ${typeof body}. Expected stream, buffer, string, or object.`); + } } async download(request: FastifyRequest, reply: FastifyReply) { diff --git a/apps/server/src/modules/filesystem/routes.ts b/apps/server/src/modules/filesystem/routes.ts index 1958811..1fb2f67 100644 --- a/apps/server/src/modules/filesystem/routes.ts +++ b/apps/server/src/modules/filesystem/routes.ts @@ -9,6 +9,10 @@ export async function filesystemRoutes(app: FastifyInstance) { return payload; }); + app.addContentTypeParser("application/json", async (request: FastifyRequest, payload: any) => { + return payload; + }); + app.put( "/filesystem/upload/:token", { diff --git a/apps/web/messages/ar-SA.json b/apps/web/messages/ar-SA.json index 069ff7d..2e7c630 100644 --- a/apps/web/messages/ar-SA.json +++ b/apps/web/messages/ar-SA.json @@ -758,6 +758,7 @@ "uploadProgress": "تقدم الرفع", "upload": "رفع", "startUploads": "بدء الرفع", + "retry": "إعادة المحاولة", "finish": "إنهاء", "success": "تم رفع الملف بنجاح", "allSuccess": "{count, plural, =1 {تم رفع الملف بنجاح} other {تم رفع # ملف بنجاح}}", @@ -1280,6 +1281,7 @@ "linkInactive": "هذا الرابط غير نشط.", "linkExpired": "هذا الرابط منتهي الصلاحية.", "uploadFailed": "خطأ في رفع الملف", + "retry": "إعادة المحاولة", "fileTooLarge": "الملف كبير جداً. الحجم الأقصى: {maxSize}", "fileTypeNotAllowed": "نوع الملف غير مسموح به. الأنواع المقبولة: {allowedTypes}", "maxFilesExceeded": "الحد الأقصى المسموح به هو {maxFiles} ملف/ملفات", diff --git a/apps/web/messages/de-DE.json b/apps/web/messages/de-DE.json index 7e7563c..1fdbbb9 100644 --- a/apps/web/messages/de-DE.json +++ b/apps/web/messages/de-DE.json @@ -758,6 +758,7 @@ "uploadProgress": "Upload-Fortschritt", "upload": "Hochladen", "startUploads": "Uploads Starten", + "retry": "Wiederholen", "finish": "Beenden", "success": "Datei erfolgreich hochgeladen", "allSuccess": "{count, plural, =1 {Datei erfolgreich hochgeladen} other {# Dateien erfolgreich hochgeladen}}", @@ -1280,6 +1281,7 @@ "linkInactive": "Dieser Link ist inaktiv.", "linkExpired": "Dieser Link ist abgelaufen.", "uploadFailed": "Fehler beim Hochladen der Datei", + "retry": "Wiederholen", "fileTooLarge": "Datei zu groß. Maximale Größe: {maxSize}", "fileTypeNotAllowed": "Dateityp nicht erlaubt. Erlaubte Typen: {allowedTypes}", "maxFilesExceeded": "Maximal {maxFiles} Dateien erlaubt", diff --git a/apps/web/messages/en-US.json b/apps/web/messages/en-US.json index b4cf177..e674028 100644 --- a/apps/web/messages/en-US.json +++ b/apps/web/messages/en-US.json @@ -816,6 +816,7 @@ "uploadProgress": "Upload progress", "upload": "Upload", "startUploads": "Start Uploads", + "retry": "Retry", "finish": "Finish", "success": "File uploaded successfully", "allSuccess": "{count, plural, =1 {File uploaded successfully} other {# files uploaded successfully}}", @@ -1280,6 +1281,7 @@ "linkInactive": "This link is inactive.", "linkExpired": "This link has expired.", "uploadFailed": "Error uploading file", + "retry": "Retry", "fileTooLarge": "File too large. Maximum size: {maxSize}", "fileTypeNotAllowed": "File type not allowed. Accepted types: {allowedTypes}", "maxFilesExceeded": "Maximum of {maxFiles} files allowed", diff --git a/apps/web/messages/es-ES.json b/apps/web/messages/es-ES.json index 01154a4..7ac1083 100644 --- a/apps/web/messages/es-ES.json +++ b/apps/web/messages/es-ES.json @@ -758,6 +758,7 @@ "uploadProgress": "Progreso de la subida", "upload": "Subir", "startUploads": "Iniciar Subidas", + "retry": "Reintentar", "finish": "Finalizar", "success": "Archivo subido exitosamente", "allSuccess": "{count, plural, =1 {Archivo subido exitosamente} other {# archivos subidos exitosamente}}", @@ -1280,6 +1281,7 @@ "linkInactive": "Este enlace está inactivo.", "linkExpired": "Este enlace ha expirado.", "uploadFailed": "Error al subir archivo", + "retry": "Reintentar", "fileTooLarge": "Archivo demasiado grande. Tamaño máximo: {maxSize}", "fileTypeNotAllowed": "Tipo de archivo no permitido. Tipos aceptados: {allowedTypes}", "maxFilesExceeded": "Máximo de {maxFiles} archivos permitidos", diff --git a/apps/web/messages/fr-FR.json b/apps/web/messages/fr-FR.json index 0598be4..c38871f 100644 --- a/apps/web/messages/fr-FR.json +++ b/apps/web/messages/fr-FR.json @@ -758,6 +758,7 @@ "uploadProgress": "Progression du téléchargement", "upload": "Télécharger", "startUploads": "Commencer les Téléchargements", + "retry": "Réessayer", "finish": "Terminer", "success": "Fichier téléchargé avec succès", "allSuccess": "{count, plural, =1 {Fichier téléchargé avec succès} other {# fichiers téléchargés avec succès}}", @@ -1280,6 +1281,7 @@ "linkInactive": "Ce lien est inactif.", "linkExpired": "Ce lien a expiré.", "uploadFailed": "Erreur lors de l'envoi du fichier", + "retry": "Réessayer", "fileTooLarge": "Fichier trop volumineux. Taille maximale : {maxSize}", "fileTypeNotAllowed": "Type de fichier non autorisé. Types acceptés : {allowedTypes}", "maxFilesExceeded": "Maximum de {maxFiles} fichiers autorisés", diff --git a/apps/web/messages/hi-IN.json b/apps/web/messages/hi-IN.json index f679e14..f168f5b 100644 --- a/apps/web/messages/hi-IN.json +++ b/apps/web/messages/hi-IN.json @@ -758,6 +758,7 @@ "uploadProgress": "अपलोड प्रगति", "upload": "अपलोड", "startUploads": "अपलोड शुरू करें", + "retry": "पुनः प्रयास करें", "finish": "समाप्त", "success": "फ़ाइल सफलतापूर्वक अपलोड की गई", "allSuccess": "{count, plural, =1 {फ़ाइल सफलतापूर्वक अपलोड की गई} other {# फ़ाइलें सफलतापूर्वक अपलोड की गईं}}", @@ -1280,6 +1281,7 @@ "linkInactive": "यह लिंक निष्क्रिय है।", "linkExpired": "यह लिंक समाप्त हो गया है।", "uploadFailed": "फ़ाइल अपलोड करने में त्रुटि", + "retry": "पुनः प्रयास करें", "fileTooLarge": "फ़ाइल बहुत बड़ी है। अधिकतम आकार: {maxSize}", "fileTypeNotAllowed": "फ़ाइल प्रकार अनुमत नहीं है। स्वीकृत प्रकार: {allowedTypes}", "maxFilesExceeded": "अधिकतम {maxFiles} फ़ाइलें अनुमत हैं", diff --git a/apps/web/messages/it-IT.json b/apps/web/messages/it-IT.json index f13ea2d..23fee47 100644 --- a/apps/web/messages/it-IT.json +++ b/apps/web/messages/it-IT.json @@ -758,6 +758,7 @@ "uploadProgress": "Progresso caricamento", "upload": "Carica", "startUploads": "Inizia Caricamenti", + "retry": "Riprova", "finish": "Termina", "success": "File caricato con successo", "allSuccess": "{count, plural, =1 {File caricato con successo} other {# file caricati con successo}}", @@ -1280,6 +1281,7 @@ "linkInactive": "Questo link è inattivo.", "linkExpired": "Questo link è scaduto.", "uploadFailed": "Errore durante l'invio del file", + "retry": "Riprova", "fileTooLarge": "File troppo grande. Dimensione massima: {maxSize}", "fileTypeNotAllowed": "Tipo di file non consentito. Tipi accettati: {allowedTypes}", "maxFilesExceeded": "Massimo {maxFiles} file consentiti", diff --git a/apps/web/messages/ja-JP.json b/apps/web/messages/ja-JP.json index 861789d..ae19629 100644 --- a/apps/web/messages/ja-JP.json +++ b/apps/web/messages/ja-JP.json @@ -771,6 +771,7 @@ }, "multipleTitle": "複数ファイルをアップロード", "startUploads": "アップロードを開始", + "retry": "再試行", "allSuccess": "{count, plural, =1 {ファイルがアップロードされました} other {#個のファイルがアップロードされました}}", "partialSuccess": "{success}個のファイルがアップロードされ、{error}個が失敗しました", "dragAndDrop": "またはここにファイルをドラッグ&ドロップ" @@ -1280,6 +1281,7 @@ "linkInactive": "このリンクは無効です。", "linkExpired": "このリンクは期限切れです。", "uploadFailed": "ファイルのアップロードに失敗しました", + "retry": "再試行", "fileTooLarge": "ファイルが大きすぎます。最大サイズ: {maxSize}", "fileTypeNotAllowed": "このファイル形式は許可されていません。許可される形式: {allowedTypes}", "maxFilesExceeded": "最大 {maxFiles} ファイルまで許可されています", diff --git a/apps/web/messages/ko-KR.json b/apps/web/messages/ko-KR.json index e487704..e60fd68 100644 --- a/apps/web/messages/ko-KR.json +++ b/apps/web/messages/ko-KR.json @@ -758,6 +758,7 @@ "uploadProgress": "업로드 진행률", "upload": "업로드", "startUploads": "업로드 시작", + "retry": "다시 시도", "finish": "완료", "success": "파일이 성공적으로 업로드되었습니다", "allSuccess": "{count, plural, =1 {파일이 성공적으로 업로드되었습니다} other {# 개 파일이 성공적으로 업로드되었습니다}}", @@ -1280,6 +1281,7 @@ "linkInactive": "이 링크는 비활성 상태입니다.", "linkExpired": "이 링크는 만료되었습니다.", "uploadFailed": "파일 업로드 오류", + "retry": "다시 시도", "fileTooLarge": "파일이 너무 큽니다. 최대 크기: {maxSize}", "fileTypeNotAllowed": "허용되지 않는 파일 유형입니다. 허용된 유형: {allowedTypes}", "maxFilesExceeded": "최대 {maxFiles}개의 파일만 허용됩니다", diff --git a/apps/web/messages/nl-NL.json b/apps/web/messages/nl-NL.json index fca8d7a..f7ac50c 100644 --- a/apps/web/messages/nl-NL.json +++ b/apps/web/messages/nl-NL.json @@ -758,6 +758,7 @@ "uploadProgress": "Upload voortgang", "upload": "Uploaden", "startUploads": "Uploads Starten", + "retry": "Opnieuw Proberen", "finish": "Voltooien", "success": "Bestand succesvol geüpload", "allSuccess": "{count, plural, =1 {Bestand succesvol geüpload} other {# bestanden succesvol geüpload}}", @@ -1280,6 +1281,7 @@ "linkInactive": "Deze link is inactief.", "linkExpired": "Deze link is verlopen.", "uploadFailed": "Fout bij uploaden bestand", + "retry": "Opnieuw Proberen", "fileTooLarge": "Bestand te groot. Maximum grootte: {maxSize}", "fileTypeNotAllowed": "Bestandstype niet toegestaan. Toegestane types: {allowedTypes}", "maxFilesExceeded": "Maximum van {maxFiles} bestanden toegestaan", diff --git a/apps/web/messages/pl-PL.json b/apps/web/messages/pl-PL.json index eb20a0b..98fc716 100644 --- a/apps/web/messages/pl-PL.json +++ b/apps/web/messages/pl-PL.json @@ -816,6 +816,7 @@ "uploadProgress": "Postęp przesyłania", "upload": "Prześlij", "startUploads": "Rozpocznij przesyłanie", + "retry": "Spróbuj Ponownie", "finish": "Zakończ", "success": "Plik przesłany pomyślnie", "allSuccess": "{count, plural, =1 {Plik przesłany pomyślnie} other {# plików przesłanych pomyślnie}}", @@ -1280,6 +1281,7 @@ "linkInactive": "Ten link jest nieaktywny.", "linkExpired": "Ten link wygasł.", "uploadFailed": "Błąd przesyłania pliku", + "retry": "Spróbuj Ponownie", "fileTooLarge": "Plik za duży. Maksymalny rozmiar: {maxSize}", "fileTypeNotAllowed": "Typ pliku niedozwolony. Akceptowane typy: {allowedTypes}", "maxFilesExceeded": "Dozwolono maksymalnie {maxFiles} plików", diff --git a/apps/web/messages/pt-BR.json b/apps/web/messages/pt-BR.json index 031cc3b..363f9a3 100644 --- a/apps/web/messages/pt-BR.json +++ b/apps/web/messages/pt-BR.json @@ -780,6 +780,7 @@ "uploadProgress": "Progresso do upload", "upload": "Enviar", "startUploads": "Iniciar Uploads", + "retry": "Tentar Novamente", "finish": "Concluir", "success": "Arquivo enviado com sucesso", "allSuccess": "{count, plural, =1 {Arquivo enviado com sucesso} other {# arquivos enviados com sucesso}}", @@ -1276,6 +1277,7 @@ "linkInactive": "Este link está inativo.", "linkExpired": "Este link expirou.", "uploadFailed": "Erro ao enviar arquivo", + "retry": "Tentar Novamente", "fileTooLarge": "Arquivo muito grande. Tamanho máximo: {maxSize}", "fileTypeNotAllowed": "Tipo de arquivo não permitido. Tipos aceitos: {allowedTypes}", "maxFilesExceeded": "Máximo de {maxFiles} arquivos permitidos", diff --git a/apps/web/messages/ru-RU.json b/apps/web/messages/ru-RU.json index e4b91eb..5f48a37 100644 --- a/apps/web/messages/ru-RU.json +++ b/apps/web/messages/ru-RU.json @@ -758,6 +758,7 @@ "uploadProgress": "Прогресс загрузки", "upload": "Загрузить", "startUploads": "Начать Загрузку", + "retry": "Повторить", "finish": "Завершить", "success": "Файл успешно загружен", "allSuccess": "{count, plural, =1 {Файл успешно загружен} other {# файлов успешно загружено}}", @@ -1280,6 +1281,7 @@ "linkInactive": "Эта ссылка неактивна.", "linkExpired": "Срок действия этой ссылки истек.", "uploadFailed": "Ошибка при загрузке файла", + "retry": "Повторить", "fileTooLarge": "Файл слишком большой. Максимальный размер: {maxSize}", "fileTypeNotAllowed": "Тип файла не разрешен. Разрешенные типы: {allowedTypes}", "maxFilesExceeded": "Максимально разрешено {maxFiles} файлов", diff --git a/apps/web/messages/tr-TR.json b/apps/web/messages/tr-TR.json index 9cfa853..760a81c 100644 --- a/apps/web/messages/tr-TR.json +++ b/apps/web/messages/tr-TR.json @@ -758,6 +758,7 @@ "uploadProgress": "Yükleme ilerlemesi", "upload": "Yükle", "startUploads": "Yüklemeleri Başlat", + "retry": "Tekrar Dene", "finish": "Bitir", "success": "Dosya başarıyla yüklendi", "allSuccess": "{count, plural, =1 {Dosya başarıyla yüklendi} other {# dosya başarıyla yüklendi}}", @@ -1280,6 +1281,7 @@ "linkInactive": "Bu bağlantı pasif durumda.", "linkExpired": "Bu bağlantının süresi doldu.", "uploadFailed": "Dosya yüklenirken hata oluştu", + "retry": "Tekrar Dene", "fileTooLarge": "Dosya çok büyük. Maksimum boyut: {maxSize}", "fileTypeNotAllowed": "Dosya türüne izin verilmiyor. İzin verilen türler: {allowedTypes}", "maxFilesExceeded": "Maksimum {maxFiles} dosyaya izin veriliyor", diff --git a/apps/web/messages/zh-CN.json b/apps/web/messages/zh-CN.json index e7e66db..99a92ca 100644 --- a/apps/web/messages/zh-CN.json +++ b/apps/web/messages/zh-CN.json @@ -758,6 +758,7 @@ "uploadProgress": "上传进度", "upload": "上传", "startUploads": "开始上传", + "retry": "重试", "finish": "完成", "success": "文件上传成功", "allSuccess": "{count, plural, =1 {文件上传成功} other {# 个文件上传成功}}", @@ -1280,6 +1281,7 @@ "linkInactive": "此链接已停用。", "linkExpired": "此链接已过期。", "uploadFailed": "上传文件时出错", + "retry": "重试", "fileTooLarge": "文件太大。最大大小:{maxSize}", "fileTypeNotAllowed": "不允许的文件类型。允许的类型:{allowedTypes}", "maxFilesExceeded": "最多允许 {maxFiles} 个文件", diff --git a/apps/web/src/app/(shares)/r/[alias]/components/file-upload-section.tsx b/apps/web/src/app/(shares)/r/[alias]/components/file-upload-section.tsx index 1708a78..3076185 100644 --- a/apps/web/src/app/(shares)/r/[alias]/components/file-upload-section.tsx +++ b/apps/web/src/app/(shares)/r/[alias]/components/file-upload-section.tsx @@ -331,6 +331,28 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce )} + {fileWithProgress.status === FILE_STATUS.ERROR && ( +
+ + +
+ )} ); diff --git a/apps/web/src/components/modals/file-preview-modal.tsx b/apps/web/src/components/modals/file-preview-modal.tsx index 2d427bd..a2590a5 100644 --- a/apps/web/src/components/modals/file-preview-modal.tsx +++ b/apps/web/src/components/modals/file-preview-modal.tsx @@ -32,6 +32,7 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp const [downloadUrl, setDownloadUrl] = useState(null); const [pdfLoadFailed, setPdfLoadFailed] = useState(false); const [isLoadingPreview, setIsLoadingPreview] = useState(false); + const [jsonContent, setJsonContent] = useState(null); useEffect(() => { if (isOpen && file.objectName && !isLoadingPreview) { @@ -41,6 +42,7 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp setPdfAsBlob(false); setDownloadUrl(null); setPdfLoadFailed(false); + setJsonContent(null); loadPreview(); } }, [file.objectName, isOpen]); @@ -88,6 +90,8 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp await loadAudioPreview(url); } else if (fileType === "pdf") { await loadPdfPreview(url); + } else if (fileType === "json") { + await loadJsonPreview(url); } else { setPreviewUrl(url); } @@ -155,6 +159,29 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp } }; + const loadJsonPreview = async (url: string) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const text = await response.text(); + try { + // Validate and format JSON + const parsed = JSON.parse(text); + const formatted = JSON.stringify(parsed, null, 2); + setJsonContent(formatted); + } catch (jsonError) { + // If it's not valid JSON, show as plain text + setJsonContent(text); + } + } catch (error) { + console.error("Failed to load JSON:", error); + setJsonContent(null); + } + }; + const handlePdfLoadError = async () => { if (pdfLoadFailed || pdfAsBlob) return; @@ -193,6 +220,7 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp const extension = file.name.split(".").pop()?.toLowerCase(); if (extension === "pdf") return "pdf"; + if (extension === "json") return "json"; if (["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp", "tiff"].includes(extension || "")) return "image"; if (["mp3", "wav", "ogg", "m4a", "aac", "flac"].includes(extension || "")) return "audio"; if (["mp4", "webm", "ogg", "mov", "avi", "mkv", "wmv", "flv", "m4v"].includes(extension || "")) return "video"; @@ -225,7 +253,18 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp ); } - if (!previewUrl && fileType !== "video") { + // For JSON files, we don't need previewUrl, we use jsonContent instead + if (fileType === "json" && !jsonContent && !isLoading) { + return ( +
+ +

{t("filePreview.notAvailable")}

+

{t("filePreview.downloadToView")}

+
+ ); + } + + if (!previewUrl && fileType !== "video" && fileType !== "json") { return (
@@ -275,6 +314,25 @@ export function FilePreviewModal({ isOpen, onClose, file }: FilePreviewModalProp
); + case "json": + return ( + +
+ {jsonContent ? ( +
+                  {jsonContent}
+                
+ ) : ( +
+
+
+

{t("filePreview.loading")}

+
+
+ )} +
+ + ); case "image": return ( diff --git a/apps/web/src/components/modals/upload-file-modal.tsx b/apps/web/src/components/modals/upload-file-modal.tsx index 1f99dd6..3237707 100644 --- a/apps/web/src/components/modals/upload-file-modal.tsx +++ b/apps/web/src/components/modals/upload-file-modal.tsx @@ -453,7 +453,33 @@ export function UploadFileModal({ isOpen, onClose, onSuccess }: UploadFileModalP > - ) : upload.status === UploadStatus.SUCCESS ? null : ( + ) : upload.status === UploadStatus.SUCCESS ? null : upload.status === UploadStatus.ERROR ? ( +
+ + +
+ ) : (