diff --git a/docker-compose.yml b/docker-compose.yml index 8d5a664..ff9eee1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,6 +8,8 @@ services: - "8125:8125" environment: - PYTHONUNBUFFERED=1 + env_file: + - .env volumes: - shared-repo:/usr/src/app # - ~/repo/taobin_project:/taobin_project diff --git a/main.py b/main.py index 9a5a937..10aa8c2 100644 --- a/main.py +++ b/main.py @@ -12,7 +12,10 @@ app = FastAPI() BASE_DIR = Path("/usr/src/app/taobin_project") # BASE_DIR = Path("/taobin_project") +SERVICE_NAME = os.getenv("SERVICE_NAME") + GIT_REPO_SERVER_URL = os.getenv("GIT_REPO_SERVER_URL") +FRONTEND_NOTIFY_URL = os.getenv("FRONTEND_NOTIFY_URL") ALLOWED_FOLDERS = {"page_drink", "page_drink_disable", @@ -104,7 +107,7 @@ async def commit_files_to_git( async with httpx.AsyncClient() as client: response = await client.post( - git_server_url, + git_server_url + "/commit", data=commit_data, files=multipart_files, timeout=30.0 @@ -112,6 +115,62 @@ async def commit_files_to_git( response.raise_for_status() return response.json() +def backup_existing_file(dest: Path) -> Optional[Path]: + if dest.exists(): + backup_path = dest.with_suffix(dest.suffix + f".bak.{uuid.uuid4().hex[:8]}") + shutil.copy2(dest, backup_path) + print(f"[BACKUP] {dest.name} → {backup_path.name}") + return backup_path + return None + +def rollback_files(saved_files: list[dict], backups: dict[str, Optional[Path]], folder: str, country: Optional[str] = None): + for item in saved_files: + filename = item["filename"] + dest = get_image_dir(folder, country) / filename + backup = backups.get(filename) + + try: + if backup and backup.exists(): + if dest.exists(): + dest.unlink() + shutil.move(str(backup), str(dest)) + print(f"[ROLLBACK] Restored: {filename}") + else: + if dest.exists(): + dest.unlink() + print(f"[ROLLBACK] Deleted new file: {filename}") + except Exception as e: + print(f"[ROLLBACK ERROR] {filename}: {e}") + +def cleanup_backups(backups: dict[str, Optional[Path]]): + for filename, backup in backups.items(): + if backup and backup.exists(): + try: + backup.unlink() + except Exception as e: + print(f"[CLEANUP ERROR] {backup}: {e}") + +async def notify_frontend(uid: str, msg: str): + if not FRONTEND_NOTIFY_URL: + print(f"[NOTIFY SKIP] FRONTEND_NOTIFY_URL not set") + return + + payload = { + "type": "notify", + "payload": { + "from": SERVICE_NAME, + "level": "error", + "to": uid, + "msg": msg + } + } + try: + async with httpx.AsyncClient() as client: + await client.post(FRONTEND_NOTIFY_URL, json=payload, timeout=10.0) + print(f"[NOTIFY SEND SUCCESS] {payload}") + except Exception as e: + print(f"[NOTIFY ERROR] Failed to notify frontend: {e}") + # ───────────────────────────────────────── # UPLOAD @@ -132,7 +191,7 @@ async def upload_images( "country": "tha" } - if not (country): + if not (required_user_fields.get("country")): raise HTTPException(status_code=400, detail="Invalid country") for field, value in required_user_fields.items(): @@ -144,25 +203,43 @@ async def upload_images( validate_folder(folder) saved = [] - for file in files: - ext = validate_ext(file.filename) - filename = Path(file.filename).name - dest = get_image_dir(folder) / filename - with open(dest, "wb") as f: - shutil.copyfileobj(file.file, f) - saved.append({ - "filename": filename, - "url": f"/image/{folder}/{filename}" - }) + backups: dict[str, Optional[Path]] = {} - # Commit to Git - git_response = await commit_files_to_git( - files=files, - folder=folder, - display_name=required_user_fields.get("displayname"), - email=required_user_fields.get("email"), - country=required_user_fields.get("country") - ) + try: + for file in files: + ext = validate_ext(file.filename) + filename = Path(file.filename).name + dest = get_image_dir(folder) / filename + + backups[filename] = backup_existing_file(dest) + + with open(dest, "wb") as f: + shutil.copyfileobj(file.file, f) + saved.append({ + "filename": filename, + "url": f"/image/{folder}/{filename}" + }) + except Exception as e: + rollback_files(saved, backups, folder) + error_msg = f"Save image failed: {str(e)}" + await notify_frontend(uid=uid, msg=error_msg) + raise HTTPException(status_code=500, detail=error_msg) + + try: + git_response = await commit_files_to_git( + files=files, + folder=folder, + display_name=displayname, + email=email, + country=required_user_fields.get("country") + ) + except Exception as e: + rollback_files(saved, backups, folder) + error_msg = f"Git commit failed: {str(e)}" + await notify_frontend(uid=uid, msg=error_msg) + raise HTTPException(status_code=502, detail=f"{error_msg}, files rolled back") + + cleanup_backups(backups) return { "uploaded": saved, @@ -199,24 +276,43 @@ async def upload_inter_images( validate_folder(folder) saved = [] - for file in files: - ext = validate_ext(file.filename) - filename = Path(file.filename).name - dest = get_image_dir(folder, country) / filename - with open(dest, "wb") as f: - shutil.copyfileobj(file.file, f) - saved.append({ - "filename": filename, - "url": f"/inter/{country}/image/{folder}/{filename}" - }) + backups: dict[str, Optional[Path]] = {} - git_response = await commit_files_to_git( - files=files, - folder=folder, - display_name=required_user_fields.get("displayname"), - email=required_user_fields.get("email"), - country=required_user_fields.get("country") - ) + try: + for file in files: + ext = validate_ext(file.filename) + filename = Path(file.filename).name + dest = get_image_dir(folder, country) / filename + + backups[filename] = backup_existing_file(dest) + + with open(dest, "wb") as f: + shutil.copyfileobj(file.file, f) + saved.append({ + "filename": filename, + "url": f"/inter/{country}/image/{folder}/{filename}" + }) + except Exception as e: + rollback_files(saved, backups, folder, country) + error_msg = f"Save image failed: {str(e)}" + await notify_frontend(uid=uid, msg=error_msg) + raise HTTPException(status_code=500, detail=error_msg) + + try: + git_response = await commit_files_to_git( + files=files, + folder=folder, + display_name=displayname, + email=email, + country=country + ) + except Exception as e: + rollback_files(saved, backups, folder, country) + error_msg = f"Git commit failed: {str(e)}" + await notify_frontend(uid=uid, msg=error_msg) + raise HTTPException(status_code=502, detail=f"{error_msg}, files rolled back") + + cleanup_backups(backups) return { "uploaded": saved,