# ============= ide_completa.py ============= from fastapi import APIRouter, HTTPException from fastapi.responses import FileResponse from pydantic import BaseModel import requests import zipfile import io from typing import Dict, List router = APIRouter() # MEMÓRIA COMPARTILHADA DO PROJETO projeto_atual = { "url": "", "arquivos": {}, "estrutura": [] } class LinkGitHub(BaseModel): url: str class EditarArquivo(BaseModel): caminho: str novo_conteudo: str def baixar_github(url: str) -> tuple[Dict[str, str], List[str]]: """Baixa TODOS os arquivos de um repo GitHub público usando a API do GitHub""" # Limpa e extrai owner/repo url = url.rstrip('/') if url.endswith('.git'): url = url[:-4] parts = url.replace('https://github.com/', '').split('/') if len(parts) < 2: raise ValueError("URL inválida") owner, repo = parts[0], parts[1] # Tenta main primeiro, depois master for branch in ['main', 'master']: api_url = f"https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}?recursive=1" resp = requests.get(api_url) if resp.status_code == 200: break else: raise ValueError("Repositório não encontrado ou privado") tree = resp.json().get('tree', []) arquivos = {} estrutura = [] for item in tree: path = item['path'] estrutura.append(path) # Ignora pastas desnecessárias if any(x in path for x in ['node_modules', '.git/', 'build/', '.gradle', '.idea']): continue if item['type'] == 'blob': # Baixa conteúdo do arquivo via raw.githubusercontent.com raw_url = f"https://raw.githubusercontent.com/{owner}/{repo}/{branch}/{path}" try: file_resp = requests.get(raw_url, timeout=5) if file_resp.status_code == 200: try: # Tenta decodificar como texto UTF-8 arquivos[path] = file_resp.content.decode('utf-8') except: # Arquivo binário - ignora pass except: pass return arquivos, estrutura @router.post("/carregar-projeto") async def carregar_projeto(data: LinkGitHub): """Carrega projeto completo do GitHub incluindo pasta lib""" try: arquivos, estrutura = baixar_github(data.url) projeto_atual['url'] = data.url projeto_atual['arquivos'] = arquivos projeto_atual['estrutura'] = estrutura return { "status": "sucesso", "total_arquivos": len(arquivos), "estrutura": estrutura } except Exception as e: raise HTTPException(status_code=400, detail=str(e)) @router.get("/estrutura") def get_estrutura(): """Retorna estrutura completa de pastas e arquivos""" return {"estrutura": projeto_atual['estrutura']} @router.get("/arquivo/{caminho:path}") def get_arquivo(caminho: str): """Obtém conteúdo de um arquivo específico""" if caminho in projeto_atual['arquivos']: return {"conteudo": projeto_atual['arquivos'][caminho]} raise HTTPException(status_code=404, detail="Arquivo não encontrado") @router.post("/editar-arquivo") def editar_arquivo(data: EditarArquivo): """Salva edição de arquivo no projeto em memória""" projeto_atual['arquivos'][data.caminho] = data.novo_conteudo return {"status": "salvo", "arquivo": data.caminho} @router.get("/baixar-projeto") def baixar_projeto(): """Gera ZIP do projeto completo com todas as modificações""" if not projeto_atual['arquivos']: raise HTTPException(status_code=400, detail="Nenhum projeto carregado") # Cria ZIP em memória zip_buffer = io.BytesIO() with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: for caminho, conteudo in projeto_atual['arquivos'].items(): zip_file.writestr(caminho, conteudo) zip_buffer.seek(0) # Salva temporariamente para download with open('/tmp/projeto.zip', 'wb') as f: f.write(zip_buffer.getvalue()) return FileResponse( '/tmp/projeto.zip', media_type='application/zip', filename='projeto_modificado.zip' ) @router.get("/status-projeto") def status_projeto(): """Status atual do projeto carregado""" return { "carregado": bool(projeto_atual['arquivos']), "url": projeto_atual['url'], "total_arquivos": len(projeto_atual['arquivos']), "total_pastas": len(projeto_atual['estrutura']) }