{"record":{"id":"8794ed2c5c239b4e","repo":"odysseus-dev/odysseus","slug":"no-image","errorCode":null,"errorMessage":"No image","messagePattern":"No image","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"routes/gallery/gallery_routes.py","lineNumber":568,"sourceCode":"                img_path.write_bytes(content)\n                img.file_hash = hashlib.sha256(content).hexdigest()\n                img.file_size = len(content)\n                img.width, img.height = rotated.size\n            db.commit()\n            return {\"ok\": True, \"width\": img.width, \"height\": img.height}\n        finally:\n            db.close()\n\n    # ---- POST /api/gallery/ai-upscale ----\n    @router.post(\"/api/gallery/ai-upscale\")\n    async def gallery_ai_upscale(request: Request):\n        \"\"\"AI upscale using img2img with the diffusion server.\"\"\"\n        import base64, httpx\n\n        user = require_privilege(request, \"can_generate_images\")\n        form = await request.form()\n        file = form.get(\"image\")\n        if not file: raise HTTPException(400, \"No image\")\n        scale = int(form.get(\"scale\", \"2\"))\n\n        image_bytes = await read_upload_limited(file, GALLERY_TRANSFORM_UPLOAD_MAX_BYTES, \"Image upload\")\n        b64 = base64.b64encode(image_bytes).decode()\n\n        # Find image endpoint\n        db = SessionLocal()\n        try:\n            ep = _first_visible_image_endpoint(db, user)\n        finally:\n            db.close()\n\n        if not ep:\n            raise HTTPException(400, \"No image generation endpoint configured. Add one in Settings → Add Models.\")\n\n        base_url = ep.base_url.rstrip(\"/\")\n        if not base_url.endswith(\"/v1\"):\n            base_url += \"/v1\"","sourceCodeStart":550,"sourceCodeEnd":586,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/gallery/gallery_routes.py#L550-L586","documentation":"HTTP 400 raised by POST /api/gallery/ai-upscale when the multipart form contains no 'image' field. Unlike the replace endpoint this check is only `if not file`, so a string value in the field would pass here and fail later in read_upload_limited; a wholly absent field fails immediately. The route also requires the can_generate_images privilege first.","triggerScenarios":"POST /api/gallery/ai-upscale with an empty form, a JSON body, or a FormData missing an 'image' part.","commonSituations":"Frontend reusing a FormData built for a different endpoint; file picker cleared before submit; curl without -F.","solutions":["Append the file as 'image' in multipart/form-data.","Validate the file is selected before enabling the upscale button.","Note the privilege gate: the user must have can_generate_images or the request fails earlier with 403."],"exampleFix":"// before\nfd.append('img', file); // wrong name -> 400\n// after\nfd.append('image', file); fd.append('scale', '2');","handlingStrategy":"validation","validationCode":"if (!file) return showError('Choose an image to upscale');\nconst fd = new FormData(); fd.append('image', file); fd.append('scale', '2');","typeGuard":"const hasImagePart = (fd) => fd.get('image') != null;","tryCatchPattern":"try { await upscale(fd); } catch (e) { if (e.status === 400) showError('Attach the image in the \"image\" field'); }","preventionTips":["Reuse a single correctly-keyed FormData helper for all transform endpoints","Disable action until a file is picked","Remember can_generate_images is checked first (403 otherwise)"],"tags":["http","validation","gallery","upscale","multipart"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}