mirror of
				https://github.com/open-webui/openapi-servers
				synced 2025-06-26 18:17:04 +00:00 
			
		
		
		
	migrate pending confirmation store to local filestore for persistence between sessions
This commit is contained in:
		
							parent
							
								
									569d8905ef
								
							
						
					
					
						commit
						5478ae007b
					
				| @ -12,6 +12,7 @@ import difflib | ||||
| import shutil | ||||
| from datetime import datetime, timezone, timedelta # Added timedelta | ||||
| import uuid # Added uuid | ||||
| import json # Added json | ||||
| from config import ALLOWED_DIRECTORIES | ||||
| 
 | ||||
| app = FastAPI( | ||||
| @ -143,10 +144,58 @@ class GetMetadataRequest(BaseModel): | ||||
| # Global state for pending confirmations | ||||
| # ------------------------------------------------------------------------------ | ||||
| 
 | ||||
| # Store pending confirmations: {token: {"path": str, "recursive": bool, "expiry": datetime}} | ||||
| pending_confirmations: Dict[str, Dict] = {} | ||||
| # --- Confirmation Token State Management (using a file) --- | ||||
| CONFIRMATION_FILE = pathlib.Path("./.pending_confirmations.json") | ||||
| CONFIRMATION_TTL_SECONDS = 60 # Token validity period | ||||
| 
 | ||||
| def load_confirmations() -> Dict[str, Dict]: | ||||
|     """Loads pending confirmations from the JSON file.""" | ||||
|     if not CONFIRMATION_FILE.exists(): | ||||
|         return {} | ||||
|     try: | ||||
|         with CONFIRMATION_FILE.open("r") as f: | ||||
|             data = json.load(f) | ||||
|             # Convert expiry string back to datetime object | ||||
|             now = datetime.now(timezone.utc) | ||||
|             valid_confirmations = {} | ||||
|             for token, details in data.items(): | ||||
|                 try: | ||||
|                     details["expiry"] = datetime.fromisoformat(details["expiry"]) | ||||
|                     # Clean up expired tokens during load | ||||
|                     if details["expiry"] > now: | ||||
|                          valid_confirmations[token] = details | ||||
|                 except (ValueError, TypeError, KeyError): | ||||
|                      print(f"Warning: Skipping invalid confirmation data for token {token}") | ||||
|                      continue # Skip invalid entries | ||||
|             return valid_confirmations | ||||
|     except (json.JSONDecodeError, IOError) as e: | ||||
|         print(f"Error loading confirmations file: {e}. Returning empty dict.") | ||||
|         return {} | ||||
| 
 | ||||
| def save_confirmations(confirmations: Dict[str, Dict]): | ||||
|     """Saves pending confirmations to the JSON file.""" | ||||
|     try: | ||||
|         # Convert datetime objects to ISO strings for JSON serialization | ||||
|         serializable_confirmations = {} | ||||
|         for token, details in confirmations.items(): | ||||
|              serializable_details = details.copy() | ||||
|              serializable_details["expiry"] = details["expiry"].isoformat() | ||||
|              serializable_confirmations[token] = serializable_details | ||||
| 
 | ||||
|         with CONFIRMATION_FILE.open("w") as f: | ||||
|             json.dump(serializable_confirmations, f, indent=2) | ||||
|     except IOError as e: | ||||
|         print(f"Error saving confirmations file: {e}") | ||||
| 
 | ||||
| # Clean up the file on startup if it exists from a previous run | ||||
| if CONFIRMATION_FILE.exists(): | ||||
|     # print("Cleaning up stale confirmation file on startup.") | ||||
|     try: | ||||
|         CONFIRMATION_FILE.unlink() | ||||
|     except OSError as e: | ||||
|         # print(f"Warning: Could not delete stale confirmation file: {e}") # Removed print | ||||
|         pass # Silently ignore if cleanup fails, not critical | ||||
| 
 | ||||
| # ------------------------------------------------------------------------------ | ||||
| # Routes | ||||
| # ------------------------------------------------------------------------------ | ||||
| @ -202,9 +251,6 @@ async def write_file(data: WriteFileRequest = Body(...)): | ||||
|     except Exception as e: | ||||
|         raise HTTPException(status_code=500, detail=f"Failed to write to {data.path}: {str(e)}") | ||||
| 
 | ||||
| 
 | ||||
| # Removed duplicate import - already added Union at the top | ||||
| 
 | ||||
| @app.post( | ||||
|     "/edit_file", | ||||
|     response_model=Union[SuccessResponse, DiffResponse], # Use Union for multiple response types | ||||
| @ -356,12 +402,15 @@ async def delete_path(data: DeletePathRequest = Body(...)): | ||||
| 
 | ||||
|     Use 'recursive=True' to delete non-empty directories. | ||||
|     """ | ||||
|     pending_confirmations = load_confirmations() # Load state from file | ||||
|     path = normalize_path(data.path) | ||||
|     now = datetime.now(timezone.utc) | ||||
| 
 | ||||
|     # --- Step 2: Confirmation Request --- | ||||
|     if data.confirmation_token: | ||||
|         # print(f"Attempting confirmation with token: {data.confirmation_token}") # Removed print | ||||
|         if data.confirmation_token not in pending_confirmations: | ||||
|             # print(f"Error: Token '{data.confirmation_token}' not found in pending_confirmations.") # Removed print | ||||
|             raise HTTPException(status_code=400, detail="Invalid or expired confirmation token.") | ||||
| 
 | ||||
|         confirmation_data = pending_confirmations[data.confirmation_token] | ||||
| @ -369,6 +418,7 @@ async def delete_path(data: DeletePathRequest = Body(...)): | ||||
|         # Validate token expiry | ||||
|         if now > confirmation_data["expiry"]: | ||||
|             del pending_confirmations[data.confirmation_token] # Clean up expired token | ||||
|             save_confirmations(pending_confirmations) # Save updated state | ||||
|             raise HTTPException(status_code=400, detail="Confirmation token has expired.") | ||||
| 
 | ||||
|         # Validate request parameters match | ||||
| @ -380,6 +430,7 @@ async def delete_path(data: DeletePathRequest = Body(...)): | ||||
| 
 | ||||
|         # --- Parameters match and token is valid: Proceed with deletion --- | ||||
|         del pending_confirmations[data.confirmation_token] # Consume the token | ||||
|         save_confirmations(pending_confirmations) # Save updated state | ||||
| 
 | ||||
|         try: | ||||
|             if not path.exists(): | ||||
| @ -427,6 +478,7 @@ async def delete_path(data: DeletePathRequest = Body(...)): | ||||
|             "recursive": data.recursive, | ||||
|             "expiry": expiry_time, | ||||
|         } | ||||
|         save_confirmations(pending_confirmations) # Save updated state | ||||
| 
 | ||||
|         # Return confirmation required response | ||||
|         return ConfirmationRequiredResponse( | ||||
| @ -444,13 +496,13 @@ async def move_path(data: MovePathRequest = Body(...)): | ||||
|     Returns JSON success message. | ||||
|     """ | ||||
|     source = normalize_path(data.source_path) | ||||
|     destination = normalize_path(data.destination_path) # Also normalize destination | ||||
|     destination = normalize_path(data.destination_path) | ||||
| 
 | ||||
|     try: | ||||
|         if not source.exists(): | ||||
|             raise HTTPException(status_code=404, detail=f"Source path not found: {data.source_path}") | ||||
| 
 | ||||
|         shutil.move(str(source), str(destination)) # Raises FileNotFoundError, PermissionError etc. | ||||
|         shutil.move(str(source), str(destination)) | ||||
|         return SuccessResponse(message=f"Successfully moved '{data.source_path}' to '{data.destination_path}'") | ||||
| 
 | ||||
|     except PermissionError: | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user