# -*- coding: utf-8 -*- from __future__ import annotations import os from pathlib import Path from typing import List, Dict, Any, Optional from flask import Blueprint, request, jsonify, send_file, abort, current_app from werkzeug.utils import secure_filename # ===== Konfiguracja przez ENV ===== if API_KEY := os.getenv("API_KEY") is None: with open("/home/pi/gpt_cont_api_key", "r") as f: API_KEY = f.read().strip() else: API_KEY = os.getenv("API_KEY", "") # np. openssl rand -hex 32 UPLOAD_DIR = Path(os.getenv("UPLOAD_DIR", "/home/pi/tmp_git/")) # katalog na dysku MAX_CONTENT_MB = int(os.getenv("MAX_CONTENT_MB", "200")) # Google Drive (opcjonalnie) GDRIVE_ENABLE = os.getenv("GDRIVE_ENABLE", "0") == "1" GDRIVE_SA_JSON = os.getenv("GDRIVE_SA_JSON", "") # ścieżka do pliku .json konta serwisowego GDRIVE_FOLDER_ID = os.getenv("GDRIVE_FOLDER_ID", "") # ID folderu na Drive bp = Blueprint("uploader", __name__) # Inicjalizacja katalogu UPLOAD_DIR.mkdir(parents=True, exist_ok=True) def _check_auth() -> bool: """Proste Bearer auth. Jeśli API_KEY puste -> bez auth (niezalecane).""" if not API_KEY: return True auth = request.headers.get("Authorization", "") if auth.startswith("Bearer "): token = auth[7:].strip() return token == API_KEY return False def _safe_join(base: Path, *parts: str) -> Path: """Zapobiega ../ — wymusza pozostanie w katalogu bazowym.""" p = (base.joinpath(*parts)).resolve() if not str(p).startswith(str(base.resolve())): abort(400, description="Invalid path") return p # ===== Google Drive helper ===== _drive_client_cached = None def _get_drive(): global _drive_client_cached if _drive_client_cached is not None: return _drive_client_cached if not (GDRIVE_ENABLE and GDRIVE_SA_JSON and os.path.exists(GDRIVE_SA_JSON)): return None from google.oauth2 import service_account from googleapiclient.discovery import build scopes = ["https://www.googleapis.com/auth/drive.file"] creds = service_account.Credentials.from_service_account_file(GDRIVE_SA_JSON, scopes=scopes) _drive_client_cached = build("drive", "v3", credentials=creds, cache_discovery=False) return _drive_client_cached def upload_to_drive(local_path: Path, filename: str) -> Optional[Dict[str, Any]]: drv = _get_drive() if drv is None: return None from googleapiclient.http import MediaFileUpload file_metadata = {"name": filename} if GDRIVE_FOLDER_ID: file_metadata["parents"] = [GDRIVE_FOLDER_ID] media = MediaFileUpload(str(local_path), resumable=False) created = drv.files().create(body=file_metadata, media_body=media, fields="id,webViewLink,webContentLink").execute() file_id = created.get("id") # Przydatne linki: return { "file_id": file_id, "webViewLink": created.get("webViewLink"), "webContentLink": created.get("webContentLink"), "direct_view": f"https://drive.google.com/file/d/{file_id}/view", "direct_download": f"https://drive.google.com/uc?export=download&id={file_id}", } @bp.get("/health") def health(): return jsonify(ok=True) @bp.post("/upload") def upload(): """Przyjmuje: - multipart/form-data z jednym plikiem ('file') lub wieloma ('files') - opcjonalnie: form 'subdir' (podkatalog), 'drive' (1/0) żeby wymusić wysyłkę na Drive """ if not _check_auth(): return jsonify(error="Unauthorized"), 401 # Limit ciała żądania po stronie Flaska: request.max_content_length = MAX_CONTENT_MB * 1024 * 1024 files = [] if "file" in request.files: files = [request.files["file"]] elif "files" in request.files: files = request.files.getlist("files") else: return jsonify(error="No file provided (use 'file' or 'files')"), 400 subdir = (request.form.get("subdir") or "").strip() target_dir = _safe_join(UPLOAD_DIR, subdir) if subdir else UPLOAD_DIR target_dir.mkdir(parents=True, exist_ok=True) want_drive = (request.form.get("drive") == "1") or (request.args.get("drive") == "1") saved: List[Dict[str, Any]] = [] for f in files: if not f or not f.filename: continue fname = secure_filename(f.filename) dest = _safe_join(target_dir, fname) f.save(dest) item = { "filename": fname, "size": dest.stat().st_size, "path": str(dest.relative_to(UPLOAD_DIR)), "url_hint": f"/api/files/{dest.relative_to(UPLOAD_DIR)}", } if want_drive and GDRIVE_ENABLE: try: gd = upload_to_drive(dest, fname) if gd: item["gdrive"] = gd except Exception as e: # log i idziemy dalej current_app.logger.exception("Drive upload failed: %s", e) item["gdrive_error"] = str(e) saved.append(item) if not saved: return jsonify(error="No valid files"), 400 return jsonify(ok=True, saved=saved) @bp.get("/files/") def get_file(relpath: str): if not _check_auth(): return jsonify(error="Unauthorized"), 401 target = _safe_join(UPLOAD_DIR, relpath) if not target.exists() or not target.is_file(): return jsonify(error="Not found"), 404 return send_file(target, as_attachment=False)