Spaces:
Sleeping
Sleeping
| # ============= 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 | |
| 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)) | |
| def get_estrutura(): | |
| """Retorna estrutura completa de pastas e arquivos""" | |
| return {"estrutura": projeto_atual['estrutura']} | |
| 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") | |
| 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} | |
| 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' | |
| ) | |
| 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']) | |
| } |