Upload a file to the workspace scratch/ folder. Accepts multipart/form-data with a 'file' field. Returns: { status: "success", data: { path, url } }
()
| 1148 | |
| 1149 | @agent_bp.route('/workspace/scratch/upload', methods=['POST']) |
| 1150 | def scratch_upload(): |
| 1151 | """Upload a file to the workspace scratch/ folder. |
| 1152 | |
| 1153 | Accepts multipart/form-data with a 'file' field. |
| 1154 | Returns: { status: "success", data: { path, url } } |
| 1155 | """ |
| 1156 | import hashlib |
| 1157 | from werkzeug.utils import secure_filename as _werkzeug_secure_filename |
| 1158 | |
| 1159 | if 'file' not in request.files: |
| 1160 | raise AppError(ErrorCode.INVALID_REQUEST, "No file in request") |
| 1161 | |
| 1162 | file = request.files['file'] |
| 1163 | if not file.filename: |
| 1164 | raise AppError(ErrorCode.INVALID_REQUEST, "No filename") |
| 1165 | |
| 1166 | identity_id = get_identity_id() |
| 1167 | workspace = get_workspace(identity_id) |
| 1168 | scratch_jail = workspace.confined_scratch |
| 1169 | |
| 1170 | raw = file.read() |
| 1171 | file_hash = hashlib.sha256(raw).hexdigest()[:8] |
| 1172 | safe_name = _werkzeug_secure_filename(file.filename) |
| 1173 | base, ext = os.path.splitext(safe_name) |
| 1174 | final_name = f"{base}_{file_hash}{ext}" |
| 1175 | |
| 1176 | try: |
| 1177 | dest = scratch_jail.resolve(final_name) |
| 1178 | except ValueError: |
| 1179 | raise AppError(ErrorCode.VALIDATION_ERROR, "Invalid filename") |
| 1180 | dest.write_bytes(raw) |
| 1181 | |
| 1182 | return json_ok({ |
| 1183 | "path": f"scratch/{final_name}", |
| 1184 | "url": f"/api/workspace/scratch/{final_name}", |
| 1185 | }) |
| 1186 | |
| 1187 | |
| 1188 | @agent_bp.route('/workspace/scratch/<path:filename>', methods=['GET']) |
nothing calls this directly
no test coverage detected