|
|
|
@@ -388,7 +388,7 @@ ALLOWED_COVER_DOMAINS = {
|
|
|
|
|
"www.googleapis.com"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SENSITIVE_AUDIT_FIELDS = ["email", "username", "full_name", "phone"]
|
|
|
|
|
SENSITIVE_AUDIT_FIELDS = ["email", "username", "full_name", "phone", "borrower", "ip"]
|
|
|
|
|
|
|
|
|
|
# Apply the configuration for general use throughout the app
|
|
|
|
|
APP_VERSION = __version__
|
|
|
|
@@ -660,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."""
|
|
|
|
@@ -1635,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':
|
|
|
|
@@ -1675,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
|
|
|
|
|
|
|
|
|
@@ -2835,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-----------------------------------------------------------------------------"""
|
|
|
|
|
|
|
|
|
@@ -3350,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'])
|
|
|
|
@@ -3567,15 +3567,20 @@ def api_item_detail(item_id):
|
|
|
|
|
for rec in borrow_records:
|
|
|
|
|
if rec.get('Status') == 'active' and rec.get('User'):
|
|
|
|
|
try:
|
|
|
|
|
borrower_value = decrypt_text(rec.get('User'))
|
|
|
|
|
user_raw = rec.get('User')
|
|
|
|
|
if user_raw is not None:
|
|
|
|
|
borrower_value = decrypt_text(str(user_raw))
|
|
|
|
|
except Exception:
|
|
|
|
|
borrower_value = rec.get('User')
|
|
|
|
|
borrower_value = str(rec.get('User', ''))
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
if not borrower_value and item.get('User'):
|
|
|
|
|
try:
|
|
|
|
|
borrower_value = decrypt_text(item.get('User'))
|
|
|
|
|
item_user = item.get('User')
|
|
|
|
|
if item_user is not None:
|
|
|
|
|
borrower_value = decrypt_text(str(item_user))
|
|
|
|
|
except Exception:
|
|
|
|
|
borrower_value = item.get('User')
|
|
|
|
|
borrower_value = str(item.get('User', ''))
|
|
|
|
|
|
|
|
|
|
# Helper to format datetimes
|
|
|
|
|
def fmt_dt(dt):
|
|
|
|
@@ -3589,33 +3594,40 @@ def api_item_detail(item_id):
|
|
|
|
|
if borrow_records:
|
|
|
|
|
rows = []
|
|
|
|
|
for rec in borrow_records:
|
|
|
|
|
user_raw = rec.get('User') or ''
|
|
|
|
|
user_raw = rec.get('User')
|
|
|
|
|
try:
|
|
|
|
|
user = decrypt_text(user_raw) if user_raw else ''
|
|
|
|
|
# FIX: Cast to string before decrypting
|
|
|
|
|
user = decrypt_text(str(user_raw)) if user_raw is not None else ''
|
|
|
|
|
except Exception:
|
|
|
|
|
user = user_raw
|
|
|
|
|
# FIX: Ensure the fallback is also a string for html.escape
|
|
|
|
|
user = str(user_raw) if user_raw is not None else ''
|
|
|
|
|
|
|
|
|
|
status = rec.get('Status', '')
|
|
|
|
|
start = fmt_dt(rec.get('Start'))
|
|
|
|
|
end = fmt_dt(rec.get('End'))
|
|
|
|
|
notes = html.escape(str(rec.get('Notes') or ''))
|
|
|
|
|
|
|
|
|
|
rows.append(f"<li><strong>{html.escape(user or '-')}</strong> — {html.escape(status)} — {html.escape(start)} → {html.escape(end)}{(' — ' + notes) if notes else ''}</li>")
|
|
|
|
|
borrows_html = f"<h3>Ausleihhistorie</h3><ul>{''.join(rows)}</ul>"
|
|
|
|
|
|
|
|
|
|
# Basic detail HTML
|
|
|
|
|
detail_html = f"""
|
|
|
|
|
<h2>{html.escape(item.get('Name', 'Untitled'))}</h2>
|
|
|
|
|
<p><strong>Autor/Künstler:</strong> {html.escape(item.get('Autor', item.get('Author', '-')))}</p>
|
|
|
|
|
<p><strong>ISBN:</strong> {html.escape(item.get('ISBN', item.get('Code4', '-')))}</p>
|
|
|
|
|
<p><strong>Anzahl:</strong> {html.escape(item.get('SeriesCount', '-'))}</p>
|
|
|
|
|
<p><strong>Ort:</strong> {html.escape(item.get('Ort', '-'))}</p>
|
|
|
|
|
<p><strong>Typ:</strong> {html.escape(item.get('ItemType', '-'))}</p>
|
|
|
|
|
<p><strong>Kategorie:</strong> {html.escape(item.get('library_category', '-'))}</p>
|
|
|
|
|
<p><strong>Beschreibung:</strong> {html.escape(item.get('Beschreibung', '-'))}</p>
|
|
|
|
|
<p><strong>Status:</strong> {html.escape(status_label)}</p>
|
|
|
|
|
{f'<p><strong>Ausgeliehen von:</strong> {html.escape(str(borrower_value))}</p>' if borrower_value and status_label == 'Ausgeliehen' else ''}
|
|
|
|
|
{f'<p><strong>Ausgeliehen von:</strong> {html.escape(borrower_value)}</p>' if borrower_value and status_label == 'Ausgeliehen' else ''}
|
|
|
|
|
{borrows_html}
|
|
|
|
|
"""
|
|
|
|
|
client.close()
|
|
|
|
|
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'])
|
|
|
|
@@ -4229,7 +4241,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'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -4401,7 +4414,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'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -4738,7 +4752,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()
|
|
|
|
@@ -4755,7 +4769,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')
|
|
|
|
@@ -4829,7 +4844,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()
|
|
|
|
@@ -4919,7 +4934,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()
|
|
|
|
@@ -4963,7 +4978,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():
|
|
|
|
@@ -5078,7 +5094,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})
|
|
|
|
|
|
|
|
|
@@ -5139,7 +5156,9 @@ def upload_item():
|
|
|
|
|
individual_codes_raw = sanitize_form_value(request.form.get('individual_codes', ''))
|
|
|
|
|
item_count_raw = sanitize_form_value(request.form.get('item_count', '1'))
|
|
|
|
|
item_type_input = sanitize_form_value(request.form.get('item_type_input', ''))
|
|
|
|
|
|
|
|
|
|
library_category = sanitize_form_value(request.form.get('library_category', ''))
|
|
|
|
|
|
|
|
|
|
app.logger.info(f"Upload attempt by {encrypt_text(username)}: name={name!r}, ort={ort!r}, beschreibung length={len(beschreibung)}, images count={len(images)}, filters={filter_upload}, filters2={filter_upload2}, filters3={filter_upload3}, anschaffungs_jahr={anschaffungs_jahr}, anschaffungs_kosten={anschaffungs_kosten}, code_4={code_4}, isbn={isbn_raw!r}, upload_mode={upload_mode!r}, item_count={item_count_raw!r}, item_type_input={item_type_input!r}, individual_codes={individual_codes_raw!r}")
|
|
|
|
|
try:
|
|
|
|
|
item_count = int(item_count_raw) if item_count_raw else 1
|
|
|
|
|
except (TypeError, ValueError):
|
|
|
|
@@ -5151,6 +5170,8 @@ def upload_item():
|
|
|
|
|
if individual_codes_raw:
|
|
|
|
|
individual_codes = [c.strip() for c in str(individual_codes_raw).replace(',', '\n').splitlines() if c.strip()]
|
|
|
|
|
|
|
|
|
|
app.logger.info(f"DEBUG: Parsed individual codes: {individual_codes}, count: {len(individual_codes)}")
|
|
|
|
|
|
|
|
|
|
# Check if this is a duplication
|
|
|
|
|
is_duplicating = request.form.get('is_duplicating') == 'true'
|
|
|
|
|
|
|
|
|
@@ -5186,7 +5207,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
|
|
|
|
@@ -5266,6 +5287,8 @@ def upload_item():
|
|
|
|
|
for c in extra_codes:
|
|
|
|
|
if c not in all_item_codes:
|
|
|
|
|
all_item_codes.append(c)
|
|
|
|
|
|
|
|
|
|
app.logger.info(f"DEBUG: Unified item codes: {all_item_codes}, total count: {len(all_item_codes)}")
|
|
|
|
|
|
|
|
|
|
# -------------------------------------------------------------------
|
|
|
|
|
# 2. OVERRIDE ITEM COUNT
|
|
|
|
@@ -5297,7 +5320,7 @@ def upload_item():
|
|
|
|
|
# Validate every code in our master list against the database
|
|
|
|
|
for code in all_item_codes:
|
|
|
|
|
if not it.is_code_unique(code):
|
|
|
|
|
|
|
|
|
|
app.logger.info(f"DEBUG: Code '{code}' is not unique.")
|
|
|
|
|
# Special case: If they only uploaded ONE item, redirect them to that existing item
|
|
|
|
|
if item_count == 1:
|
|
|
|
|
existing_item = it._get_items_collection().find_one({"code_4": code})
|
|
|
|
@@ -5330,13 +5353,13 @@ def upload_item():
|
|
|
|
|
if not base_code:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
candidate = base_code if position == 1 else f"{base_code}-{position}"
|
|
|
|
|
candidate = base_code if position == 1 else f"{base_code}"
|
|
|
|
|
if it.is_code_unique(candidate):
|
|
|
|
|
return candidate
|
|
|
|
|
|
|
|
|
|
suffix = 1
|
|
|
|
|
while suffix <= 1000:
|
|
|
|
|
alternative = f"{candidate}-{suffix}"
|
|
|
|
|
alternative = f"{candidate}"
|
|
|
|
|
if it.is_code_unique(alternative):
|
|
|
|
|
return alternative
|
|
|
|
|
suffix += 1
|
|
|
|
@@ -5549,7 +5572,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:
|
|
|
|
@@ -5657,7 +5680,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
|
|
|
|
|
|
|
|
|
@@ -5716,7 +5739,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})")
|
|
|
|
|
|
|
|
|
@@ -5766,7 +5789,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
|
|
|
|
|
|
|
|
|
@@ -5778,7 +5800,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
|
|
|
|
|
|
|
|
|
@@ -5915,7 +5936,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:
|
|
|
|
@@ -5951,7 +5972,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
|
|
|
|
@@ -6032,7 +6053,8 @@ def upload_item():
|
|
|
|
|
is_grouped_sub_item=(position > 1),
|
|
|
|
|
parent_item_id=parent_item_id,
|
|
|
|
|
isbn=item_isbn,
|
|
|
|
|
item_type=item_type
|
|
|
|
|
item_type=item_type,
|
|
|
|
|
library_category=library_category
|
|
|
|
|
)
|
|
|
|
|
if not item_id:
|
|
|
|
|
break
|
|
|
|
@@ -6185,7 +6207,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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -6338,7 +6360,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:
|
|
|
|
@@ -6470,7 +6492,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:
|
|
|
|
@@ -7201,9 +7223,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')
|
|
|
|
|
|
|
|
|
@@ -7300,7 +7322,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>')
|
|
|
|
@@ -7326,7 +7348,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')
|
|
|
|
@@ -7407,7 +7429,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):
|
|
|
|
@@ -7505,8 +7527,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:
|
|
|
|
@@ -7578,16 +7601,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):
|
|
|
|
|
"""
|
|
|
|
@@ -7682,7 +7704,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):
|
|
|
|
@@ -7733,9 +7755,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'))
|
|
|
|
|
|
|
|
|
@@ -7921,15 +7941,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'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -8027,7 +8049,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()
|
|
|
|
@@ -8046,7 +8068,7 @@ def admin_audit_dashboard():
|
|
|
|
|
al.ensure_audit_indexes(db)
|
|
|
|
|
verify_result = al.verify_audit_chain(db)
|
|
|
|
|
|
|
|
|
|
audit_rows = list(db['audit_log'].find(...).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:
|
|
|
|
@@ -8098,7 +8120,7 @@ def admin_audit_export_pdf_official():
|
|
|
|
|
])
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
audit_rows = list(db['audit_log'].find(...).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:
|
|
|
|
@@ -8125,7 +8147,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()
|
|
|
|
@@ -8188,7 +8210,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'])
|
|
|
|
@@ -8237,7 +8259,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-------------------------------------------------------"""
|
|
|
|
|
|
|
|
|
@@ -8306,7 +8328,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'))
|
|
|
|
|
|
|
|
|
@@ -8920,10 +8943,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'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -9423,7 +9447,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)"""
|
|
|
|
@@ -9722,8 +9747,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():
|
|
|
|
@@ -9807,12 +9832,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():
|
|
|
|
@@ -9871,8 +9896,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
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -10720,8 +10745,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()
|
|
|
|
@@ -10792,8 +10817,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:
|
|
|
|
@@ -10827,14 +10852,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):
|
|
|
|
@@ -10908,16 +10933,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'))
|
|
|
|
|
|
|
|
|
@@ -10948,22 +10972,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
|
|
|
|
@@ -11109,7 +11132,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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -11150,11 +11172,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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -11381,7 +11403,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:
|
|
|
|
@@ -11410,7 +11432,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):
|
|
|
|
@@ -11646,7 +11668,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'])
|
|
|
|
@@ -11691,7 +11713,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):
|
|
|
|
|
"""
|
|
|
|
@@ -11843,7 +11865,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'])
|
|
|
|
@@ -11880,7 +11902,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'])
|
|
|
|
@@ -11914,7 +11936,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'])
|
|
|
|
@@ -11939,7 +11961,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'])
|
|
|
|
@@ -11978,4 +12000,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
|
|
|
|
|