feat: Upload in freigegebene Ordner + Benachrichtigung

Share-Links fuer Ordner erlauben jetzt auch Uploads:

Backend:
- POST /share/<token>/upload - Datei in freigegebenen Ordner hochladen
- Passwort-Schutz wird bei Upload ebenfalls geprueft
- share_info gibt jetzt upload_allowed zurueck (true bei Ordner-Shares)
- Email-Benachrichtigung an den Ersteller wenn jemand eine Datei
  hochlaedt (Dateiname, Groesse, IP-Adresse)

Frontend (ShareView):
- Ordner-Shares zeigen jetzt eine Upload-Zone (Drag & Drop + Button)
- Fortschrittsbalken beim Upload mit Datei-Zaehler
- Erfolgs-/Fehlermeldung nach Upload
- Passwortgeschuetzte Ordner-Shares: erst entsperren, dann uploaden

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker
2026-04-11 18:35:10 +02:00
parent e811210977
commit 6a17748552
3 changed files with 199 additions and 12 deletions
+62
View File
@@ -490,6 +490,7 @@ def share_info(token):
'size': f.size,
'mime_type': f.mime_type,
'has_password': bool(link.password_hash),
'upload_allowed': f.is_folder,
}), 200
@@ -557,6 +558,67 @@ def share_download(token):
download_name=f.name)
@api_bp.route('/share/<token>/upload', methods=['POST'])
def share_upload(token):
"""Upload a file via a share link (only if the shared item is a folder)."""
link = ShareLink.query.filter_by(token=token).first()
if not link:
return jsonify({'error': 'Link nicht gefunden'}), 404
if link.is_expired():
return jsonify({'error': 'Link abgelaufen'}), 410
# Check password if set
if link.password_hash:
password = request.form.get('password', '') or request.headers.get('X-Share-Password', '')
if not bcrypt.check_password_hash(link.password_hash, password):
return jsonify({'error': 'Passwort erforderlich'}), 401
f = db.session.get(File, link.file_id)
if not f.is_folder:
return jsonify({'error': 'Upload nur in freigegebene Ordner moeglich'}), 400
if 'file' not in request.files:
return jsonify({'error': 'Keine Datei gesendet'}), 400
uploaded = request.files['file']
if not uploaded.filename:
return jsonify({'error': 'Leerer Dateiname'}), 400
filename = uploaded.filename
mime = uploaded.content_type or mimetypes.guess_type(filename)[0] or 'application/octet-stream'
storage_name = str(uuid.uuid4())
user_dir = _user_upload_dir(f.owner_id)
storage_path = user_dir / storage_name
uploaded.save(str(storage_path))
size = os.path.getsize(str(storage_path))
checksum = _compute_checksum(str(storage_path))
file_obj = File(
owner_id=f.owner_id,
parent_id=f.id,
name=filename,
is_folder=False,
mime_type=mime,
size=size,
storage_path=storage_name,
checksum=checksum,
)
db.session.add(file_obj)
db.session.commit()
# Notify share link creator about the upload
try:
from app.services.system_mail import notify_share_link_upload
notify_share_link_upload(link, f.name, filename, size, request.remote_addr)
except Exception:
pass
return jsonify(file_obj.to_dict()), 201
@api_bp.route('/share/<token>', methods=['DELETE'])
@token_required
def delete_share_link(token):