minmal-file-cloud-email-pim.../backend/app/api/client_downloads.py

125 lines
4.0 KiB
Python

"""Client download management - upload builds, serve downloads."""
import os
from pathlib import Path
from flask import request, jsonify, send_from_directory, current_app
from app.api import api_bp
# Supported platforms and their file extensions
PLATFORMS = {
'linux': {'name': 'Linux', 'icon': 'pi-desktop', 'extensions': ['.AppImage', '.deb', '']},
'windows': {'name': 'Windows', 'icon': 'pi-microsoft', 'extensions': ['.msi', '.exe']},
'mac': {'name': 'macOS', 'icon': 'pi-apple', 'extensions': ['.dmg']},
'android': {'name': 'Android', 'icon': 'pi-android', 'extensions': ['.apk']},
'ios': {'name': 'iOS', 'icon': 'pi-apple', 'extensions': ['.ipa']},
}
def _clients_dir():
"""Get the client downloads directory."""
base = Path(current_app.config.get('UPLOAD_PATH', '/app/data/files')).parent
d = base / 'client-downloads'
d.mkdir(parents=True, exist_ok=True)
return d
def _verify_build_token():
"""Verify the build upload token from header or query param."""
expected = os.environ.get('SECRET_KEY', '')
if not expected:
return False
token = request.headers.get('X-Build-Token', '') or request.args.get('build_token', '')
return token == expected
# --- Public: list available clients ---
@api_bp.route('/clients', methods=['GET'])
def list_clients():
"""List available client downloads (public, no auth needed)."""
clients_dir = _clients_dir()
available = []
for platform, info in PLATFORMS.items():
platform_dir = clients_dir / platform
if not platform_dir.exists():
continue
files = sorted(platform_dir.iterdir(), key=lambda f: f.stat().st_mtime, reverse=True)
if not files:
continue
# Take the newest file
latest = files[0]
available.append({
'platform': platform,
'name': info['name'],
'icon': info['icon'],
'filename': latest.name,
'size': latest.stat().st_size,
'updated_at': latest.stat().st_mtime,
'download_url': f'/api/clients/{platform}/download',
})
return jsonify({
'clients': available,
'has_clients': len(available) > 0,
}), 200
@api_bp.route('/clients/<platform>/download', methods=['GET'])
def download_client(platform):
"""Download the latest client for a platform (public, no auth)."""
if platform not in PLATFORMS:
return jsonify({'error': 'Unbekannte Plattform'}), 404
clients_dir = _clients_dir()
platform_dir = clients_dir / platform
if not platform_dir.exists():
return jsonify({'error': 'Kein Client fuer diese Plattform verfuegbar'}), 404
files = sorted(platform_dir.iterdir(), key=lambda f: f.stat().st_mtime, reverse=True)
if not files:
return jsonify({'error': 'Kein Client verfuegbar'}), 404
latest = files[0]
return send_from_directory(str(platform_dir), latest.name, as_attachment=True)
# --- Build upload (authenticated with BUILD_UPLOAD_TOKEN) ---
@api_bp.route('/clients/<platform>/upload', methods=['POST'])
def upload_client(platform):
"""Upload a new client build. Authenticated with BUILD_UPLOAD_TOKEN."""
if not _verify_build_token():
return jsonify({'error': 'Ungueltiger Build-Token'}), 403
if platform not in PLATFORMS:
return jsonify({'error': 'Unbekannte Plattform'}), 404
if 'file' not in request.files:
return jsonify({'error': 'Keine Datei gesendet'}), 400
upload = request.files['file']
if not upload.filename:
return jsonify({'error': 'Leerer Dateiname'}), 400
clients_dir = _clients_dir()
platform_dir = clients_dir / platform
platform_dir.mkdir(parents=True, exist_ok=True)
# Remove old files for this platform (keep only latest)
for old_file in platform_dir.iterdir():
old_file.unlink()
dest = platform_dir / upload.filename
upload.save(str(dest))
return jsonify({
'message': f'{PLATFORMS[platform]["name"]} Client hochgeladen',
'filename': upload.filename,
'size': dest.stat().st_size,
}), 200