Compare commits

...

7 Commits

3 changed files with 154 additions and 145 deletions
+117 -131
View File
@@ -388,6 +388,8 @@ ALLOWED_COVER_DOMAINS = {
"www.googleapis.com"
}
SENSITIVE_AUDIT_FIELDS = ["email", "username", "full_name", "phone", "borrower", "ip"]
# Apply the configuration for general use throughout the app
APP_VERSION = __version__
RELEASE_STATE_FILE = os.path.join(os.path.dirname(BASE_DIR), '.release-version')
@@ -658,7 +660,7 @@ def _csrf_error_response(message='CSRF token fehlt oder ist ungültig.'):
if request.is_json or request.path.startswith('/api/') or request.path in {'/download_book_cover', '/proxy_image', '/log_mobile_issue'}:
return jsonify({'error': message}), 400
flash(message, 'error')
return redirect(request.referrer or url_for('home'))
return redirect(url_for('login'))
def _get_current_module(path):
"""Resolve the active UI module for navbar separation."""
@@ -726,9 +728,9 @@ def _append_audit_event_standalone(event_type, payload):
al.append_audit_event(
db=db,
event_type=event_type,
actor=encrypt_text(session.get('username', 'system')),
payload=encrypt_text(str(payload)),
request_ip=encrypt_text(request.remote_addr),
actor=session.get('username', 'system'),
payload=str(payload),
request_ip=request.remote_addr,
source='web',
)
except Exception as exc:
@@ -1633,7 +1635,7 @@ def allowed_file(filename, file_content=None, max_size_mb=cfg.MAX_UPLOAD_MB):
file_content.seek(0) # Reset file pointer after reading
except Exception as e:
error_msg = f"Error validating image content for {filename}: {str(e)}"
error_msg = f"Error validating image content for {filename}"
app.logger.error(error_msg)
if extension == 'png':
@@ -1673,9 +1675,9 @@ def allowed_file(filename, file_content=None, max_size_mb=cfg.MAX_UPLOAD_MB):
except Exception as raw_err:
app.logger.error(f"PNG DEBUG: Error during raw file analysis: {str(raw_err)}")
traceback.print_exc()
return False, f"Datei '{filename}' konnte nicht als Bild erkannt werden. Fehler: {str(e)}"
return False, f"Datei '{filename}' konnte nicht als Bild erkannt werden. "
# Add more content type validations as needed for other file types
@@ -2833,8 +2835,8 @@ def catch_all_files(filename):
# If we get here, the file wasn't found
return Response(f"File {filename} not found", status=404)
except Exception as e:
print(f"Error in catch-all route for {filename}: {str(e)}")
return Response(f"Error serving file: {str(e)}", status=500)
app.logger.error(f"Error in catch-all route for {filename}: {str(e)}")
return Response(f"Error serving file {filename}", status=500)
"""-------------------------------------------------------------Main Views-----------------------------------------------------------------------------"""
@@ -3348,7 +3350,7 @@ def api_library_items():
}), 200
except Exception as e:
app.logger.error(f"Error fetching library items: {e}")
return jsonify({'error': str(e)}), 500
return jsonify({'error': 'An error occurred while fetching library items'}), 500
@app.route('/api/library_scan_action', methods=['POST'])
@@ -3613,7 +3615,7 @@ def api_item_detail(item_id):
return detail_html, 200
except Exception as e:
app.logger.error(f"Error fetching item detail: {e}")
return jsonify({'error': str(e)}), 500
return jsonify({'error': 'An error occurred while fetching the item detail'}), 500
@app.route('/api/library_item/<item_id>/update', methods=['POST'])
@@ -4227,7 +4229,8 @@ def student_card_barcode_download():
download_name=f'schuelerausweise_all_{datetime.datetime.now().strftime("%Y%m%d_%H%M%S")}.pdf'
)
except Exception as e:
flash(f'Fehler beim PDF-Download: {str(e)}', 'error')
app.logger.error(f"Error occurred while generating PDF for card {card['AusweisId']}: {e}")
flash('Fehler beim PDF-Download', 'error')
return redirect(url_for('student_cards_admin'))
@@ -4399,7 +4402,8 @@ def student_card_single_barcode_download(card_id):
download_name=f'ausweis_{card["AusweisId"]}.pdf'
)
except Exception as e:
flash(f'Fehler beim PDF-Download: {str(e)}', 'error')
app.logger.error(f"Error occurred while generating PDF for card {card['AusweisId']}: {e}")
flash('Fehler beim PDF-Download', 'error')
return redirect(url_for('student_cards_admin'))
@@ -4736,7 +4740,7 @@ def get_items():
'has_more': pagination_requested and ((offset + count) < total_count)
})
except Exception as e:
return jsonify({'items': [], 'error': str(e)}), 500
return jsonify({'items': []}), 500
finally:
if client:
client.close()
@@ -4753,7 +4757,8 @@ def get_item_json(id):
item['_id'] = str(item['_id'])
return jsonify(item)
except Exception as e:
return jsonify({'error': str(e)}), 500
app.logger.error(f"Error occurred while fetching item {id}: {e}")
return jsonify({'error': 'An error occurred while fetching the item'}), 500
@app.route('/get_bookings')
@@ -4827,7 +4832,7 @@ def get_bookings():
return jsonify({'ok': True, 'bookings': result})
except Exception as e:
return jsonify({'ok': False, 'error': str(e), 'bookings': []}), 500
return jsonify({'ok': False, 'bookings': []}), 500
finally:
if client:
client.close()
@@ -4917,7 +4922,7 @@ def get_user_appointments():
return jsonify({'ok': True, 'bookings': result})
except Exception as e:
return jsonify({'ok': False, 'error': str(e), 'bookings': []}), 500
return jsonify({'ok': False, 'bookings': []}), 500
finally:
if client:
client.close()
@@ -4961,7 +4966,8 @@ def api_booking_conflicts():
client.close()
return jsonify({'conflicts': result, 'count': len(result)})
except Exception as e:
return jsonify({'error': str(e), 'conflicts': []}), 500
app.logger.error(f"Error occurred while fetching booking conflicts: {e}")
return jsonify({'error': 'An error occurred while fetching booking conflicts', 'conflicts': []}), 500
"""Favorites management endpoints (persistent + session cache)."""
def _ensure_session_favs():
@@ -5076,7 +5082,8 @@ def debug_favorites():
try:
db_favs = us.get_favorites(username)
except Exception as e:
return jsonify({'ok': False, 'error': f'db_error: {e}', 'session': session_favs})
app.logger.error(f"Error fetching DB favorites: {e}")
return jsonify({'ok': False, 'error': 'Failed to fetch DB favorites', 'session': session_favs})
merged = sorted(set(session_favs) | set(db_favs))
return jsonify({'ok': True, 'user': username, 'session': session_favs, 'db': db_favs, 'merged': merged})
@@ -5184,7 +5191,7 @@ def upload_item():
except json.JSONDecodeError as e:
app.logger.error(f"Error parsing mobile data: {str(e)}")
except Exception as e:
error_msg = f"Fehler beim Verarbeiten der Formulardaten: {str(e)}"
error_msg = f"Fehler beim Verarbeiten der Formulardaten"
app.logger.error(error_msg)
if is_mobile:
return jsonify({'success': False, 'message': error_msg}), 400
@@ -5547,7 +5554,7 @@ def upload_item():
if is_png:
app.logger.error(f"PNG DEBUG: {image_log_prefix} Fallback PNG save also failed: {str(fallback_err)}")
app.logger.error(f"PNG DEBUG: {image_log_prefix} Error type: {type(fallback_err).__name__}")
traceback.print_exc()
error_count += 1
continue
else:
@@ -5655,7 +5662,7 @@ def upload_item():
except Exception as webp_err:
app.logger.error(f"PNG DEBUG: {image_log_prefix} Final WebP conversion failed: {str(webp_err)}")
traceback.print_exc()
error_count += 1
continue
@@ -5714,7 +5721,7 @@ def upload_item():
if is_png:
app.logger.error(f"PNG DEBUG: {image_log_prefix} Could not get PNG dimensions: {str(dim_err)}")
app.logger.error(f"PNG DEBUG: {image_log_prefix} Error type: {type(dim_err).__name__}")
traceback.print_exc()
app.logger.info(f"{image_log_prefix} Starting optimization for {saved_filename} ({original_size/1024:.1f}KB, {original_dimensions})")
@@ -5764,7 +5771,6 @@ def upload_item():
app.logger.warning(f"{image_log_prefix} Optimization failed or returned no file")
except Exception as e:
app.logger.error(f"{image_log_prefix} Optimization failed: {str(e)}")
traceback.print_exc()
# No fallback thumbnail generation needed as we only use the main image
@@ -5776,7 +5782,6 @@ def upload_item():
except Exception as e:
app.logger.error(f"{image_log_prefix} Unexpected error: {str(e)}")
traceback.print_exc()
error_count += 1
# Continue with the next image
@@ -5913,7 +5918,7 @@ def upload_item():
app.logger.error(f"Error generating optimized versions for {new_filename}: {e}")
# If optimization fails, at least keep the original file
result = {'original': new_filename}
traceback.print_exc()
# If we didn't find the image, use a placeholder
else:
@@ -5949,7 +5954,7 @@ def upload_item():
app.logger.info(f"Processed image {i+1}/{original_count}: {new_filename}")
except Exception as e:
app.logger.error(f"Error processing image {i+1}/{original_count} ({dup_img}): {str(e)}")
traceback.print_exc()
error_count += 1
# Log placeholder usage
@@ -6183,7 +6188,7 @@ def duplicate_item():
except Exception as e:
print(f"Error in duplicate_item: {e}")
traceback.print_exc()
return jsonify({'success': False, 'message': 'Serverfehler beim Duplizieren'}), 500
@@ -6336,7 +6341,7 @@ def delete_item(id):
soft_deleted_borrows = int(delete_result.get('soft_deleted_borrows', 0))
archived_files = int((delete_result.get('archive') or {}).get('archived_files', 0))
except Exception as e:
app.logger.error(f"Error during soft-delete for item group {id}: {str(e)}")
app.logger.error(f"Error during soft-delete for item group {id}")
delete_success = False
archived_files = 0
finally:
@@ -6468,7 +6473,7 @@ def bulk_delete_items():
'group_item_ids': result.get('group_item_ids', []),
})
except Exception as e:
app.logger.error(f"Error during bulk delete: {str(e)}")
app.logger.error(f"Error during bulk delete for items {item_ids}: {e}")
return jsonify({'success': False, 'message': 'Fehler beim Sammellöschen.'}), 500
finally:
if client:
@@ -7199,9 +7204,9 @@ def zurueckgeben(id):
)
except Exception as e:
print(f"Error in return process: {e}")
app.logger.error(f"Error in return process: {e}")
it.update_item_status(id, True)
flash(f'Element zurückgegeben, aber ein Fehler ist aufgetreten: {str(e)}', 'warning')
flash('Element zurückgegeben, aber ein Fehler ist aufgetreten', 'warning')
else:
flash('Sie sind nicht berechtigt, dieses Element zurückzugeben, oder es ist bereits verfügbar', 'error')
@@ -7298,7 +7303,7 @@ def get_planned_bookings(item_id):
client.close()
return jsonify({'ok': True, 'bookings': bookings})
except Exception as e:
return jsonify({'ok': False, 'error': str(e)}), 500
return jsonify({'ok': False}), 500
@app.route('/get_planned_bookings_public/<item_id>')
@@ -7324,7 +7329,7 @@ def get_planned_bookings_public(item_id):
client.close()
return jsonify({'ok': True, 'bookings': bookings})
except Exception as e:
return jsonify({'ok': False, 'error': str(e)}), 500
return jsonify({'ok': False}), 500
@app.route('/check_availability')
@@ -7405,7 +7410,7 @@ def check_availability():
client.close()
return jsonify({'ok': True, 'available': len(conflicts) == 0, 'conflicts': conflicts})
except Exception as e:
return jsonify({'ok': False, 'error': str(e)}), 500
return jsonify({'ok': False}), 500
# def create_qr_code(id):
@@ -7503,8 +7508,9 @@ def plan_booking():
if booking_type == 'single':
end_date = start_date
except ValueError as e:
return {"success": False, "error": f"Invalid date format: {e}"}, 400
app.logger.error(f"Invalid date format: {e}")
return {"success": False, "error": "Invalid date format"}, 400
# Check if item exists
item = it.get_item(item_id)
if not item:
@@ -7576,16 +7582,15 @@ def plan_booking():
}
else:
# All failed
return {"success": False, "errors": errors}, 400
return {"success": False}, 500
else:
# All succeeded
return {"success": True, "booking_ids": booking_ids}
except Exception as e:
import traceback
print(f"Error in plan_booking: {e}")
traceback.print_exc()
return {"success": False, "error": f"Serverfehler: {str(e)}"}, 500
app.logger.error(f"Error in plan_booking: {e}")
return {"success": False, "error": f"Fehler beim Planen der Buchung"}, 500
def process_day_bookings(item_id, booking_date, periods, notes):
"""
@@ -7680,7 +7685,7 @@ def add_booking():
return jsonify({'success': True, 'booking_id': str(booking_id)})
except Exception as e:
return jsonify({'success': False, 'error': str(e)})
return jsonify({'success': False})
@app.route('/cancel_booking/<id>', methods=['POST'])
def cancel_booking(id):
@@ -7731,9 +7736,7 @@ def terminplan():
return render_template('terminplan.html', school_periods=SCHOOL_PERIODS)
except Exception as e:
import traceback
print(f"Error rendering terminplan: {e}")
traceback.print_exc()
app.logger.error(f"Error rendering terminplan: {e}")
flash('Ein Fehler ist beim Anzeigen des Kalenders aufgetreten.', 'error')
return redirect(url_for('home'))
@@ -7919,15 +7922,17 @@ def delete_user():
client.close()
except Exception as e:
flash(f'Warnung: Ausleihungen/Reservierungen für {username} konnten nicht vollständig zurückgesetzt werden: {str(e)}', 'warning')
app.logger.error(f"Error resetting borrowings for user {encrypt_text(username)}: {e}")
flash(f'Warnung: Ausleihungen/Reservierungen für {username} konnten nicht vollständig zurückgesetzt werden', 'warning')
# Delete the user
try:
us.delete_user(username)
flash(f'Benutzer {username} erfolgreich gelöscht', 'success')
except Exception as e:
flash(f'Fehler beim Löschen des Benutzers: {str(e)}', 'error')
app.logger.error(f"Error deleting user {encrypt_text(username)}: {e}")
flash('Fehler beim Löschen des Benutzers', 'error')
return redirect(url_for('user_del'))
@@ -8025,7 +8030,7 @@ def admin_verify_audit_chain():
status_code = 200 if result.get('ok') else 409
return jsonify(result), status_code
except Exception as exc:
return jsonify({'ok': False, 'error': str(exc)}), 500
return jsonify({'ok': False, 'error': 'Internal server error'}), 500
finally:
if client:
client.close()
@@ -8044,23 +8049,14 @@ def admin_audit_dashboard():
al.ensure_audit_indexes(db)
verify_result = al.verify_audit_chain(db)
audit_rows = list(
db['audit_log'].find(
{},
{
'chain_index': 1,
'event_type': 1,
'actor': 1,
'source': 1,
'ip': 1,
'timestamp': 1,
'created_at': 1,
'entry_hash': 1,
'prev_hash': 1,
'payload': 1,
}
).sort('chain_index', -1).limit(200)
)
audit_rows = list(db['audit_log'].find({}).sort('chain_index', -1).limit(200))
# DEC_START: Decrypt the sensitive fields for display
for row in audit_rows:
if "payload" in row:
# decrypt_document_fields acts in-place
decrypt_document_fields(row["payload"], SENSITIVE_AUDIT_FIELDS)
# DEC_END
return render_template(
'admin_audit.html',
@@ -8105,28 +8101,18 @@ def admin_audit_export_pdf_official():
])
)
audit_rows = list(
db['audit_log'].find(
{},
{
'chain_index': 1,
'event_type': 1,
'actor': 1,
'source': 1,
'ip': 1,
'timestamp': 1,
'created_at': 1,
'entry_hash': 1,
'prev_hash': 1,
'payload': 1,
}
).sort('chain_index', -1).limit(limit)
)
audit_rows = list(db['audit_log'].find({}).sort('chain_index', -1).limit(limit))
# DEC_START: Decrypt sensitive fields for the PDF report
for row in audit_rows:
if "payload" in row:
decrypt_document_fields(row["payload"], SENSITIVE_AUDIT_FIELDS)
# DEC_END
# Get school information from settings or use defaults
school_info = _get_school_info_for_export()
# Generate PDF
# Generate PDF with now-decrypted audit_rows
pdf_content = pdf_export.generate_audit_pdf(
verify_result=verify_result,
event_counts=event_counts,
@@ -8134,7 +8120,7 @@ def admin_audit_export_pdf_official():
export_type='official',
school_info=school_info
)
response = make_response(pdf_content)
response.headers['Content-Type'] = 'application/pdf'
response.headers['Content-Disposition'] = f'attachment; filename=audit-official-report-{datetime.datetime.utcnow().strftime("%Y%m%d-%H%M%S")}.pdf'
@@ -8142,7 +8128,7 @@ def admin_audit_export_pdf_official():
except Exception as exc:
app.logger.error(f"PDF Official Report export error: {str(exc)}\n{traceback.format_exc()}")
return jsonify({'ok': False, 'error': str(exc)}), 500
return jsonify({'ok': False, 'error': 'Internal server error'}), 500
finally:
if client:
client.close()
@@ -8205,7 +8191,7 @@ def admin_image_cache_stats():
})
except Exception as e:
app.logger.error(f"Error getting cache stats: {str(e)}")
return jsonify({'ok': False, 'error': str(e)}), 500
return jsonify({'ok': False}), 500
@app.route('/admin/image_cache_cleanup', methods=['POST'])
@@ -8254,7 +8240,7 @@ def admin_image_cache_cleanup():
})
except Exception as e:
app.logger.error(f"Error during image cache cleanup: {str(e)}")
return jsonify({'ok': False, 'error': str(e)}), 500
return jsonify({'ok': False}), 500
"""-----------------------------------------------------------Borrowing Management Routes-------------------------------------------------------"""
@@ -8323,7 +8309,8 @@ def admin_reset_borrowing(borrow_id):
client.close()
except Exception as e:
flash(f'Fehler beim Zurücksetzen: {str(e)}', 'error')
app.logger.error(f"Error resetting borrowing status for {borrow_id}: {e}")
flash('Fehler beim Zurücksetzen', 'error')
return redirect(url_for('admin_borrowings'))
@@ -8937,10 +8924,11 @@ def admin_reset_user_password():
# Reset the password
try:
us.update_password(username, new_password)
flash(f'Passwort für {username} wurde erfolgreich zurückgesetzt auf: {new_password}', 'success')
flash(f'Passwort für {encrypt_text(username)} wurde erfolgreich zurückgesetzt auf: {new_password}', 'success')
except Exception as e:
flash(f'Fehler beim Zurücksetzen des Passworts: {str(e)}', 'error')
app.logger.error(f'Error resetting password for {encrypt_text(username)}: {e}')
flash('Fehler beim Zurücksetzen des Passworts', 'error')
return redirect(url_for('user_del'))
@@ -9440,7 +9428,8 @@ def search_word(word):
return jsonify({"success": True, "response": list(id_set)})
except Exception as e:
return jsonify({"success": False, "response": str(e)})
app.logger.error(f"Error searching for word: {e}")
return jsonify({"success": False})
def _fetch_from_google_books(clean_isbn):
"""Source 1: Google Books API (Free, No Key required for basic use)"""
@@ -9739,8 +9728,8 @@ def fetch_book_info(isbn):
return jsonify({"error": f"Kein Buch zu dieser ISBN gefunden: {clean_isbn}"}), 404
except Exception as e:
print(f"Error fetching book data: {e}")
return jsonify({"error": f"Failed to fetch book information: {str(e)}"}), 500
app.logger.error(f"Error fetching book data: {e}")
return jsonify({"error": f"Failed to fetch book information"}), 500
@app.route('/download_book_cover', methods=['POST'])
def download_book_cover():
@@ -9824,12 +9813,12 @@ def download_book_cover():
})
except requests.exceptions.RequestException as e:
print(f"Network error downloading book cover: {e}")
app.logger.error(f"Network error downloading book cover: {e}")
return jsonify({"error": "Netzwerkfehler beim Herunterladen des Bildes."}), 500
except Exception as e:
print(f"Error downloading book cover: {e}")
app.logger.error(f"Error downloading book cover: {e}")
# Fixed syntax here: Removed the injected HTML that was appended to this line
return jsonify({"error": f"Failed to download image: {str(e)}"}), 500
return jsonify({"error": f"Failed to download image"}), 500
"""
@app.route('/proxy_image')
def proxy_image():
@@ -9888,8 +9877,8 @@ def proxy_image():
}
)
except Exception as e:
print(f"Error in proxy_image: {e}")
return jsonify({"error": f"Error fetching image: {str(e)}"}), 500
app.logger.error(f"Error in proxy_image: {e}")
return jsonify({"error": f"Error fetching image"}), 500
"""
@@ -10737,8 +10726,8 @@ def schedule_appointment():
if has_conflict:
return jsonify({'success': False, 'message': 'Termin kollidiert mit bestehender Buchung'}), 409
except Exception as e:
print(f"Error checking for booking conflicts: {e}")
return jsonify({'success': False, 'message': f'Fehler beim Prüfen der Verfügbarkeit: {str(e)}'}), 500
app.logger.error(f"Error checking for booking conflicts: {e}")
return jsonify({'success': False, 'message': f'Fehler beim Prüfen der Verfügbarkeit'}), 500
# Check if the appointment should already be active
now = datetime.datetime.now()
@@ -10809,8 +10798,8 @@ def schedule_appointment():
if not appointment_id:
return jsonify({'success': False, 'message': 'Termin konnte nicht erstellt werden'}), 500
except Exception as e:
print(f"Error creating booking: {e}")
return jsonify({'success': False, 'message': f'Fehler beim Erstellen des Termins: {str(e)}'}), 500
app.logger.error(f"Error creating booking: {e}")
return jsonify({'success': False, 'message': f'Fehler beim Erstellen des Termins'}), 500
# If we got this far, we have a valid appointment_id
try:
@@ -10844,14 +10833,14 @@ def schedule_appointment():
return jsonify({'success': False, 'message': 'Element konnte nicht mit Termininformationen aktualisiert werden'}), 500
except Exception as e:
print(f"Error updating item with appointment info: {e}")
return jsonify({'success': False, 'message': f'Fehler beim Aktualisieren des Elements: {str(e)}'}), 500
app.logger.error(f"Error updating item with appointment info: {e}")
return jsonify({'success': False, 'message': f'Fehler beim Aktualisieren des Elements'}), 500
except Exception as e:
print(f"Error creating appointment: {e}")
app.logger.error(f"Error creating appointment: {e}")
import traceback
traceback.print_exc()
return jsonify({'success': False, 'message': f'Serverfehler aufgetreten: {str(e)}'}), 500
return jsonify({'success': False, 'message': f'Serverfehler aufgetreten'}), 500
@app.route('/cancel_ausleihung/<id>', methods=['POST'])
def cancel_ausleihung_route(id):
@@ -10925,16 +10914,15 @@ def cancel_ausleihung_route(id):
next_appt = item_doc.get('NextAppointment', {})
if next_appt and str(next_appt.get('appointment_id')) == str(id):
cleared = it.clear_item_next_appointment(item_id)
print(f"Cleared NextAppointment for item {item_id}: {cleared}")
except Exception as clear_err:
print(f"Warning: could not clear NextAppointment for cancelled ausleihung {id}: {clear_err}")
app.logger.warning(f"Warning: could not clear NextAppointment for cancelled ausleihung {id}: {clear_err}")
else:
print(f"Failed to cancel ausleihung with ID: {id}")
app.logger.warning(f"Failed to cancel ausleihung with ID: {id}")
flash('Fehler beim Stornieren der Ausleihung', 'error')
except Exception as e:
print(f"Error canceling ausleihung: {e}")
flash(f'Fehler: {str(e)}', 'error')
app.logger.warning(f"Error canceling ausleihung: {e}")
flash(f'Fehler', 'error')
return redirect(url_for('my_borrowed_items'))
@@ -10965,22 +10953,21 @@ def reset_item(id):
if result['success']:
return jsonify({
'success': True,
'message': result['message'],
'details': result.get('details', {})
'message': 'Item reset successfully'
})
else:
return jsonify({
'success': False,
'message': result['message']
'message': 'Failed to reset item'
}), 400
except Exception as e:
print(f"Error in reset_item route: {e}")
import traceback
traceback.print_exc()
return jsonify({
'success': False,
'error': f'Serverfehler: {str(e)}'
'error': f'Serverfehler'
}), 500
# New image and video optimization functions
@@ -11126,7 +11113,6 @@ def create_image_thumbnail(image_path, thumbnail_path, size, debug_prefix=""):
return True
except Exception as e:
print(f"Error creating image thumbnail for {image_path}: {str(e)}")
return False
@@ -11167,11 +11153,11 @@ def create_video_thumbnail(video_path, thumbnail_path, size):
return success
else:
print(f"ffmpeg failed for {video_path}: {result.stderr}")
app.logger.error(f"ffmpeg failed for {video_path}: {result.stderr}")
return False
except Exception as e:
print(f"Error creating video thumbnail for {video_path}: {str(e)}")
app.logger.error(f"Error creating video thumbnail for {video_path}: {str(e)}")
return False
@@ -11398,7 +11384,7 @@ def generate_optimized_versions(filename, max_original_width=500, target_size_kb
except Exception as e:
app.logger.error(f"{log_prefix} Failed to process image: {str(e)}")
traceback.print_exc()
# Just copy the original file as is
if not is_webp_ext and os.path.exists(original_path) and not os.path.exists(converted_path):
try:
@@ -11427,7 +11413,7 @@ def generate_optimized_versions(filename, max_original_width=500, target_size_kb
except Exception as e:
app.logger.error(f"{log_prefix} Unhandled exception in optimization: {str(e)}")
traceback.print_exc()
# If anything went wrong but the original file exists, just use it
if os.path.exists(original_path):
@@ -11663,7 +11649,7 @@ def cleanup_old_optimized_images(max_age_days=30):
}
except Exception as e:
app.logger.error(f"Error during optimized image cleanup: {str(e)}")
return {'deleted': 0, 'freed_mb': 0, 'error': str(e)}
return {'deleted': 0, 'freed_mb': 0}
@app.route('/log_mobile_issue', methods=['POST'])
@@ -11708,7 +11694,7 @@ def log_mobile_issue():
return jsonify({'success': True})
except Exception as e:
app.logger.error(f"Error logging mobile issue: {str(e)}")
return jsonify({'success': False, 'error': str(e)})
return jsonify({'success': False})
def delete_item_images(filenames):
"""
@@ -11860,7 +11846,7 @@ def subscribe_to_push():
except Exception as e:
app.logger.error(f'Error subscribing to push: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
return jsonify({'success': False}), 500
@app.route('/api/push/unsubscribe', methods=['POST'])
@@ -11897,7 +11883,7 @@ def unsubscribe_from_push():
except Exception as e:
app.logger.error(f'Error unsubscribing from push: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
return jsonify({'success': False}), 500
@app.route('/api/push/subscriptions', methods=['GET'])
@@ -11931,7 +11917,7 @@ def get_push_subscriptions():
except Exception as e:
app.logger.error(f'Error getting push subscriptions: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
return jsonify({'success': False}), 500
@app.route('/api/push/vapid-key', methods=['GET'])
@@ -11956,7 +11942,7 @@ def get_vapid_key():
except Exception as e:
app.logger.error(f'Error getting VAPID key: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
return jsonify({'success': False}), 500
@app.route('/api/push/test', methods=['POST'])
@@ -11995,4 +11981,4 @@ def test_push_notification():
except Exception as e:
app.logger.error(f'Error sending test push: {e}')
return jsonify({'success': False, 'error': str(e)}), 500
return jsonify({'success': False}), 500
+1 -1
View File
@@ -1235,5 +1235,5 @@ def reset_item_completely(item_id):
except Exception as e:
return {
'success': False,
'message': f'Fehler beim Zurücksetzen: {str(e)}'
'message': f'Fehler beim Zurücksetzen.'
}
+36 -13
View File
@@ -10,6 +10,7 @@ import hashlib
import json
import random
import time
from Web.modules.inventarsystem.data_protection import encrypt_document_fields, decrypt_document_fields
from pymongo.errors import DuplicateKeyError
@@ -24,21 +25,34 @@ def _entry_hash(prev_hash, payload):
base = f"{prev_hash}|{_stable_json(payload)}"
return hashlib.sha256(base.encode("utf-8")).hexdigest()
def append_audit_event(db, event_type, actor, payload, request_ip=None, source="web", max_retries=5):
def get_decrypted_audit_logs(db, query=None, decrypt_fields=None):
"""
Append an audit event to a tamper-evident chain.
Retrieve and decrypt audit logs for analysis.
Args:
db: MongoDB database handle.
event_type (str): Event category.
actor (str): User/system who performed the action.
payload (dict): Event details.
request_ip (str, optional): Request origin.
source (str): Source subsystem.
query (dict): MongoDB query filter.
decrypt_fields (list): Fields within the 'payload' that should be decrypted.
"""
logs = db["audit_log"]
cursor = logs.find(query or {}).sort("chain_index", 1)
results = []
for entry in cursor:
# Decrypt specific fields if provided
if decrypt_fields:
decrypt_document_fields(entry.get("payload", {}), decrypt_fields)
results.append(entry)
return results
Returns:
dict: Inserted audit entry.
def append_audit_event(db, event_type, actor, payload, request_ip=None, source="web", max_retries=5, encrypt_fields=None):
"""
Append an audit event, optionally encrypting specific payload fields.
Args:
...
encrypt_fields (list, optional): List of keys in 'payload' to encrypt.
"""
logs = db["audit_log"]
attempts = 0
@@ -49,15 +63,24 @@ def append_audit_event(db, event_type, actor, payload, request_ip=None, source="
chain_index = int(previous.get("chain_index", 0)) + 1 if previous else 1
timestamp = datetime.datetime.utcnow()
# 1. Create the payload dictionary
event_payload = payload or {}
# 2. Encrypt sensitive fields in-place if requested
if encrypt_fields:
encrypt_document_fields(event_payload, encrypt_fields)
entry_payload = {
"event_type": event_type,
"actor": actor or "system",
"source": source,
"ip": request_ip or "",
"payload": payload or {},
"payload": event_payload,
"timestamp": timestamp.isoformat() + "Z",
}
# 3. Hash the payload (which now contains encrypted values)
entry_hash = _entry_hash(prev_hash, entry_payload)
entry = {