From 90143f7dc099d0c0ea66bab696ce45e065daefb2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 12 Nov 2025 21:44:27 +0000 Subject: [PATCH] Add attachment deletion route and automatic cleanup on record deletion Co-authored-by: aiulian25 <17886483+aiulian25@users.noreply.github.com> --- ATTACHMENT_API.md | 35 +++++++++++++++++++++++++++++++++++ backend/app.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/ATTACHMENT_API.md b/ATTACHMENT_API.md index 878e3bb..bfd19d6 100644 --- a/ATTACHMENT_API.md +++ b/ATTACHMENT_API.md @@ -85,6 +85,41 @@ Download or view an attachment. --- +### Delete Attachment +Delete an attachment file. + +**Endpoint:** `DELETE /api/attachments/delete` + +**Authentication:** Required + +**Query Parameters:** +- `path` (required): Relative path to the attachment (e.g., `attachments/filename.pdf`) + +**Response:** +```json +{ + "message": "Attachment deleted successfully" +} +``` + +**Error Responses:** +- 400: No file path provided +- 403: Invalid file path (security violation) +- 404: File not found +- 500: Server error during deletion + +**Example:** +```javascript +await fetch(`/api/attachments/delete?path=${encodeURIComponent(filePath)}`, { + method: 'DELETE', + credentials: 'include' +}); +``` + +**Note:** When deleting a fuel record, service record, or recurring expense, the associated attachment is automatically deleted. + +--- + ## Data Models ### FuelRecord diff --git a/backend/app.py b/backend/app.py index c704614..cde4410 100644 --- a/backend/app.py +++ b/backend/app.py @@ -418,6 +418,29 @@ def download_attachment(): return send_from_directory(directory, filename, as_attachment=True) +@app.route('/api/attachments/delete', methods=['DELETE']) +@login_required +def delete_attachment(): + """Delete an attachment file. Only the file owner can delete it.""" + file_path = request.args.get('path') + + if not file_path: + return jsonify({'error': 'No file path provided'}), 400 + + full_path = os.path.join('/app/uploads', file_path) + + if not full_path.startswith('/app/uploads'): + return jsonify({'error': 'Invalid file path'}), 403 + + if not os.path.exists(full_path): + return jsonify({'error': 'File not found'}), 404 + + try: + os.remove(full_path) + return jsonify({'message': 'Attachment deleted successfully'}), 200 + except Exception as e: + return jsonify({'error': f'Failed to delete attachment: {str(e)}'}), 500 + @app.route('/api/vehicles/', methods=['GET', 'PUT', 'DELETE']) @login_required def vehicle_detail(vehicle_id): @@ -827,6 +850,15 @@ def service_record_operations(vehicle_id, record_id): return jsonify({'message': 'Service record updated successfully'}) elif request.method == 'DELETE': + # Clean up attachment if it exists + if record.document_path: + try: + full_path = os.path.join('/app/uploads', record.document_path) + if os.path.exists(full_path): + os.remove(full_path) + except Exception as e: + print(f"Failed to delete attachment: {e}") + db.session.delete(record) db.session.commit() return jsonify({'message': 'Service record deleted successfully'}) @@ -861,6 +893,15 @@ def fuel_record_operations(vehicle_id, record_id): return jsonify({'message': 'Fuel record updated successfully'}) elif request.method == 'DELETE': + # Clean up attachment if it exists + if record.document_path: + try: + full_path = os.path.join('/app/uploads', record.document_path) + if os.path.exists(full_path): + os.remove(full_path) + except Exception as e: + print(f"Failed to delete attachment: {e}") + db.session.delete(record) db.session.commit() return jsonify({'message': 'Fuel record deleted successfully'})