462 lines
17 KiB
Python
462 lines
17 KiB
Python
"""
|
|
Upload-Blueprint für das 3D-Druck-Management-System
|
|
|
|
Dieses Modul enthält alle Routen und Funktionen für Datei-Uploads.
|
|
"""
|
|
|
|
from flask import Blueprint, jsonify, request, send_file, abort
|
|
from flask_login import login_required, current_user
|
|
from werkzeug.utils import secure_filename
|
|
from functools import wraps
|
|
import os
|
|
from typing import Dict, Any
|
|
from datetime import datetime
|
|
|
|
from models import get_db_session, SystemLog
|
|
from utils.logging_config import get_logger
|
|
from utils.file_manager import file_manager, save_job_file, save_guest_file, save_avatar_file, save_asset_file, save_log_file, save_backup_file, save_temp_file, delete_file as delete_file_safe
|
|
from utils.settings import UPLOAD_FOLDER, ALLOWED_EXTENSIONS
|
|
|
|
# Blueprint erstellen
|
|
uploads_blueprint = Blueprint('uploads', __name__, url_prefix='/api')
|
|
|
|
# Logger initialisieren
|
|
uploads_logger = get_logger("uploads")
|
|
|
|
def admin_required(f):
|
|
"""Decorator für Admin-geschützte Routen"""
|
|
@wraps(f)
|
|
@login_required
|
|
def decorated_function(*args, **kwargs):
|
|
if not current_user.is_admin:
|
|
abort(403)
|
|
return f(*args, **kwargs)
|
|
return decorated_function
|
|
|
|
def allowed_file(filename: str) -> bool:
|
|
"""Prüft ob die Dateiendung erlaubt ist"""
|
|
return '.' in filename and \
|
|
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
|
|
|
@uploads_blueprint.route('/upload/job', methods=['POST'])
|
|
@login_required
|
|
def upload_job_file():
|
|
"""Lädt eine Job-Datei hoch"""
|
|
try:
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'Keine Datei hochgeladen'}), 400
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return jsonify({'error': 'Keine Datei ausgewählt'}), 400
|
|
|
|
if not allowed_file(file.filename):
|
|
return jsonify({'error': 'Dateityp nicht erlaubt'}), 400
|
|
|
|
# Datei speichern
|
|
result = save_job_file(file, user_id=current_user.id)
|
|
|
|
if result['success']:
|
|
# SystemLog erstellen
|
|
with get_db_session() as db_session:
|
|
SystemLog.log_system_event(
|
|
level="INFO",
|
|
message=f"Job-Datei hochgeladen: {result['filename']}",
|
|
module="uploads",
|
|
user_id=current_user.id
|
|
)
|
|
db_session.commit()
|
|
|
|
uploads_logger.info(f"Job-Datei erfolgreich hochgeladen: {result['filename']} von User {current_user.id}")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'filename': result['filename'],
|
|
'path': result['path'],
|
|
'url': result['url'],
|
|
'size': result['size']
|
|
})
|
|
else:
|
|
return jsonify({'error': result.get('error', 'Upload fehlgeschlagen')}), 500
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Job-Upload: {str(e)}")
|
|
return jsonify({'error': 'Fehler beim Hochladen der Datei'}), 500
|
|
|
|
@uploads_blueprint.route('/upload/guest', methods=['POST'])
|
|
def upload_guest_file():
|
|
"""Lädt eine Gast-Datei hoch (ohne Login)"""
|
|
try:
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'Keine Datei hochgeladen'}), 400
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return jsonify({'error': 'Keine Datei ausgewählt'}), 400
|
|
|
|
if not allowed_file(file.filename):
|
|
return jsonify({'error': 'Dateityp nicht erlaubt'}), 400
|
|
|
|
# Gast-ID aus Request holen
|
|
guest_id = request.form.get('guest_id', 'anonymous')
|
|
|
|
# Datei speichern
|
|
result = save_guest_file(file, guest_id=guest_id)
|
|
|
|
if result['success']:
|
|
uploads_logger.info(f"Gast-Datei erfolgreich hochgeladen: {result['filename']} für Gast {guest_id}")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'filename': result['filename'],
|
|
'path': result['path'],
|
|
'url': result['url'],
|
|
'size': result['size']
|
|
})
|
|
else:
|
|
return jsonify({'error': result.get('error', 'Upload fehlgeschlagen')}), 500
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Gast-Upload: {str(e)}")
|
|
return jsonify({'error': 'Fehler beim Hochladen der Datei'}), 500
|
|
|
|
@uploads_blueprint.route('/upload/avatar', methods=['POST'])
|
|
@login_required
|
|
def upload_avatar():
|
|
"""Lädt ein Avatar-Bild hoch"""
|
|
try:
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'Keine Datei hochgeladen'}), 400
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return jsonify({'error': 'Keine Datei ausgewählt'}), 400
|
|
|
|
# Prüfe Dateityp (nur Bilder)
|
|
allowed_image_extensions = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
|
|
if not ('.' in file.filename and file.filename.rsplit('.', 1)[1].lower() in allowed_image_extensions):
|
|
return jsonify({'error': 'Nur Bilddateien erlaubt'}), 400
|
|
|
|
# Datei speichern
|
|
result = save_avatar_file(file, user_id=current_user.id)
|
|
|
|
if result['success']:
|
|
# Altes Avatar löschen (optional)
|
|
# TODO: Implementiere Avatar-Verwaltung in User-Model
|
|
|
|
uploads_logger.info(f"Avatar erfolgreich hochgeladen: {result['filename']} für User {current_user.id}")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'filename': result['filename'],
|
|
'path': result['path'],
|
|
'url': result['url'],
|
|
'size': result['size']
|
|
})
|
|
else:
|
|
return jsonify({'error': result.get('error', 'Upload fehlgeschlagen')}), 500
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Avatar-Upload: {str(e)}")
|
|
return jsonify({'error': 'Fehler beim Hochladen des Avatars'}), 500
|
|
|
|
@uploads_blueprint.route('/upload/asset', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def upload_asset():
|
|
"""Lädt ein Asset hoch (nur für Admins)"""
|
|
try:
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'Keine Datei hochgeladen'}), 400
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return jsonify({'error': 'Keine Datei ausgewählt'}), 400
|
|
|
|
# Asset-Typ aus Request
|
|
asset_type = request.form.get('type', 'general')
|
|
|
|
# Datei speichern
|
|
result = save_asset_file(file, asset_type=asset_type, user_id=current_user.id)
|
|
|
|
if result['success']:
|
|
# SystemLog erstellen
|
|
with get_db_session() as db_session:
|
|
SystemLog.log_system_event(
|
|
level="INFO",
|
|
message=f"Asset hochgeladen: {result['filename']} (Typ: {asset_type})",
|
|
module="uploads",
|
|
user_id=current_user.id
|
|
)
|
|
db_session.commit()
|
|
|
|
uploads_logger.info(f"Asset erfolgreich hochgeladen: {result['filename']} (Typ: {asset_type})")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'filename': result['filename'],
|
|
'path': result['path'],
|
|
'url': result['url'],
|
|
'size': result['size'],
|
|
'type': asset_type
|
|
})
|
|
else:
|
|
return jsonify({'error': result.get('error', 'Upload fehlgeschlagen')}), 500
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Asset-Upload: {str(e)}")
|
|
return jsonify({'error': 'Fehler beim Hochladen des Assets'}), 500
|
|
|
|
@uploads_blueprint.route('/upload/log', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def upload_log():
|
|
"""Lädt eine Log-Datei hoch (nur für Admins)"""
|
|
try:
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'Keine Datei hochgeladen'}), 400
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return jsonify({'error': 'Keine Datei ausgewählt'}), 400
|
|
|
|
# Log-Typ aus Request
|
|
log_type = request.form.get('type', 'general')
|
|
|
|
# Datei speichern
|
|
result = save_log_file(file, log_type=log_type, user_id=current_user.id)
|
|
|
|
if result['success']:
|
|
uploads_logger.info(f"Log-Datei erfolgreich hochgeladen: {result['filename']} (Typ: {log_type})")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'filename': result['filename'],
|
|
'path': result['path'],
|
|
'url': result['url'],
|
|
'size': result['size'],
|
|
'type': log_type
|
|
})
|
|
else:
|
|
return jsonify({'error': result.get('error', 'Upload fehlgeschlagen')}), 500
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Log-Upload: {str(e)}")
|
|
return jsonify({'error': 'Fehler beim Hochladen der Log-Datei'}), 500
|
|
|
|
@uploads_blueprint.route('/upload/backup', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def upload_backup():
|
|
"""Lädt eine Backup-Datei hoch (nur für Admins)"""
|
|
try:
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'Keine Datei hochgeladen'}), 400
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return jsonify({'error': 'Keine Datei ausgewählt'}), 400
|
|
|
|
# Backup-Typ aus Request
|
|
backup_type = request.form.get('type', 'database')
|
|
|
|
# Datei speichern
|
|
result = save_backup_file(file, backup_type=backup_type, user_id=current_user.id)
|
|
|
|
if result['success']:
|
|
# SystemLog erstellen
|
|
with get_db_session() as db_session:
|
|
SystemLog.log_system_event(
|
|
level="INFO",
|
|
message=f"Backup hochgeladen: {result['filename']} (Typ: {backup_type})",
|
|
module="uploads",
|
|
user_id=current_user.id
|
|
)
|
|
db_session.commit()
|
|
|
|
uploads_logger.info(f"Backup erfolgreich hochgeladen: {result['filename']} (Typ: {backup_type})")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'filename': result['filename'],
|
|
'path': result['path'],
|
|
'url': result['url'],
|
|
'size': result['size'],
|
|
'type': backup_type
|
|
})
|
|
else:
|
|
return jsonify({'error': result.get('error', 'Upload fehlgeschlagen')}), 500
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Backup-Upload: {str(e)}")
|
|
return jsonify({'error': 'Fehler beim Hochladen des Backups'}), 500
|
|
|
|
@uploads_blueprint.route('/upload/temp', methods=['POST'])
|
|
@login_required
|
|
def upload_temp_file():
|
|
"""Lädt eine temporäre Datei hoch"""
|
|
try:
|
|
if 'file' not in request.files:
|
|
return jsonify({'error': 'Keine Datei hochgeladen'}), 400
|
|
|
|
file = request.files['file']
|
|
if file.filename == '':
|
|
return jsonify({'error': 'Keine Datei ausgewählt'}), 400
|
|
|
|
# Datei speichern
|
|
result = save_temp_file(file, user_id=current_user.id)
|
|
|
|
if result['success']:
|
|
uploads_logger.info(f"Temporäre Datei erfolgreich hochgeladen: {result['filename']}")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'filename': result['filename'],
|
|
'path': result['path'],
|
|
'url': result['url'],
|
|
'size': result['size'],
|
|
'ttl': 3600 # 1 Stunde TTL für temporäre Dateien
|
|
})
|
|
else:
|
|
return jsonify({'error': result.get('error', 'Upload fehlgeschlagen')}), 500
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Temp-Upload: {str(e)}")
|
|
return jsonify({'error': 'Fehler beim Hochladen der temporären Datei'}), 500
|
|
|
|
@uploads_blueprint.route('/files/<path:file_path>', methods=['GET'])
|
|
@login_required
|
|
def serve_uploaded_file(file_path):
|
|
"""Gibt eine hochgeladene Datei zurück"""
|
|
try:
|
|
# Sicherheitsprüfung: Pfad-Traversal verhindern
|
|
safe_path = os.path.normpath(file_path)
|
|
if '..' in safe_path or safe_path.startswith('/'):
|
|
abort(403)
|
|
|
|
# Vollständiger Pfad
|
|
full_path = os.path.join(UPLOAD_FOLDER, safe_path)
|
|
|
|
# Prüfe ob Datei existiert
|
|
if not os.path.exists(full_path) or not os.path.isfile(full_path):
|
|
abort(404)
|
|
|
|
# Prüfe Zugriffsrechte (je nach Dateityp)
|
|
# TODO: Implementiere detaillierte Zugriffskontrolle
|
|
|
|
# Datei zurückgeben
|
|
return send_file(full_path, as_attachment=True)
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Abrufen der Datei {file_path}: {str(e)}")
|
|
abort(500)
|
|
|
|
@uploads_blueprint.route('/files/<path:file_path>', methods=['DELETE'])
|
|
@login_required
|
|
def delete_uploaded_file(file_path):
|
|
"""Löscht eine hochgeladene Datei"""
|
|
try:
|
|
# Sicherheitsprüfung: Pfad-Traversal verhindern
|
|
safe_path = os.path.normpath(file_path)
|
|
if '..' in safe_path or safe_path.startswith('/'):
|
|
return jsonify({'error': 'Ungültiger Dateipfad'}), 403
|
|
|
|
# Vollständiger Pfad
|
|
full_path = os.path.join(UPLOAD_FOLDER, safe_path)
|
|
|
|
# Prüfe ob Datei existiert
|
|
if not os.path.exists(full_path) or not os.path.isfile(full_path):
|
|
return jsonify({'error': 'Datei nicht gefunden'}), 404
|
|
|
|
# Prüfe Zugriffsrechte
|
|
# TODO: Implementiere detaillierte Zugriffskontrolle
|
|
# Aktuell: Nur eigene Dateien oder Admin
|
|
|
|
# Datei löschen
|
|
if delete_file_safe(full_path):
|
|
# SystemLog erstellen
|
|
with get_db_session() as db_session:
|
|
SystemLog.log_system_event(
|
|
level="INFO",
|
|
message=f"Datei gelöscht: {safe_path}",
|
|
module="uploads",
|
|
user_id=current_user.id
|
|
)
|
|
db_session.commit()
|
|
|
|
uploads_logger.info(f"Datei erfolgreich gelöscht: {safe_path} von User {current_user.id}")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'message': 'Datei erfolgreich gelöscht'
|
|
})
|
|
else:
|
|
return jsonify({'error': 'Fehler beim Löschen der Datei'}), 500
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Löschen der Datei {file_path}: {str(e)}")
|
|
return jsonify({'error': 'Fehler beim Löschen der Datei'}), 500
|
|
|
|
@uploads_blueprint.route('/admin/files/stats', methods=['GET'])
|
|
@login_required
|
|
@admin_required
|
|
def get_file_stats():
|
|
"""Gibt Statistiken über hochgeladene Dateien zurück"""
|
|
try:
|
|
stats = file_manager.get_storage_stats()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'stats': {
|
|
'total_files': stats.get('total_files', 0),
|
|
'total_size': stats.get('total_size', 0),
|
|
'by_type': stats.get('by_type', {}),
|
|
'by_user': stats.get('by_user', {}),
|
|
'storage_path': UPLOAD_FOLDER
|
|
}
|
|
})
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Abrufen der Datei-Statistiken: {str(e)}")
|
|
return jsonify({'error': 'Fehler beim Abrufen der Statistiken'}), 500
|
|
|
|
@uploads_blueprint.route('/admin/files/cleanup', methods=['POST'])
|
|
@login_required
|
|
@admin_required
|
|
def cleanup_temp_files():
|
|
"""Bereinigt temporäre Dateien"""
|
|
try:
|
|
# Cleanup-Optionen aus Request
|
|
data = request.get_json() or {}
|
|
older_than_hours = data.get('older_than_hours', 24)
|
|
dry_run = data.get('dry_run', False)
|
|
|
|
# Cleanup durchführen
|
|
result = file_manager.cleanup_temp_files(
|
|
older_than_hours=older_than_hours,
|
|
dry_run=dry_run
|
|
)
|
|
|
|
if not dry_run:
|
|
# SystemLog erstellen
|
|
with get_db_session() as db_session:
|
|
SystemLog.log_system_event(
|
|
level="INFO",
|
|
message=f"Temporäre Dateien bereinigt: {result['deleted_count']} Dateien, {result['freed_space']} Bytes",
|
|
module="uploads",
|
|
user_id=current_user.id
|
|
)
|
|
db_session.commit()
|
|
|
|
uploads_logger.info(f"Cleanup durchgeführt: {result['deleted_count']} Dateien gelöscht")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'deleted_count': result['deleted_count'],
|
|
'freed_space': result['freed_space'],
|
|
'dry_run': dry_run
|
|
})
|
|
|
|
except Exception as e:
|
|
uploads_logger.error(f"Fehler beim Cleanup: {str(e)}")
|
|
return jsonify({'error': 'Fehler beim Bereinigen der temporären Dateien'}), 500 |