کارهایی که بعد از این ارور باید انجام بدی ارور فعلی: فرانت هنوز دارد API قدیمی resumable را صدا می‌زند: /resumable/upload/start/ /resumable/upload//chunk/ /resumable/upload//finish/ ولی بک‌اند جدید روی API اصلی کار می‌کند: storage/upload/ پس فقط باید Upload.jsx را اصلاح کنی. ──────────────────────────── 1) این 3 تابع قدیمی را پاک کن ──────────────────────────── داخل: src/pages/Upload.jsx این‌ها را کامل حذف کن: const startUploadSession = async (item) => { const res = await Axiosinstance.post("/resumable/upload/start/", { filename: item.fileName, total_size: item.file.size, mime_type: item.file.type, chunk_size: item.chunkSize, total_chunks: item.totalChunks }); return res.data.session_id; }; const uploadChunk = async (item, chunkNumber) => { const start = chunkNumber * item.chunkSize; const end = Math.min(start + item.chunkSize, item.file.size); const blob = item.file.slice(start, end); const formData = new FormData(); formData.append("session_id", item.sessionId); formData.append("chunk_number", chunkNumber); formData.append("data", blob); await Axiosinstance.post( `resumable/upload/${item.sessionId}/chunk/`, formData ); }; const finishUpload = async (item) => { await Axiosinstance.post(`/resumable/upload/${item.sessionId}/finish/`, { session_id: item.sessionId }); }; ──────────────────────────── 2) این نسخه جدید را جایگزین کن ──────────────────────────── همان‌جا داخل Upload.jsx این کد را بگذار: const startUploadSession = async (item) => { const formData = new FormData(); formData.append("resumable_action", "start"); formData.append("filename", item.fileName); formData.append("total_size", String(item.file.size)); formData.append("mime_type", item.file.type || ""); formData.append("chunk_size", String(item.chunkSize)); formData.append("total_chunks", String(item.totalChunks)); const res = await Axiosinstance.post("storage/upload/", formData, { headers: { "Content-Type": "multipart/form-data", }, }); return res.data.session_id; }; const getUploadStatus = async (sessionId) => { const formData = new FormData(); formData.append("resumable_action", "status"); formData.append("session_id", sessionId); const res = await Axiosinstance.post("storage/upload/", formData, { headers: { "Content-Type": "multipart/form-data", }, }); return res.data; }; const uploadChunk = async (item, chunkNumber) => { const start = chunkNumber * item.chunkSize; const end = Math.min(start + item.chunkSize, item.file.size); const blob = item.file.slice(start, end); const formData = new FormData(); formData.append("resumable_action", "chunk"); formData.append("session_id", item.sessionId); formData.append("chunk_number", String(chunkNumber)); formData.append("chunk", blob, item.fileName); await Axiosinstance.post("storage/upload/", formData, { headers: { "Content-Type": "multipart/form-data", }, }); }; const finishUpload = async (item) => { const formData = new FormData(); formData.append("resumable_action", "finish"); formData.append("session_id", item.sessionId); formData.append("folder", item.folderId || ""); formData.append("description", item.description || ""); formData.append("tags", item.tags || ""); formData.append("encrypt", item.encrypt ? "true" : "false"); const res = await Axiosinstance.post("storage/upload/", formData, { headers: { "Content-Type": "multipart/form-data", }, }); return res.data; }; ──────────────────────────── 3) تابع uploadSingle را با این نسخه جایگزین کن ──────────────────────────── داخل Upload.jsx تابع فعلی uploadSingle را کامل پاک کن و این را بگذار: const uploadSingle = async (id) => { let item = queue.find((q) => q.id === id); if (!item) return; updateItem(id, { status: "uploading", paused: false, }); try { let sessionId = item.sessionId; if (!sessionId) { sessionId = await startUploadSession(item); updateItem(id, { sessionId, currentChunk: 0, progress: 0, loaded: 0, }); item = { ...item, sessionId, currentChunk: 0, }; } else { const statusData = await getUploadStatus(sessionId); const uploadedChunks = Array.isArray(statusData.uploaded_chunks) ? statusData.uploaded_chunks : []; const nextChunk = uploadedChunks.length ? Math.max(...uploadedChunks.map((n) => Number(n))) + 1 : item.currentChunk || 0; updateItem(id, { currentChunk: nextChunk, }); item = { ...item, currentChunk: nextChunk, }; } for (let i = item.currentChunk || 0; i < item.totalChunks; i++) { const latest = queue.find((q) => q.id === id); if (latest?.paused) { updateItem(id, { currentChunk: i, status: "uploading", }); return; } const activeItem = { ...item, ...latest, sessionId, }; await uploadChunk(activeItem, i); const uploadedBytes = Math.min( (i + 1) * item.chunkSize, item.file.size ); const percent = Math.round( (uploadedBytes / item.file.size) * 100 ); updateItem(id, { progress: percent, currentChunk: i + 1, loaded: uploadedBytes, }); } const finalItem = { ...item, sessionId, }; await finishUpload(finalItem); updateItem(id, { status: "success", progress: 100, loaded: item.file.size, paused: false, }); success(`فایل ${item.fileName} آپلود شد.`); } catch (err) { updateItem(id, { status: "error", paused: false, }); toastError(getErrMsg(err)); } }; ──────────────────────────── 4) تابع resumeUpload را اصلاح کن ──────────────────────────── تابع فعلی: const resumeUpload = (id) => { updateItem(id, { paused: false }); uploadSingle(id); }; به این تبدیل شود: const resumeUpload = (id) => { updateItem(id, { paused: false, status: "uploading", }); setTimeout(() => { uploadSingle(id); }, 100); }; ──────────────────────────── 5) مطمئن شو هیچ آدرس قدیمی باقی نمانده ──────────────────────────── داخل کل Upload.jsx سرچ کن: resumable/upload اگر چیزی پیدا شد، باید حذف شود. همچنین سرچ کن: /resumable نباید در Upload.jsx باقی مانده باشد. تنها آدرس درست باید این باشد: storage/upload/ ──────────────────────────── 6) پروژه فرانت را اجرا کن ──────────────────────────── داخل مسیر فرانت: npm run dev یا اگر build می‌گیری: npm run build اگر build خطا نداد، برو مرحله بعد. ──────────────────────────── 7) تست در مرورگر ──────────────────────────── Network tab را باز کن. یک فایل انتخاب کن و آپلود بزن. باید request اول این باشد: POST /api/storage/upload/ داخل FormData باید این‌ها باشد: resumable_action = start filename total_size mime_type chunk_size total_chunks بعد requestهای بعدی باید باز همین باشند: POST /api/storage/upload/ ولی با: resumable_action = chunk session_id chunk_number chunk در آخر هم: POST /api/storage/upload/ با: resumable_action = finish session_id folder description tags encrypt ──────────────────────────── 8) اگر 404 گرفتی ──────────────────────────── یعنی آدرس Axiosinstance اشتباه بسته شده. چک کن اگر Axiosinstance baseURL این شکلی است: baseURL: "https://domain.com/api/" پس درست است که بنویسی: Axiosinstance.post("storage/upload/", formData) ولی اگر baseURL فقط دامنه است: baseURL: "https://domain.com/" باید بنویسی: Axiosinstance.post("/api/storage/upload/", formData) با توجه به کدت که این‌ها کار می‌کنند: Axiosinstance.get("storage/items/") Axiosinstance.post("storage/folders/", payload) پس احتمالاً همین درست است: storage/upload/ ──────────────────────────── 9) اگر 400 گرفتی ──────────────────────────── Response body را نگاه کن. احتمال‌های رایج: session_id is required یعنی در chunk یا finish، sessionId داخل item ذخیره نشده. chunk_number and chunk file are required یعنی اسم فایل chunk اشتباه فرستاده شده. باید این باشد: formData.append("chunk", blob, item.fileName); filename, total_size, chunk_size, total_chunks are required یعنی start دارد JSON می‌رود یا FormData ناقص است. ──────────────────────────── 10) اگر 401 گرفتی ──────────────────────────── یعنی توکن Authorization در Axiosinstance ارسال نمی‌شود. چک کن requestهای storage/items کار می‌کنند یا نه. اگر storage/items کار می‌کند ولی upload نه، احتمالاً مشکل CSRF یا Content-Type است. ──────────────────────────── 11) تست pause/resume ──────────────────────────── یک فایل بزرگ انتخاب کن. آپلود را بزن. وسط کار توقف بزن. نباید session از بین برود. بعد ادامه بزن. باید دوباره status بگیرد و از chunk بعدی ادامه بدهد. ──────────────────────────── 12) نتیجه مورد انتظار ──────────────────────────── دیگر هیچ request نباید به این‌ها برود: /resumable/upload/start/ /resumable/upload//chunk/ /resumable/upload//finish/ همه چیز باید برود روی: POST /api/storage/upload/ با resumable_action. اگر این اتفاق افتاد، فرانت با بک‌اند جدید هماهنگ شده.