{"record":{"id":"685103a33424e540","repo":"Crosstalk-Solutions/project-nomad","slug":"upload-failed","errorCode":null,"errorMessage":"Upload failed","messagePattern":"Upload failed","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"admin/app/controllers/zim_controller.ts","lineNumber":168,"sourceCode":"      }\n      if (!filename) {\n        return response.status(400).send({ message: 'No file received' })\n      }\n\n      const { added } = await this.zimService.registerLocalUpload(filename)\n\n      return response.status(201).send({\n        message: 'ZIM file uploaded and registered successfully',\n        filename,\n        added,\n      })\n    } catch (error) {\n      logger.error('[ZimController] Upload failed:', error)\n      if (tmpPath) {\n        const { unlink } = await import('fs/promises')\n        await unlink(tmpPath).catch(() => {})\n      }\n      return response.status(500).send({ message: 'Upload failed' })\n    }\n  }\n\n  // Wikipedia selector endpoints\n\n  async getWikipediaState({}: HttpContext) {\n    return this.zimService.getWikipediaState()\n  }\n\n  async selectWikipedia({ request }: HttpContext) {\n    const payload = await request.validateUsing(selectWikipediaValidator)\n    return this.zimService.selectWikipedia(payload.optionId)\n  }\n\n  // Custom library endpoints\n\n  async listCustomLibraries({}: HttpContext) {\n    return this.zimService.listCustomLibraries()","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/controllers/zim_controller.ts#L150-L186","documentation":"Generic 500 returned by the ZimController upload endpoint whenever the multi-part ZIM file upload fails for any reason (disk write error, missing file part, size limit, service-layer failure). The controller cleans up its temp file, logs the real cause via logger.error, but deliberately hides it behind 'Upload failed'.","triggerScenarios":"POST to the ZIM upload route with a malformed/missing file part, a file exceeding body size limits, a full/readonly tmp directory, or when zimService throws during processing (e.g. invalid ZIM file).","commonSituations":"adonis bodyparser maxFileSize too small for multi-GB ZIM files; tmp dir not writable in container; client sending wrong field name for the file; corrupted or truncated ZIM upload over flaky connection.","solutions":["Check server logs for the '[ZimController] Upload failed:' line — the real underlying error is logged there","Verify the adonisjs bodyparser config allows large files (request multipart file size limits) and that the client sends the file under the expected field name","Ensure the tmp directory the controller writes to exists and is writable (volume mounted, correct permissions in Docker)","Retry with a smaller/known-good ZIM file to isolate payload corruption from environment issues"],"exampleFix":"// config/bodyparser.ts — allow large ZIM uploads\n// before\nfileTypes: {},\n// after\nfile: {\n  maxFileSize: '8gb',\n},","handlingStrategy":"try-catch","validationCode":"// before upload: check file exists client-side\nif (!file || file.size === 0) throw new Error('Empty file')\nif (!file.name.endsWith('.zim')) throw new Error('Expected a .zim file')","typeGuard":"const isFilePart = (p: unknown): p is { file: File; name: string } =>\n  !!p && typeof p === 'object' && p.file instanceof File && typeof p.name === 'string'","tryCatchPattern":"try {\n  const res = await fetch('/admin/zim/upload', { method: 'POST', body: formData })\n  if (!res.ok) throw new Error(`Upload failed (${res.status})`)\n} catch (e) {\n  // surface to UI; check server logs for '[ZimController] Upload failed:' detail\n  notifyUser('Upload failed — see server logs for details')\n}","preventionTips":["Check server logs first — the 500 hides the real cause","Pre-validate file size against bodyparser limits before uploading","Stream large ZIM files instead of buffering in tmp when possible"],"tags":["upload","file-upload","zim","http-500","multipart"],"backgroundTag":"file-upload-failed","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}